Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f093bdb1e | |||
| 675673f026 | |||
| f1e3630d1b | |||
| 38c48fc99f | |||
| f48f909ee2 | |||
| 433787ff80 | |||
| ed89b6494d | |||
| 7830356f24 | |||
| 5415c71e9e | |||
| 2c5036596e | |||
| 0c2508d715 | |||
| f4422d4af1 | |||
| 88679dfae8 | |||
| 10a66cd399 | |||
| 9451611c3a | |||
| dc52f7f2bc | |||
| f1a368a6c2 | |||
| ad12779b73 | |||
| 7d2ac327a3 | |||
| f62d4aa5f2 | |||
| a367365ce5 | |||
| aad915ad86 | |||
| 5443c7c22a | |||
| 5a915ca06a | |||
| 47a5fc8c01 | |||
| 523de9d8b9 | |||
| b07228922f | |||
| 4c70118be6 | |||
| 868ce1fcae | |||
| b1749b1347 | |||
| d8bdf5472f | |||
| 7a1241cbf2 | |||
| 27e2dc7136 | |||
| 8b1c733943 | |||
| 331d6839d9 | |||
| bf2f380f56 | |||
| a11b081510 | |||
| c27f073938 | |||
| 59bcb9244e | |||
| c21ff803fe | |||
| 7056ab7e4e | |||
| 1b1619de06 | |||
| 538d4bad65 | |||
| 85b3c22dc7 | |||
| 675466a0f5 | |||
| 870382097b | |||
| 20fcbd78e3 | |||
| 0094cdef45 | |||
| 8f1eba7fbe | |||
| 436c9d60cb | |||
| 083f110cf4 | |||
| f263ee96b3 | |||
| 271f8d7fa9 | |||
| a8c8939a3f | |||
| 71a37da1a4 | |||
| 81f7d9c90e | |||
| d5a7560479 | |||
| 209c56e592 | |||
| 9a49185183 | |||
| a2800464c8 | |||
| aab53ee919 | |||
| 4a64927c12 | |||
| bc80436a34 | |||
| 01270c6938 | |||
| 18ab0abe97 | |||
| 8a36a44341 | |||
| 95e7d0c90b | |||
| c60453ea31 | |||
| 7b22d67920 | |||
| f6eadaad57 | |||
| 6007b84e7a | |||
| 4d225a43ef | |||
| 55d4a6eaad | |||
| 5613df6031 | |||
| d34f4e1fea | |||
| a471cb071a | |||
| 317aaaf23c | |||
| 6f0d10935c | |||
| fd9043f749 | |||
| b0b3746968 | |||
| 1cdf66c44b | |||
| d3feaa8964 | |||
| f7043ab9c7 | |||
| e02af03be0 | |||
| d0d8e92dad |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -47,3 +47,4 @@ coverage*.xml
|
||||
|
||||
# Publish output
|
||||
publish/
|
||||
mauiplan.md
|
||||
|
||||
182
AnimationManager.cs
Normal file
182
AnimationManager.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux;
|
||||
|
||||
public static class AnimationManager
|
||||
{
|
||||
private class RunningAnimation
|
||||
{
|
||||
public required SkiaView View { get; set; }
|
||||
public string PropertyName { get; set; } = "";
|
||||
public double StartValue { get; set; }
|
||||
public double EndValue { get; set; }
|
||||
public DateTime StartTime { get; set; }
|
||||
public uint Duration { get; set; }
|
||||
public Easing Easing { get; set; } = Easing.Linear;
|
||||
public required TaskCompletionSource<bool> Completion { get; set; }
|
||||
public CancellationToken Token { get; set; }
|
||||
}
|
||||
|
||||
private static readonly List<RunningAnimation> _animations = new();
|
||||
private static bool _isRunning;
|
||||
private static CancellationTokenSource? _cts;
|
||||
|
||||
private static void EnsureRunning()
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
_isRunning = true;
|
||||
_cts = new CancellationTokenSource();
|
||||
_ = RunAnimationLoop(_cts.Token);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task RunAnimationLoop(CancellationToken token)
|
||||
{
|
||||
while (!token.IsCancellationRequested && _animations.Count > 0)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var completed = new List<RunningAnimation>();
|
||||
|
||||
foreach (var animation in _animations.ToList())
|
||||
{
|
||||
if (animation.Token.IsCancellationRequested)
|
||||
{
|
||||
completed.Add(animation);
|
||||
animation.Completion.TrySetResult(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
var progress = Math.Clamp(
|
||||
(now - animation.StartTime).TotalMilliseconds / animation.Duration,
|
||||
0.0, 1.0);
|
||||
|
||||
var easedProgress = animation.Easing.Ease(progress);
|
||||
var value = animation.StartValue + (animation.EndValue - animation.StartValue) * easedProgress;
|
||||
|
||||
SetProperty(animation.View, animation.PropertyName, value);
|
||||
|
||||
if (progress >= 1.0)
|
||||
{
|
||||
completed.Add(animation);
|
||||
animation.Completion.TrySetResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var animation in completed)
|
||||
{
|
||||
_animations.Remove(animation);
|
||||
}
|
||||
|
||||
if (_animations.Count == 0)
|
||||
{
|
||||
_isRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
await Task.Delay(16, token);
|
||||
}
|
||||
|
||||
_isRunning = false;
|
||||
}
|
||||
|
||||
private static void SetProperty(SkiaView view, string propertyName, double value)
|
||||
{
|
||||
switch (propertyName)
|
||||
{
|
||||
case nameof(SkiaView.Opacity):
|
||||
view.Opacity = (float)value;
|
||||
break;
|
||||
case nameof(SkiaView.Scale):
|
||||
view.Scale = value;
|
||||
break;
|
||||
case nameof(SkiaView.ScaleX):
|
||||
view.ScaleX = value;
|
||||
break;
|
||||
case nameof(SkiaView.ScaleY):
|
||||
view.ScaleY = value;
|
||||
break;
|
||||
case nameof(SkiaView.Rotation):
|
||||
view.Rotation = value;
|
||||
break;
|
||||
case nameof(SkiaView.RotationX):
|
||||
view.RotationX = value;
|
||||
break;
|
||||
case nameof(SkiaView.RotationY):
|
||||
view.RotationY = value;
|
||||
break;
|
||||
case nameof(SkiaView.TranslationX):
|
||||
view.TranslationX = value;
|
||||
break;
|
||||
case nameof(SkiaView.TranslationY):
|
||||
view.TranslationY = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static double GetProperty(SkiaView view, string propertyName)
|
||||
{
|
||||
return propertyName switch
|
||||
{
|
||||
nameof(SkiaView.Opacity) => view.Opacity,
|
||||
nameof(SkiaView.Scale) => view.Scale,
|
||||
nameof(SkiaView.ScaleX) => view.ScaleX,
|
||||
nameof(SkiaView.ScaleY) => view.ScaleY,
|
||||
nameof(SkiaView.Rotation) => view.Rotation,
|
||||
nameof(SkiaView.RotationX) => view.RotationX,
|
||||
nameof(SkiaView.RotationY) => view.RotationY,
|
||||
nameof(SkiaView.TranslationX) => view.TranslationX,
|
||||
nameof(SkiaView.TranslationY) => view.TranslationY,
|
||||
_ => 0.0
|
||||
};
|
||||
}
|
||||
|
||||
public static Task<bool> AnimateAsync(
|
||||
SkiaView view,
|
||||
string propertyName,
|
||||
double targetValue,
|
||||
uint length = 250,
|
||||
Easing? easing = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
CancelAnimation(view, propertyName);
|
||||
|
||||
var animation = new RunningAnimation
|
||||
{
|
||||
View = view,
|
||||
PropertyName = propertyName,
|
||||
StartValue = GetProperty(view, propertyName),
|
||||
EndValue = targetValue,
|
||||
StartTime = DateTime.UtcNow,
|
||||
Duration = length,
|
||||
Easing = easing ?? Easing.Linear,
|
||||
Completion = new TaskCompletionSource<bool>(),
|
||||
Token = cancellationToken
|
||||
};
|
||||
|
||||
_animations.Add(animation);
|
||||
EnsureRunning();
|
||||
|
||||
return animation.Completion.Task;
|
||||
}
|
||||
|
||||
public static void CancelAnimation(SkiaView view, string propertyName)
|
||||
{
|
||||
var animation = _animations.FirstOrDefault(a => a.View == view && a.PropertyName == propertyName);
|
||||
if (animation != null)
|
||||
{
|
||||
_animations.Remove(animation);
|
||||
animation.Completion.TrySetResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CancelAnimations(SkiaView view)
|
||||
{
|
||||
foreach (var animation in _animations.Where(a => a.View == view).ToList())
|
||||
{
|
||||
_animations.Remove(animation);
|
||||
animation.Completion.TrySetResult(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
297
CLAUDE.md
Normal file
297
CLAUDE.md
Normal file
@@ -0,0 +1,297 @@
|
||||
# CLAUDE.md - OpenMaui XAML Reconstruction
|
||||
|
||||
## CURRENT TASK: Reconstruct XAML from Decompiled Code
|
||||
|
||||
The sample applications (ShellDemo, TodoApp, XamlBrowser) were recovered from decompiled DLLs. The XAML files were compiled away - we have only the generated `InitializeComponent()` code. **Screenshots will be provided** to help verify visual accuracy.
|
||||
|
||||
---
|
||||
|
||||
## Project Locations
|
||||
|
||||
| What | Path |
|
||||
|------|------|
|
||||
| **Main codebase** | `/Users/nible/Documents/GitHub/maui-linux-main/` |
|
||||
| **Samples (target)** | `/Users/nible/Documents/GitHub/maui-linux-main/samples_temp/` |
|
||||
| **Decompiled samples** | `/Users/nible/Documents/GitHub/recovered/source/` |
|
||||
|
||||
---
|
||||
|
||||
## Git Branch
|
||||
|
||||
**Work on `final` branch.** Commit frequently.
|
||||
|
||||
```bash
|
||||
git branch # Should show: * final
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## XAML Reconstruction Overview
|
||||
|
||||
### ShellDemo (10 pages + shell + app)
|
||||
|
||||
| File | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| App.xaml | [ ] | Colors, Styles (ThemedEntry, TitleLabel, etc.) |
|
||||
| AppShell.xaml | [ ] | Shell with FlyoutHeader, 9 FlyoutItems |
|
||||
| HomePage.xaml | [ ] | Welcome screen with logo |
|
||||
| ButtonsPage.xaml | [ ] | Button demos |
|
||||
| TextInputPage.xaml | [ ] | Entry/Editor demos |
|
||||
| SelectionPage.xaml | [ ] | CheckBox, Switch, RadioButton demos |
|
||||
| PickersPage.xaml | [ ] | DatePicker, TimePicker, Picker demos |
|
||||
| ListsPage.xaml | [ ] | CollectionView demos |
|
||||
| ProgressPage.xaml | [ ] | ProgressBar, ActivityIndicator demos |
|
||||
| GridsPage.xaml | [ ] | Grid layout demos |
|
||||
| AboutPage.xaml | [ ] | About information |
|
||||
| DetailPage.xaml | [ ] | Navigation detail page |
|
||||
|
||||
### TodoApp (app + 3 pages)
|
||||
|
||||
| File | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| App.xaml | [ ] | Colors, Icon strings |
|
||||
| TodoListPage.xaml | [ ] | Main list with swipe actions |
|
||||
| NewTodoPage.xaml | [ ] | Add new todo form |
|
||||
| TodoDetailPage.xaml | [ ] | Edit todo details |
|
||||
|
||||
### XamlBrowser (app + 1 page) - COMPLETE
|
||||
|
||||
| File | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| App.xaml | [x] | Colors, styles (NavButtonStyle, GoButtonStyle, AddressBarStyle, StatusLabelStyle) |
|
||||
| App.xaml.cs | [x] | BrowserApp with ToggleTheme() |
|
||||
| MainPage.xaml | [x] | Toolbar with nav buttons, address bar, WebView, status bar |
|
||||
| MainPage.xaml.cs | [x] | Navigation logic, progress animation, theme toggle |
|
||||
| MauiProgram.cs | [x] | UseLinuxPlatform() setup |
|
||||
| Program.cs | [x] | LinuxProgramHost entry point |
|
||||
| Resources/Images/*.svg | [x] | 10 toolbar icons (dark/light variants) |
|
||||
|
||||
---
|
||||
|
||||
## How to Reconstruct XAML
|
||||
|
||||
### Step 1: Read the decompiled InitializeComponent()
|
||||
|
||||
Look for patterns like:
|
||||
```csharp
|
||||
// Setting a property
|
||||
((BindableObject)val8).SetValue(Label.TextProperty, (object)"OpenMaui");
|
||||
|
||||
// AppThemeBinding (light/dark mode)
|
||||
val7.Light = "White";
|
||||
val7.Dark = "#E0E0E0";
|
||||
|
||||
// StaticResource
|
||||
val.Key = "PrimaryColor";
|
||||
|
||||
// Layout hierarchy
|
||||
((Layout)val12).Children.Add((IView)(object)val6);
|
||||
```
|
||||
|
||||
### Step 2: Convert to XAML
|
||||
|
||||
```csharp
|
||||
// This C#:
|
||||
((BindableObject)val8).SetValue(Label.TextProperty, (object)"OpenMaui");
|
||||
((BindableObject)val8).SetValue(Label.FontSizeProperty, (object)22.0);
|
||||
((BindableObject)val8).SetValue(Label.FontAttributesProperty, (object)(FontAttributes)1);
|
||||
val7.Light = "White";
|
||||
val7.Dark = "#E0E0E0";
|
||||
((BindableObject)val8).SetBinding(Label.TextColorProperty, val74);
|
||||
```
|
||||
|
||||
```xml
|
||||
<!-- Becomes this XAML: -->
|
||||
<Label Text="OpenMaui"
|
||||
FontSize="22"
|
||||
FontAttributes="Bold"
|
||||
TextColor="{AppThemeBinding Light=White, Dark=#E0E0E0}" />
|
||||
```
|
||||
|
||||
### Step 3: Verify with screenshots (when provided)
|
||||
|
||||
Compare the reconstructed XAML against the actual screenshots to ensure visual fidelity.
|
||||
|
||||
---
|
||||
|
||||
## App.xaml Resources Reference
|
||||
|
||||
### ShellDemo Colors (extracted from decompiled)
|
||||
|
||||
```xml
|
||||
<!-- Light theme -->
|
||||
<Color x:Key="PrimaryColor">#2196F3</Color>
|
||||
<Color x:Key="PrimaryDarkColor">#1976D2</Color>
|
||||
<Color x:Key="AccentColor">#FF4081</Color>
|
||||
<Color x:Key="PageBackgroundLight">#F8F8F8</Color>
|
||||
<Color x:Key="CardBackgroundLight">#FFFFFF</Color>
|
||||
<Color x:Key="TextPrimaryLight">#212121</Color>
|
||||
<Color x:Key="TextSecondaryLight">#757575</Color>
|
||||
<Color x:Key="BorderLight">#E0E0E0</Color>
|
||||
<Color x:Key="EntryBackgroundLight">#F9F9F9</Color>
|
||||
<Color x:Key="ShellBackgroundLight">#FFFFFF</Color>
|
||||
<Color x:Key="FlyoutBackgroundLight">#FFFFFF</Color>
|
||||
<Color x:Key="ProgressTrackLight">#E0E0E0</Color>
|
||||
|
||||
<!-- Dark theme -->
|
||||
<Color x:Key="PageBackgroundDark">#121212</Color>
|
||||
<Color x:Key="CardBackgroundDark">#1E1E1E</Color>
|
||||
<Color x:Key="TextPrimaryDark">#FFFFFF</Color>
|
||||
<Color x:Key="TextSecondaryDark">#B0B0B0</Color>
|
||||
<Color x:Key="BorderDark">#424242</Color>
|
||||
<Color x:Key="EntryBackgroundDark">#2C2C2C</Color>
|
||||
<Color x:Key="ShellBackgroundDark">#1E1E1E</Color>
|
||||
<Color x:Key="FlyoutBackgroundDark">#1E1E1E</Color>
|
||||
<Color x:Key="ProgressTrackDark">#424242</Color>
|
||||
```
|
||||
|
||||
### ShellDemo Styles (extracted from decompiled)
|
||||
|
||||
- **ThemedEntry**: BackgroundColor, TextColor, PlaceholderColor with AppThemeBinding
|
||||
- **ThemedEditor**: BackgroundColor, TextColor, PlaceholderColor with AppThemeBinding
|
||||
- **TitleLabel**: FontSize=24, FontAttributes=Bold, TextColor with AppThemeBinding
|
||||
- **SubtitleLabel**: FontSize=16, TextColor with AppThemeBinding
|
||||
- **ThemedFrame**: BackgroundColor, BorderColor with AppThemeBinding
|
||||
- **ThemedProgressBar**: ProgressColor=PrimaryColor, BackgroundColor with AppThemeBinding
|
||||
- **PrimaryButton**: BackgroundColor=PrimaryColor, TextColor=White
|
||||
- **SecondaryButton**: Light/dark themed background and text
|
||||
|
||||
### TodoApp Colors
|
||||
|
||||
```xml
|
||||
<Color x:Key="PrimaryColor">#5C6BC0</Color>
|
||||
<Color x:Key="PrimaryDarkColor">#3949AB</Color>
|
||||
<Color x:Key="AccentColor">#26A69A</Color>
|
||||
<Color x:Key="DangerColor">#EF5350</Color>
|
||||
<!-- ... plus light/dark theme colors -->
|
||||
```
|
||||
|
||||
### TodoApp Icons (Material Design)
|
||||
|
||||
```xml
|
||||
<x:String x:Key="IconAdd"></x:String>
|
||||
<x:String x:Key="IconDelete"></x:String>
|
||||
<x:String x:Key="IconSave"></x:String>
|
||||
<x:String x:Key="IconCheck"></x:String>
|
||||
<x:String x:Key="IconEdit"></x:String>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AppShell.xaml Structure (ShellDemo)
|
||||
|
||||
From decompiled code, the shell has:
|
||||
|
||||
```xml
|
||||
<Shell Title="OpenMaui Controls Demo"
|
||||
FlyoutBehavior="Flyout"
|
||||
FlyoutBackgroundColor="{AppThemeBinding Light={StaticResource FlyoutBackgroundLight}, Dark={StaticResource FlyoutBackgroundDark}}">
|
||||
|
||||
<!-- FlyoutHeader: Grid with logo and title -->
|
||||
<Shell.FlyoutHeader>
|
||||
<Grid BackgroundColor="{AppThemeBinding ...}" HeightRequest="140" Padding="15">
|
||||
<HorizontalStackLayout VerticalOptions="Center" Spacing="12">
|
||||
<Image Source="openmaui_logo.svg" WidthRequest="60" HeightRequest="60" />
|
||||
<VerticalStackLayout VerticalOptions="Center">
|
||||
<Label Text="OpenMaui" FontSize="22" FontAttributes="Bold"
|
||||
TextColor="{AppThemeBinding Light=White, Dark=#E0E0E0}" />
|
||||
<Label Text="Controls Demo" FontSize="13" Opacity="0.9"
|
||||
TextColor="{AppThemeBinding Light=White, Dark=#B0B0B0}" />
|
||||
</VerticalStackLayout>
|
||||
</HorizontalStackLayout>
|
||||
</Grid>
|
||||
</Shell.FlyoutHeader>
|
||||
|
||||
<!-- FlyoutItems with emoji icons -->
|
||||
<FlyoutItem Title="Home" Route="Home">
|
||||
<FlyoutItem.Icon><FontImageSource Glyph="🏠" FontFamily="Default" Color="{AppThemeBinding ...}" /></FlyoutItem.Icon>
|
||||
<ShellContent ContentTemplate="{DataTemplate local:HomePage}" />
|
||||
</FlyoutItem>
|
||||
|
||||
<FlyoutItem Title="Buttons" Route="Buttons">...</FlyoutItem>
|
||||
<FlyoutItem Title="Text Input" Route="TextInput">...</FlyoutItem>
|
||||
<FlyoutItem Title="Selection" Route="Selection">...</FlyoutItem>
|
||||
<FlyoutItem Title="Pickers" Route="Pickers">...</FlyoutItem>
|
||||
<FlyoutItem Title="Lists" Route="Lists">...</FlyoutItem>
|
||||
<FlyoutItem Title="Progress" Route="Progress">...</FlyoutItem>
|
||||
<FlyoutItem Title="Grids" Route="Grids">...</FlyoutItem>
|
||||
<FlyoutItem Title="About" Route="About">...</FlyoutItem>
|
||||
</Shell>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Decompiled File Locations
|
||||
|
||||
| Sample | Decompiled Path |
|
||||
|--------|-----------------|
|
||||
| ShellDemo | `/Users/nible/Documents/GitHub/recovered/source/ShellDemo/ShellDemo/` |
|
||||
| TodoApp | `/Users/nible/Documents/GitHub/recovered/source/TodoApp/TodoApp/` |
|
||||
| XamlBrowser | `/Users/nible/Documents/GitHub/recovered/source/XamlBrowser/XamlBrowser/` |
|
||||
|
||||
---
|
||||
|
||||
## Build Command
|
||||
|
||||
```bash
|
||||
cd /Users/nible/Documents/GitHub/maui-linux-main
|
||||
dotnet build OpenMaui.Controls.Linux.csproj
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Patterns in Decompiled Code
|
||||
|
||||
### 1. Color Values
|
||||
```csharp
|
||||
Color val = new Color(11f / 85f, 0.5882353f, 81f / 85f, 1f);
|
||||
// = Color.FromRgba(0.129, 0.588, 0.953, 1.0) = #2196F3
|
||||
```
|
||||
|
||||
### 2. AppThemeBinding
|
||||
```csharp
|
||||
AppThemeBindingExtension val7 = new AppThemeBindingExtension();
|
||||
val7.Light = "White";
|
||||
val7.Dark = "#E0E0E0";
|
||||
```
|
||||
Becomes: `{AppThemeBinding Light=White, Dark=#E0E0E0}`
|
||||
|
||||
### 3. StaticResource
|
||||
```csharp
|
||||
val.Key = "PrimaryColor";
|
||||
```
|
||||
Becomes: `{StaticResource PrimaryColor}`
|
||||
|
||||
### 4. Layout Hierarchy
|
||||
```csharp
|
||||
((Layout)val12).Children.Add((IView)(object)val6);
|
||||
((Layout)val12).Children.Add((IView)(object)val11);
|
||||
```
|
||||
The children are added in order - first child is val6, second is val11.
|
||||
|
||||
### 5. FontAttributes Enum
|
||||
```csharp
|
||||
(FontAttributes)1 // Bold
|
||||
(FontAttributes)2 // Italic
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Workflow for Each File
|
||||
|
||||
1. **Read decompiled** `InitializeComponent()` method
|
||||
2. **Extract** all UI elements and their properties
|
||||
3. **Write XAML** with proper structure
|
||||
4. **Create code-behind** (usually just constructor calling InitializeComponent)
|
||||
5. **Verify** against screenshot if available
|
||||
6. **Update tracking** in this file
|
||||
7. **Commit** with descriptive message
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- The decompiled code has ALL the information needed - it's just in C# form instead of XAML
|
||||
- Screenshots will help verify visual accuracy
|
||||
- Focus on one file at a time
|
||||
- Commit after each completed file
|
||||
39
Controls/EntryExtensions.cs
Normal file
39
Controls/EntryExtensions.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Controls;
|
||||
|
||||
namespace Microsoft.Maui.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Provides attached properties for Entry controls.
|
||||
/// </summary>
|
||||
public static class EntryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Attached property for SelectAllOnDoubleClick behavior.
|
||||
/// When true, double-clicking the entry selects all text instead of just the word.
|
||||
/// </summary>
|
||||
public static readonly BindableProperty SelectAllOnDoubleClickProperty =
|
||||
BindableProperty.CreateAttached(
|
||||
"SelectAllOnDoubleClick",
|
||||
typeof(bool),
|
||||
typeof(EntryExtensions),
|
||||
false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SelectAllOnDoubleClick value for the specified entry.
|
||||
/// </summary>
|
||||
public static bool GetSelectAllOnDoubleClick(BindableObject view)
|
||||
{
|
||||
return (bool)view.GetValue(SelectAllOnDoubleClickProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the SelectAllOnDoubleClick value for the specified entry.
|
||||
/// </summary>
|
||||
public static void SetSelectAllOnDoubleClick(BindableObject view, bool value)
|
||||
{
|
||||
view.SetValue(SelectAllOnDoubleClickProperty, value);
|
||||
}
|
||||
}
|
||||
20
Converters/ColorExtensions.cs
Normal file
20
Converters/ColorExtensions.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Graphics;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Converters;
|
||||
|
||||
public static class ColorExtensions
|
||||
{
|
||||
public static SKColor ToSKColor(this Color color)
|
||||
{
|
||||
return SKColorTypeConverter.ToSKColor(color);
|
||||
}
|
||||
|
||||
public static Color ToMauiColor(this SKColor color)
|
||||
{
|
||||
return SKColorTypeConverter.ToMauiColor(color);
|
||||
}
|
||||
}
|
||||
@@ -235,25 +235,3 @@ public class SKColorTypeConverter : TypeConverter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for color conversion.
|
||||
/// </summary>
|
||||
public static class ColorExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a MAUI Color to an SKColor.
|
||||
/// </summary>
|
||||
public static SKColor ToSKColor(this Color color)
|
||||
{
|
||||
return SKColorTypeConverter.ToSKColor(color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an SKColor to a MAUI Color.
|
||||
/// </summary>
|
||||
public static Color ToMauiColor(this SKColor color)
|
||||
{
|
||||
return SKColorTypeConverter.ToMauiColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
75
Converters/SKPointTypeConverter.cs
Normal file
75
Converters/SKPointTypeConverter.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Converters;
|
||||
|
||||
public class SKPointTypeConverter : TypeConverter
|
||||
{
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
|
||||
{
|
||||
return sourceType == typeof(string) || sourceType == typeof(Point) || base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
|
||||
{
|
||||
return destinationType == typeof(string) || destinationType == typeof(Point) || base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
|
||||
{
|
||||
if (value is Point point)
|
||||
{
|
||||
return new SKPoint((float)point.X, (float)point.Y);
|
||||
}
|
||||
|
||||
if (value is string str)
|
||||
{
|
||||
return ParsePoint(str);
|
||||
}
|
||||
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
|
||||
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
|
||||
{
|
||||
if (value is SKPoint point)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return $"{point.X},{point.Y}";
|
||||
}
|
||||
|
||||
if (destinationType == typeof(Point))
|
||||
{
|
||||
return new Point(point.X, point.Y);
|
||||
}
|
||||
}
|
||||
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
|
||||
private static SKPoint ParsePoint(string str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
{
|
||||
return SKPoint.Empty;
|
||||
}
|
||||
|
||||
str = str.Trim();
|
||||
var parts = str.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (parts.Length == 2 &&
|
||||
float.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var x) &&
|
||||
float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var y))
|
||||
{
|
||||
return new SKPoint(x, y);
|
||||
}
|
||||
|
||||
return SKPoint.Empty;
|
||||
}
|
||||
}
|
||||
@@ -137,192 +137,3 @@ public class SKRectTypeConverter : TypeConverter
|
||||
return SKRect.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type converter for SKSize.
|
||||
/// </summary>
|
||||
public class SKSizeTypeConverter : TypeConverter
|
||||
{
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
|
||||
{
|
||||
return sourceType == typeof(string) ||
|
||||
sourceType == typeof(Size) ||
|
||||
base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
|
||||
{
|
||||
return destinationType == typeof(string) ||
|
||||
destinationType == typeof(Size) ||
|
||||
base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
|
||||
{
|
||||
if (value is Size size)
|
||||
{
|
||||
return new SKSize((float)size.Width, (float)size.Height);
|
||||
}
|
||||
|
||||
if (value is string str)
|
||||
{
|
||||
return ParseSize(str);
|
||||
}
|
||||
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
|
||||
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
|
||||
{
|
||||
if (value is SKSize size)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return $"{size.Width},{size.Height}";
|
||||
}
|
||||
|
||||
if (destinationType == typeof(Size))
|
||||
{
|
||||
return new Size(size.Width, size.Height);
|
||||
}
|
||||
}
|
||||
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
|
||||
private static SKSize ParseSize(string str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return SKSize.Empty;
|
||||
|
||||
str = str.Trim();
|
||||
var parts = str.Split(new[] { ',', ' ', 'x', 'X' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (parts.Length == 1)
|
||||
{
|
||||
if (float.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var uniform))
|
||||
{
|
||||
return new SKSize(uniform, uniform);
|
||||
}
|
||||
}
|
||||
else if (parts.Length == 2)
|
||||
{
|
||||
if (float.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var width) &&
|
||||
float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var height))
|
||||
{
|
||||
return new SKSize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
return SKSize.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type converter for SKPoint.
|
||||
/// </summary>
|
||||
public class SKPointTypeConverter : TypeConverter
|
||||
{
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
|
||||
{
|
||||
return sourceType == typeof(string) ||
|
||||
sourceType == typeof(Point) ||
|
||||
base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
|
||||
{
|
||||
return destinationType == typeof(string) ||
|
||||
destinationType == typeof(Point) ||
|
||||
base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
|
||||
{
|
||||
if (value is Point point)
|
||||
{
|
||||
return new SKPoint((float)point.X, (float)point.Y);
|
||||
}
|
||||
|
||||
if (value is string str)
|
||||
{
|
||||
return ParsePoint(str);
|
||||
}
|
||||
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
|
||||
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
|
||||
{
|
||||
if (value is SKPoint point)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return $"{point.X},{point.Y}";
|
||||
}
|
||||
|
||||
if (destinationType == typeof(Point))
|
||||
{
|
||||
return new Point(point.X, point.Y);
|
||||
}
|
||||
}
|
||||
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
|
||||
private static SKPoint ParsePoint(string str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
return SKPoint.Empty;
|
||||
|
||||
str = str.Trim();
|
||||
var parts = str.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
if (float.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var x) &&
|
||||
float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var y))
|
||||
{
|
||||
return new SKPoint(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
return SKPoint.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for SkiaSharp type conversions.
|
||||
/// </summary>
|
||||
public static class SKTypeExtensions
|
||||
{
|
||||
public static SKRect ToSKRect(this Thickness thickness)
|
||||
{
|
||||
return SKRectTypeConverter.ThicknessToSKRect(thickness);
|
||||
}
|
||||
|
||||
public static Thickness ToThickness(this SKRect rect)
|
||||
{
|
||||
return SKRectTypeConverter.SKRectToThickness(rect);
|
||||
}
|
||||
|
||||
public static SKSize ToSKSize(this Size size)
|
||||
{
|
||||
return new SKSize((float)size.Width, (float)size.Height);
|
||||
}
|
||||
|
||||
public static Size ToSize(this SKSize size)
|
||||
{
|
||||
return new Size(size.Width, size.Height);
|
||||
}
|
||||
|
||||
public static SKPoint ToSKPoint(this Point point)
|
||||
{
|
||||
return new SKPoint((float)point.X, (float)point.Y);
|
||||
}
|
||||
|
||||
public static Point ToPoint(this SKPoint point)
|
||||
{
|
||||
return new Point(point.X, point.Y);
|
||||
}
|
||||
}
|
||||
|
||||
82
Converters/SKSizeTypeConverter.cs
Normal file
82
Converters/SKSizeTypeConverter.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Converters;
|
||||
|
||||
public class SKSizeTypeConverter : TypeConverter
|
||||
{
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
|
||||
{
|
||||
return sourceType == typeof(string) || sourceType == typeof(Size) || base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
|
||||
{
|
||||
return destinationType == typeof(string) || destinationType == typeof(Size) || base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
|
||||
{
|
||||
if (value is Size size)
|
||||
{
|
||||
return new SKSize((float)size.Width, (float)size.Height);
|
||||
}
|
||||
|
||||
if (value is string str)
|
||||
{
|
||||
return ParseSize(str);
|
||||
}
|
||||
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
|
||||
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
|
||||
{
|
||||
if (value is SKSize size)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return $"{size.Width},{size.Height}";
|
||||
}
|
||||
|
||||
if (destinationType == typeof(Size))
|
||||
{
|
||||
return new Size(size.Width, size.Height);
|
||||
}
|
||||
}
|
||||
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
|
||||
private static SKSize ParseSize(string str)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(str))
|
||||
{
|
||||
return SKSize.Empty;
|
||||
}
|
||||
|
||||
str = str.Trim();
|
||||
var parts = str.Split(new[] { ',', ' ', 'x', 'X' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (parts.Length == 1)
|
||||
{
|
||||
if (float.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var single))
|
||||
{
|
||||
return new SKSize(single, single);
|
||||
}
|
||||
}
|
||||
else if (parts.Length == 2 &&
|
||||
float.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var width) &&
|
||||
float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var height))
|
||||
{
|
||||
return new SKSize(width, height);
|
||||
}
|
||||
|
||||
return SKSize.Empty;
|
||||
}
|
||||
}
|
||||
40
Converters/SKTypeExtensions.cs
Normal file
40
Converters/SKTypeExtensions.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Graphics;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Converters;
|
||||
|
||||
public static class SKTypeExtensions
|
||||
{
|
||||
public static SKRect ToSKRect(this Thickness thickness)
|
||||
{
|
||||
return SKRectTypeConverter.ThicknessToSKRect(thickness);
|
||||
}
|
||||
|
||||
public static Thickness ToThickness(this SKRect rect)
|
||||
{
|
||||
return SKRectTypeConverter.SKRectToThickness(rect);
|
||||
}
|
||||
|
||||
public static SKSize ToSKSize(this Size size)
|
||||
{
|
||||
return new SKSize((float)size.Width, (float)size.Height);
|
||||
}
|
||||
|
||||
public static Size ToSize(this SKSize size)
|
||||
{
|
||||
return new Size(size.Width, size.Height);
|
||||
}
|
||||
|
||||
public static SKPoint ToSKPoint(this Point point)
|
||||
{
|
||||
return new SKPoint((float)point.X, (float)point.Y);
|
||||
}
|
||||
|
||||
public static Point ToPoint(this SKPoint point)
|
||||
{
|
||||
return new Point(point.X, point.Y);
|
||||
}
|
||||
}
|
||||
76
Dispatching/LinuxDispatcher.cs
Normal file
76
Dispatching/LinuxDispatcher.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using Microsoft.Maui.Dispatching;
|
||||
using Microsoft.Maui.Platform.Linux.Native;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Dispatching;
|
||||
|
||||
public class LinuxDispatcher : IDispatcher
|
||||
{
|
||||
private static int _mainThreadId;
|
||||
|
||||
private static LinuxDispatcher? _mainDispatcher;
|
||||
|
||||
private static readonly object _lock = new object();
|
||||
|
||||
public static LinuxDispatcher? Main => _mainDispatcher;
|
||||
|
||||
public static bool IsMainThread => Environment.CurrentManagedThreadId == _mainThreadId;
|
||||
|
||||
public bool IsDispatchRequired => !IsMainThread;
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_mainThreadId = Environment.CurrentManagedThreadId;
|
||||
_mainDispatcher = new LinuxDispatcher();
|
||||
Console.WriteLine($"[LinuxDispatcher] Initialized on thread {_mainThreadId}");
|
||||
}
|
||||
}
|
||||
|
||||
public bool Dispatch(Action action)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(action, "action");
|
||||
if (!IsDispatchRequired)
|
||||
{
|
||||
action();
|
||||
return true;
|
||||
}
|
||||
GLibNative.IdleAdd(delegate
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("[LinuxDispatcher] Error in dispatched action: " + ex.Message);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool DispatchDelayed(TimeSpan delay, Action action)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(action, "action");
|
||||
GLibNative.TimeoutAdd((uint)Math.Max(0.0, delay.TotalMilliseconds), delegate
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("[LinuxDispatcher] Error in delayed action: " + ex.Message);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
public IDispatcherTimer CreateTimer()
|
||||
{
|
||||
return new LinuxDispatcherTimer(this);
|
||||
}
|
||||
}
|
||||
15
Dispatching/LinuxDispatcherProvider.cs
Normal file
15
Dispatching/LinuxDispatcherProvider.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Microsoft.Maui.Dispatching;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Dispatching;
|
||||
|
||||
public class LinuxDispatcherProvider : IDispatcherProvider
|
||||
{
|
||||
private static LinuxDispatcherProvider? _instance;
|
||||
|
||||
public static LinuxDispatcherProvider Instance => _instance ?? (_instance = new LinuxDispatcherProvider());
|
||||
|
||||
public IDispatcher? GetForCurrentThread()
|
||||
{
|
||||
return LinuxDispatcher.Main;
|
||||
}
|
||||
}
|
||||
109
Dispatching/LinuxDispatcherTimer.cs
Normal file
109
Dispatching/LinuxDispatcherTimer.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using Microsoft.Maui.Dispatching;
|
||||
using Microsoft.Maui.Platform.Linux.Native;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Dispatching;
|
||||
|
||||
public class LinuxDispatcherTimer : IDispatcherTimer
|
||||
{
|
||||
private readonly LinuxDispatcher _dispatcher;
|
||||
|
||||
private uint _sourceId;
|
||||
|
||||
private TimeSpan _interval = TimeSpan.FromMilliseconds(100);
|
||||
|
||||
private bool _isRepeating = true;
|
||||
|
||||
private bool _isRunning;
|
||||
|
||||
public TimeSpan Interval
|
||||
{
|
||||
get
|
||||
{
|
||||
return _interval;
|
||||
}
|
||||
set
|
||||
{
|
||||
_interval = value;
|
||||
if (_isRunning)
|
||||
{
|
||||
Stop();
|
||||
Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRepeating
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isRepeating;
|
||||
}
|
||||
set
|
||||
{
|
||||
_isRepeating = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRunning => _isRunning;
|
||||
|
||||
public event EventHandler? Tick;
|
||||
|
||||
public LinuxDispatcherTimer(LinuxDispatcher dispatcher)
|
||||
{
|
||||
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
_isRunning = true;
|
||||
ScheduleNext();
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (_isRunning)
|
||||
{
|
||||
_isRunning = false;
|
||||
if (_sourceId != 0)
|
||||
{
|
||||
GLibNative.SourceRemove(_sourceId);
|
||||
_sourceId = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ScheduleNext()
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
uint intervalMs = (uint)Math.Max(1.0, _interval.TotalMilliseconds);
|
||||
_sourceId = GLibNative.TimeoutAdd(intervalMs, delegate
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
Tick?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("[LinuxDispatcherTimer] Error in Tick handler: " + ex.Message);
|
||||
}
|
||||
if (_isRepeating && _isRunning)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
_isRunning = false;
|
||||
_sourceId = 0;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
11
DisplayServerType.cs
Normal file
11
DisplayServerType.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux;
|
||||
|
||||
public enum DisplayServerType
|
||||
{
|
||||
Auto,
|
||||
X11,
|
||||
Wayland
|
||||
}
|
||||
53
Easing.cs
Normal file
53
Easing.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux;
|
||||
|
||||
public class Easing
|
||||
{
|
||||
private readonly Func<double, double> _easingFunc;
|
||||
|
||||
public static readonly Easing Linear = new(v => v);
|
||||
|
||||
public static readonly Easing SinIn = new(v => 1.0 - Math.Cos(v * Math.PI / 2.0));
|
||||
|
||||
public static readonly Easing SinOut = new(v => Math.Sin(v * Math.PI / 2.0));
|
||||
|
||||
public static readonly Easing SinInOut = new(v => -(Math.Cos(Math.PI * v) - 1.0) / 2.0);
|
||||
|
||||
public static readonly Easing CubicIn = new(v => v * v * v);
|
||||
|
||||
public static readonly Easing CubicOut = new(v => 1.0 - Math.Pow(1.0 - v, 3.0));
|
||||
|
||||
public static readonly Easing CubicInOut = new(v =>
|
||||
v < 0.5 ? 4.0 * v * v * v : 1.0 - Math.Pow(-2.0 * v + 2.0, 3.0) / 2.0);
|
||||
|
||||
// BounceOut must be declared before BounceIn since BounceIn references it
|
||||
public static readonly Easing BounceOut = new(v =>
|
||||
{
|
||||
const double n1 = 7.5625;
|
||||
const double d1 = 2.75;
|
||||
|
||||
if (v < 1 / d1)
|
||||
return n1 * v * v;
|
||||
if (v < 2 / d1)
|
||||
return n1 * (v -= 1.5 / d1) * v + 0.75;
|
||||
if (v < 2.5 / d1)
|
||||
return n1 * (v -= 2.25 / d1) * v + 0.9375;
|
||||
return n1 * (v -= 2.625 / d1) * v + 0.984375;
|
||||
});
|
||||
|
||||
public static readonly Easing BounceIn = new(v => 1.0 - BounceOut.Ease(1.0 - v));
|
||||
|
||||
public static readonly Easing SpringIn = new(v => v * v * (2.70158 * v - 1.70158));
|
||||
|
||||
public static readonly Easing SpringOut = new(v =>
|
||||
(v - 1.0) * (v - 1.0) * (2.70158 * (v - 1.0) + 1.70158) + 1.0);
|
||||
|
||||
public Easing(Func<double, double> easingFunc)
|
||||
{
|
||||
_easingFunc = easingFunc;
|
||||
}
|
||||
|
||||
public double Ease(double v) => _easingFunc(v);
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
|
||||
namespace Microsoft.Maui.Platform;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for ActivityIndicator control.
|
||||
/// </summary>
|
||||
public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator, SkiaActivityIndicator>
|
||||
{
|
||||
public static IPropertyMapper<IActivityIndicator, ActivityIndicatorHandler> Mapper = new PropertyMapper<IActivityIndicator, ActivityIndicatorHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(IActivityIndicator.IsRunning)] = MapIsRunning,
|
||||
[nameof(IActivityIndicator.Color)] = MapColor,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
public static CommandMapper<IActivityIndicator, ActivityIndicatorHandler> CommandMapper = new(ViewHandler.ViewCommandMapper);
|
||||
|
||||
public ActivityIndicatorHandler() : base(Mapper, CommandMapper) { }
|
||||
|
||||
protected override SkiaActivityIndicator CreatePlatformView() => new SkiaActivityIndicator();
|
||||
|
||||
public static void MapIsRunning(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
|
||||
{
|
||||
handler.PlatformView.IsRunning = activityIndicator.IsRunning;
|
||||
}
|
||||
|
||||
public static void MapColor(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
|
||||
{
|
||||
if (activityIndicator.Color != null)
|
||||
handler.PlatformView.Color = activityIndicator.Color.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
|
||||
{
|
||||
handler.PlatformView.IsEnabled = activityIndicator.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackground(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
|
||||
{
|
||||
if (activityIndicator.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
|
||||
{
|
||||
if (activityIndicator is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using SkiaSharp;
|
||||
using Microsoft.Maui.Platform;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
@@ -18,6 +18,7 @@ public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator,
|
||||
[nameof(IActivityIndicator.IsRunning)] = MapIsRunning,
|
||||
[nameof(IActivityIndicator.Color)] = MapColor,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
};
|
||||
|
||||
public static CommandMapper<IActivityIndicator, ActivityIndicatorHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -38,6 +39,19 @@ public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator,
|
||||
return new SkiaActivityIndicator();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaActivityIndicator platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
|
||||
// Sync properties
|
||||
if (VirtualView != null)
|
||||
{
|
||||
MapIsRunning(this, VirtualView);
|
||||
MapColor(this, VirtualView);
|
||||
MapIsEnabled(this, VirtualView);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIsRunning(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
@@ -49,7 +63,7 @@ public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator,
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (activityIndicator.Color is not null)
|
||||
handler.PlatformView.Color = activityIndicator.Color.ToSKColor();
|
||||
handler.PlatformView.Color = activityIndicator.Color;
|
||||
}
|
||||
|
||||
public static void MapBackground(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
|
||||
@@ -58,7 +72,14 @@ public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator,
|
||||
|
||||
if (activityIndicator.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.IsEnabled = activityIndicator.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Platform;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
@@ -20,10 +21,17 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
||||
[nameof(IBorderView.Content)] = MapContent,
|
||||
[nameof(IBorderStroke.Stroke)] = MapStroke,
|
||||
[nameof(IBorderStroke.StrokeThickness)] = MapStrokeThickness,
|
||||
["StrokeDashArray"] = MapStrokeDashArray,
|
||||
["StrokeDashOffset"] = MapStrokeDashOffset,
|
||||
[nameof(IBorderStroke.StrokeLineCap)] = MapStrokeLineCap,
|
||||
[nameof(IBorderStroke.StrokeLineJoin)] = MapStrokeLineJoin,
|
||||
[nameof(IBorderStroke.StrokeMiterLimit)] = MapStrokeMiterLimit,
|
||||
["StrokeShape"] = MapStrokeShape, // StrokeShape is on Border, not IBorderStroke
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
[nameof(IPadding.Padding)] = MapPadding,
|
||||
["WidthRequest"] = MapWidthRequest,
|
||||
["HeightRequest"] = MapHeightRequest,
|
||||
};
|
||||
|
||||
public static CommandMapper<IBorderView, BorderHandler> CommandMapper =
|
||||
@@ -48,13 +56,49 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
||||
protected override void ConnectHandler(SkiaBorder platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
if (VirtualView is View view)
|
||||
{
|
||||
platformView.MauiView = view;
|
||||
}
|
||||
platformView.Tapped += OnPlatformViewTapped;
|
||||
|
||||
// Explicitly map properties since they may be set before handler creation
|
||||
if (VirtualView is VisualElement ve)
|
||||
{
|
||||
if (ve.BackgroundColor != null)
|
||||
{
|
||||
platformView.BackgroundColor = ve.BackgroundColor;
|
||||
}
|
||||
else if (ve.Background is SolidColorBrush brush && brush.Color != null)
|
||||
{
|
||||
platformView.BackgroundColor = brush.Color;
|
||||
}
|
||||
if (ve.WidthRequest >= 0)
|
||||
{
|
||||
platformView.WidthRequest = ve.WidthRequest;
|
||||
}
|
||||
if (ve.HeightRequest >= 0)
|
||||
{
|
||||
platformView.HeightRequest = ve.HeightRequest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaBorder platformView)
|
||||
{
|
||||
platformView.Tapped -= OnPlatformViewTapped;
|
||||
platformView.MauiView = null;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnPlatformViewTapped(object? sender, EventArgs e)
|
||||
{
|
||||
if (VirtualView is View view)
|
||||
{
|
||||
GestureManager.ProcessTap(view, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapContent(BorderHandler handler, IBorderView border)
|
||||
{
|
||||
if (handler.PlatformView is null || handler.MauiContext is null) return;
|
||||
@@ -68,7 +112,7 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
||||
if (content.Handler == null)
|
||||
{
|
||||
Console.WriteLine($"[BorderHandler] Creating handler for content: {content.GetType().Name}");
|
||||
content.Handler = content.ToHandler(handler.MauiContext);
|
||||
content.Handler = content.ToViewHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (content.Handler?.PlatformView is SkiaView skiaContent)
|
||||
@@ -85,14 +129,14 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
||||
|
||||
if (border.Stroke is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.Stroke = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.Stroke = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapStrokeThickness(BorderHandler handler, IBorderView border)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.StrokeThickness = (float)border.StrokeThickness;
|
||||
handler.PlatformView.StrokeThickness = border.StrokeThickness;
|
||||
}
|
||||
|
||||
public static void MapBackground(BorderHandler handler, IBorderView border)
|
||||
@@ -101,7 +145,7 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
||||
|
||||
if (border.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,22 +153,27 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (border is VisualElement ve && ve.BackgroundColor != null)
|
||||
if (border is VisualElement ve)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
var bgColor = ve.BackgroundColor;
|
||||
Console.WriteLine($"[BorderHandler] MapBackgroundColor: {bgColor}");
|
||||
if (bgColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = bgColor;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapPadding(BorderHandler handler, IBorderView border)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
var padding = border.Padding;
|
||||
handler.PlatformView.PaddingLeft = (float)padding.Left;
|
||||
handler.PlatformView.PaddingTop = (float)padding.Top;
|
||||
handler.PlatformView.PaddingRight = (float)padding.Right;
|
||||
handler.PlatformView.PaddingBottom = (float)padding.Bottom;
|
||||
handler.PlatformView.PaddingLeft = padding.Left;
|
||||
handler.PlatformView.PaddingTop = padding.Top;
|
||||
handler.PlatformView.PaddingRight = padding.Right;
|
||||
handler.PlatformView.PaddingBottom = padding.Bottom;
|
||||
}
|
||||
|
||||
public static void MapStrokeShape(BorderHandler handler, IBorderView border)
|
||||
@@ -135,24 +184,109 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
||||
if (border is not Border borderControl) return;
|
||||
|
||||
var shape = borderControl.StrokeShape;
|
||||
|
||||
// Pass the shape directly to the platform view for full shape support
|
||||
handler.PlatformView.StrokeShape = shape;
|
||||
|
||||
// Also set CornerRadius for backward compatibility when StrokeShape is RoundRectangle
|
||||
if (shape is Microsoft.Maui.Controls.Shapes.RoundRectangle roundRect)
|
||||
{
|
||||
// RoundRectangle can have different corner radii, but we use a uniform one
|
||||
// Take the top-left corner as the uniform radius
|
||||
var cornerRadius = roundRect.CornerRadius;
|
||||
handler.PlatformView.CornerRadius = (float)cornerRadius.TopLeft;
|
||||
handler.PlatformView.CornerRadius = cornerRadius.TopLeft;
|
||||
}
|
||||
else if (shape is Microsoft.Maui.Controls.Shapes.Rectangle)
|
||||
{
|
||||
handler.PlatformView.CornerRadius = 0;
|
||||
handler.PlatformView.CornerRadius = 0.0;
|
||||
}
|
||||
else if (shape is Microsoft.Maui.Controls.Shapes.Ellipse)
|
||||
{
|
||||
// For ellipse, use half the min dimension as corner radius
|
||||
// This will be applied during rendering when bounds are known
|
||||
handler.PlatformView.CornerRadius = float.MaxValue; // Marker for "fully rounded"
|
||||
handler.PlatformView.CornerRadius = double.MaxValue;
|
||||
}
|
||||
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapStrokeDashArray(BorderHandler handler, IBorderView border)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
// StrokeDashArray is on Border class
|
||||
if (border is Border borderControl && borderControl.StrokeDashArray != null)
|
||||
{
|
||||
var dashArray = new DoubleCollection();
|
||||
foreach (var value in borderControl.StrokeDashArray)
|
||||
{
|
||||
dashArray.Add(value);
|
||||
}
|
||||
handler.PlatformView.StrokeDashArray = dashArray;
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapStrokeDashOffset(BorderHandler handler, IBorderView border)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
// StrokeDashOffset is on Border class
|
||||
if (border is Border borderControl)
|
||||
{
|
||||
handler.PlatformView.StrokeDashOffset = borderControl.StrokeDashOffset;
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapStrokeLineCap(BorderHandler handler, IBorderView border)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (border is IBorderStroke borderStroke)
|
||||
{
|
||||
handler.PlatformView.StrokeLineCap = borderStroke.StrokeLineCap;
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapStrokeLineJoin(BorderHandler handler, IBorderView border)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (border is IBorderStroke borderStroke)
|
||||
{
|
||||
handler.PlatformView.StrokeLineJoin = borderStroke.StrokeLineJoin;
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapStrokeMiterLimit(BorderHandler handler, IBorderView border)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (border is IBorderStroke borderStroke)
|
||||
{
|
||||
handler.PlatformView.StrokeMiterLimit = borderStroke.StrokeMiterLimit;
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapWidthRequest(BorderHandler handler, IBorderView border)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (border is VisualElement ve && ve.WidthRequest >= 0)
|
||||
{
|
||||
handler.PlatformView.WidthRequest = ve.WidthRequest;
|
||||
handler.PlatformView.InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapHeightRequest(BorderHandler handler, IBorderView border)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (border is VisualElement ve && ve.HeightRequest >= 0)
|
||||
{
|
||||
handler.PlatformView.HeightRequest = ve.HeightRequest;
|
||||
handler.PlatformView.InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,28 +30,38 @@ public partial class BoxViewHandler : ViewHandler<BoxView, SkiaBoxView>
|
||||
return new SkiaBoxView();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaBoxView platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
|
||||
// Map size requests from MAUI BoxView
|
||||
if (VirtualView is BoxView boxView)
|
||||
{
|
||||
if (boxView.WidthRequest >= 0)
|
||||
platformView.WidthRequest = boxView.WidthRequest;
|
||||
if (boxView.HeightRequest >= 0)
|
||||
platformView.HeightRequest = boxView.HeightRequest;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapColor(BoxViewHandler handler, BoxView boxView)
|
||||
{
|
||||
if (boxView.Color != null)
|
||||
{
|
||||
handler.PlatformView.Color = new SKColor(
|
||||
(byte)(boxView.Color.Red * 255),
|
||||
(byte)(boxView.Color.Green * 255),
|
||||
(byte)(boxView.Color.Blue * 255),
|
||||
(byte)(boxView.Color.Alpha * 255));
|
||||
handler.PlatformView.Color = boxView.Color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapCornerRadius(BoxViewHandler handler, BoxView boxView)
|
||||
{
|
||||
handler.PlatformView.CornerRadius = (float)boxView.CornerRadius.TopLeft;
|
||||
handler.PlatformView.CornerRadius = boxView.CornerRadius;
|
||||
}
|
||||
|
||||
public static void MapBackground(BoxViewHandler handler, BoxView boxView)
|
||||
{
|
||||
if (boxView.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
@@ -60,7 +70,7 @@ public partial class BoxViewHandler : ViewHandler<BoxView, SkiaBoxView>
|
||||
{
|
||||
if (boxView.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = boxView.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = boxView.BackgroundColor;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for Button control.
|
||||
/// </summary>
|
||||
public partial class ButtonHandler : ViewHandler<IButton, SkiaButton>
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps the property mapper for the handler.
|
||||
/// </summary>
|
||||
public static IPropertyMapper<IButton, ButtonHandler> Mapper = new PropertyMapper<IButton, ButtonHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(IButton.Text)] = MapText,
|
||||
[nameof(IButton.TextColor)] = MapTextColor,
|
||||
[nameof(IButton.Background)] = MapBackground,
|
||||
[nameof(IButton.Font)] = MapFont,
|
||||
[nameof(IButton.Padding)] = MapPadding,
|
||||
[nameof(IButton.CornerRadius)] = MapCornerRadius,
|
||||
[nameof(IButton.BorderColor)] = MapBorderColor,
|
||||
[nameof(IButton.BorderWidth)] = MapBorderWidth,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Maps the command mapper for the handler.
|
||||
/// </summary>
|
||||
public static CommandMapper<IButton, ButtonHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
{
|
||||
};
|
||||
|
||||
public ButtonHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public ButtonHandler(IPropertyMapper? mapper)
|
||||
: base(mapper ?? Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public ButtonHandler(IPropertyMapper? mapper, CommandMapper? commandMapper)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaButton CreatePlatformView()
|
||||
{
|
||||
var button = new SkiaButton();
|
||||
return button;
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaButton platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.Clicked += OnClicked;
|
||||
platformView.Pressed += OnPressed;
|
||||
platformView.Released += OnReleased;
|
||||
|
||||
// Manually map all properties on connect since MAUI may not trigger updates
|
||||
// for properties that were set before handler connection
|
||||
if (VirtualView != null)
|
||||
{
|
||||
MapText(this, VirtualView);
|
||||
MapTextColor(this, VirtualView);
|
||||
MapBackground(this, VirtualView);
|
||||
MapFont(this, VirtualView);
|
||||
MapPadding(this, VirtualView);
|
||||
MapCornerRadius(this, VirtualView);
|
||||
MapBorderColor(this, VirtualView);
|
||||
MapBorderWidth(this, VirtualView);
|
||||
MapIsEnabled(this, VirtualView);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaButton platformView)
|
||||
{
|
||||
platformView.Clicked -= OnClicked;
|
||||
platformView.Pressed -= OnPressed;
|
||||
platformView.Released -= OnReleased;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnClicked(object? sender, EventArgs e)
|
||||
{
|
||||
VirtualView?.Clicked();
|
||||
}
|
||||
|
||||
private void OnPressed(object? sender, EventArgs e)
|
||||
{
|
||||
VirtualView?.Pressed();
|
||||
}
|
||||
|
||||
private void OnReleased(object? sender, EventArgs e)
|
||||
{
|
||||
VirtualView?.Released();
|
||||
}
|
||||
|
||||
public static void MapText(ButtonHandler handler, IButton button)
|
||||
{
|
||||
handler.PlatformView.Text = button.Text ?? "";
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapTextColor(ButtonHandler handler, IButton button)
|
||||
{
|
||||
if (button.TextColor != null)
|
||||
{
|
||||
handler.PlatformView.TextColor = button.TextColor.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackground(ButtonHandler handler, IButton button)
|
||||
{
|
||||
var background = button.Background;
|
||||
if (background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
// Use ButtonBackgroundColor which is used for rendering, not base BackgroundColor
|
||||
handler.PlatformView.ButtonBackgroundColor = solidBrush.Color.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapFont(ButtonHandler handler, IButton button)
|
||||
{
|
||||
var font = button.Font;
|
||||
if (font.Family != null)
|
||||
{
|
||||
handler.PlatformView.FontFamily = font.Family;
|
||||
}
|
||||
handler.PlatformView.FontSize = (float)font.Size;
|
||||
handler.PlatformView.IsBold = font.Weight == FontWeight.Bold;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapPadding(ButtonHandler handler, IButton button)
|
||||
{
|
||||
var padding = button.Padding;
|
||||
handler.PlatformView.Padding = new SKRect(
|
||||
(float)padding.Left,
|
||||
(float)padding.Top,
|
||||
(float)padding.Right,
|
||||
(float)padding.Bottom);
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapCornerRadius(ButtonHandler handler, IButton button)
|
||||
{
|
||||
handler.PlatformView.CornerRadius = button.CornerRadius;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBorderColor(ButtonHandler handler, IButton button)
|
||||
{
|
||||
if (button.StrokeColor != null)
|
||||
{
|
||||
handler.PlatformView.BorderColor = button.StrokeColor.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBorderWidth(ButtonHandler handler, IButton button)
|
||||
{
|
||||
handler.PlatformView.BorderWidth = (float)button.StrokeThickness;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(ButtonHandler handler, IButton button)
|
||||
{
|
||||
Console.WriteLine($"[ButtonHandler] MapIsEnabled called - Text='{handler.PlatformView.Text}', IsEnabled={button.IsEnabled}");
|
||||
handler.PlatformView.IsEnabled = button.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using SkiaSharp;
|
||||
@@ -59,6 +60,20 @@ public partial class ButtonHandler : ViewHandler<IButton, SkiaButton>
|
||||
MapBackground(this, VirtualView);
|
||||
MapPadding(this, VirtualView);
|
||||
MapIsEnabled(this, VirtualView);
|
||||
|
||||
// Map size requests from MAUI Button
|
||||
if (VirtualView is Microsoft.Maui.Controls.Button mauiButton)
|
||||
{
|
||||
Console.WriteLine($"[ButtonHandler] MapSize Text='{platformView.Text}' WReq={mauiButton.WidthRequest} HReq={mauiButton.HeightRequest}");
|
||||
if (mauiButton.WidthRequest >= 0)
|
||||
platformView.WidthRequest = mauiButton.WidthRequest;
|
||||
if (mauiButton.HeightRequest >= 0)
|
||||
platformView.HeightRequest = mauiButton.HeightRequest;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"[ButtonHandler] VirtualView is NOT Microsoft.Maui.Controls.Button, type={VirtualView?.GetType().Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,13 +95,13 @@ public partial class ButtonHandler : ViewHandler<IButton, SkiaButton>
|
||||
|
||||
var strokeColor = button.StrokeColor;
|
||||
if (strokeColor is not null)
|
||||
handler.PlatformView.BorderColor = strokeColor.ToSKColor();
|
||||
handler.PlatformView.BorderColor = strokeColor;
|
||||
}
|
||||
|
||||
public static void MapStrokeThickness(ButtonHandler handler, IButton button)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.BorderWidth = (float)button.StrokeThickness;
|
||||
handler.PlatformView.BorderWidth = button.StrokeThickness;
|
||||
}
|
||||
|
||||
public static void MapCornerRadius(ButtonHandler handler, IButton button)
|
||||
@@ -101,8 +116,8 @@ public partial class ButtonHandler : ViewHandler<IButton, SkiaButton>
|
||||
|
||||
if (button.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
// Set ButtonBackgroundColor (used for rendering) not base BackgroundColor
|
||||
handler.PlatformView.ButtonBackgroundColor = solidPaint.Color.ToSKColor();
|
||||
// Set BackgroundColor (MAUI Color type)
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,17 +126,16 @@ public partial class ButtonHandler : ViewHandler<IButton, SkiaButton>
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
var padding = button.Padding;
|
||||
handler.PlatformView.Padding = new SKRect(
|
||||
(float)padding.Left,
|
||||
(float)padding.Top,
|
||||
(float)padding.Right,
|
||||
(float)padding.Bottom);
|
||||
handler.PlatformView.Padding = new Thickness(
|
||||
padding.Left,
|
||||
padding.Top,
|
||||
padding.Right,
|
||||
padding.Bottom);
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(ButtonHandler handler, IButton button)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
Console.WriteLine($"[ButtonHandler] MapIsEnabled - Text='{handler.PlatformView.Text}', IsEnabled={button.IsEnabled}");
|
||||
handler.PlatformView.IsEnabled = button.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
@@ -148,6 +162,7 @@ public partial class TextButtonHandler : ButtonHandler
|
||||
|
||||
protected override void ConnectHandler(SkiaButton platformView)
|
||||
{
|
||||
Console.WriteLine($"[TextButtonHandler] ConnectHandler START");
|
||||
base.ConnectHandler(platformView);
|
||||
|
||||
// Manually map text properties on connect since MAUI may not trigger updates
|
||||
@@ -159,6 +174,17 @@ public partial class TextButtonHandler : ButtonHandler
|
||||
MapFont(this, textButton);
|
||||
MapCharacterSpacing(this, textButton);
|
||||
}
|
||||
|
||||
// Map size requests from MAUI Button
|
||||
if (VirtualView is Microsoft.Maui.Controls.Button mauiButton)
|
||||
{
|
||||
Console.WriteLine($"[TextButtonHandler] MapSize Text='{platformView.Text}' WReq={mauiButton.WidthRequest} HReq={mauiButton.HeightRequest}");
|
||||
if (mauiButton.WidthRequest >= 0)
|
||||
platformView.WidthRequest = mauiButton.WidthRequest;
|
||||
if (mauiButton.HeightRequest >= 0)
|
||||
platformView.HeightRequest = mauiButton.HeightRequest;
|
||||
}
|
||||
Console.WriteLine($"[TextButtonHandler] ConnectHandler DONE");
|
||||
}
|
||||
|
||||
public static void MapText(TextButtonHandler handler, ITextButton button)
|
||||
@@ -172,7 +198,7 @@ public partial class TextButtonHandler : ButtonHandler
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (button.TextColor is not null)
|
||||
handler.PlatformView.TextColor = button.TextColor.ToSKColor();
|
||||
handler.PlatformView.TextColor = button.TextColor;
|
||||
}
|
||||
|
||||
public static void MapFont(TextButtonHandler handler, ITextButton button)
|
||||
@@ -181,18 +207,23 @@ public partial class TextButtonHandler : ButtonHandler
|
||||
|
||||
var font = button.Font;
|
||||
if (font.Size > 0)
|
||||
handler.PlatformView.FontSize = (float)font.Size;
|
||||
handler.PlatformView.FontSize = font.Size;
|
||||
|
||||
if (!string.IsNullOrEmpty(font.Family))
|
||||
handler.PlatformView.FontFamily = font.Family;
|
||||
|
||||
handler.PlatformView.IsBold = font.Weight >= FontWeight.Bold;
|
||||
handler.PlatformView.IsItalic = font.Slant == FontSlant.Italic || font.Slant == FontSlant.Oblique;
|
||||
// Convert Font weight/slant to FontAttributes
|
||||
FontAttributes attrs = FontAttributes.None;
|
||||
if (font.Weight >= FontWeight.Bold)
|
||||
attrs |= FontAttributes.Bold;
|
||||
if (font.Slant == FontSlant.Italic || font.Slant == FontSlant.Oblique)
|
||||
attrs |= FontAttributes.Italic;
|
||||
handler.PlatformView.FontAttributes = attrs;
|
||||
}
|
||||
|
||||
public static void MapCharacterSpacing(TextButtonHandler handler, ITextButton button)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.CharacterSpacing = (float)button.CharacterSpacing;
|
||||
handler.PlatformView.CharacterSpacing = button.CharacterSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
255
Handlers/CarouselViewHandler.cs
Normal file
255
Handlers/CarouselViewHandler.cs
Normal file
@@ -0,0 +1,255 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Platform;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for CarouselView on Linux using Skia rendering.
|
||||
/// Maps CarouselView to SkiaCarouselView platform view.
|
||||
/// </summary>
|
||||
public partial class CarouselViewHandler : ViewHandler<CarouselView, SkiaCarouselView>
|
||||
{
|
||||
private bool _isUpdatingPosition;
|
||||
|
||||
public static IPropertyMapper<CarouselView, CarouselViewHandler> Mapper =
|
||||
new PropertyMapper<CarouselView, CarouselViewHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
// ItemsView properties
|
||||
[nameof(ItemsView.ItemsSource)] = MapItemsSource,
|
||||
[nameof(ItemsView.ItemTemplate)] = MapItemTemplate,
|
||||
[nameof(ItemsView.EmptyView)] = MapEmptyView,
|
||||
|
||||
// CarouselView specific properties
|
||||
[nameof(CarouselView.Position)] = MapPosition,
|
||||
[nameof(CarouselView.CurrentItem)] = MapCurrentItem,
|
||||
[nameof(CarouselView.IsBounceEnabled)] = MapIsBounceEnabled,
|
||||
[nameof(CarouselView.IsSwipeEnabled)] = MapIsSwipeEnabled,
|
||||
[nameof(CarouselView.Loop)] = MapLoop,
|
||||
[nameof(CarouselView.PeekAreaInsets)] = MapPeekAreaInsets,
|
||||
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
};
|
||||
|
||||
public static CommandMapper<CarouselView, CarouselViewHandler> CommandMapper =
|
||||
new(ViewHandler.ViewCommandMapper)
|
||||
{
|
||||
["ScrollTo"] = MapScrollTo,
|
||||
};
|
||||
|
||||
public CarouselViewHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public CarouselViewHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaCarouselView CreatePlatformView()
|
||||
{
|
||||
return new SkiaCarouselView();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaCarouselView platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.PositionChanged += OnPositionChanged;
|
||||
platformView.Scrolled += OnScrolled;
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaCarouselView platformView)
|
||||
{
|
||||
platformView.PositionChanged -= OnPositionChanged;
|
||||
platformView.Scrolled -= OnScrolled;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnPositionChanged(object? sender, PositionChangedEventArgs e)
|
||||
{
|
||||
if (VirtualView is null || _isUpdatingPosition) return;
|
||||
|
||||
try
|
||||
{
|
||||
_isUpdatingPosition = true;
|
||||
|
||||
if (VirtualView.Position != e.CurrentPosition)
|
||||
{
|
||||
VirtualView.Position = e.CurrentPosition;
|
||||
}
|
||||
|
||||
// Update CurrentItem
|
||||
if (VirtualView.ItemsSource is System.Collections.IList list &&
|
||||
e.CurrentPosition >= 0 && e.CurrentPosition < list.Count)
|
||||
{
|
||||
VirtualView.CurrentItem = list[e.CurrentPosition];
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isUpdatingPosition = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnScrolled(object? sender, EventArgs e)
|
||||
{
|
||||
// CarouselView doesn't have a direct Scrolled event in MAUI
|
||||
// but we can use this for internal state tracking
|
||||
}
|
||||
|
||||
public static void MapItemsSource(CarouselViewHandler handler, CarouselView carouselView)
|
||||
{
|
||||
if (handler.PlatformView is null || handler.MauiContext is null) return;
|
||||
|
||||
handler.PlatformView.ClearItems();
|
||||
|
||||
var itemsSource = carouselView.ItemsSource;
|
||||
if (itemsSource == null) return;
|
||||
|
||||
var template = carouselView.ItemTemplate;
|
||||
|
||||
foreach (var item in itemsSource)
|
||||
{
|
||||
SkiaView? skiaView = null;
|
||||
|
||||
if (template != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var content = template.CreateContent();
|
||||
if (content is View view)
|
||||
{
|
||||
view.BindingContext = item;
|
||||
|
||||
if (view.Handler == null)
|
||||
{
|
||||
view.Handler = view.ToViewHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (view.Handler?.PlatformView is SkiaView sv)
|
||||
{
|
||||
skiaView = sv;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore template errors
|
||||
}
|
||||
}
|
||||
|
||||
if (skiaView == null)
|
||||
{
|
||||
// Create a simple label for the item
|
||||
skiaView = new SkiaLabel { Text = item?.ToString() ?? "" };
|
||||
}
|
||||
|
||||
handler.PlatformView.AddItem(skiaView);
|
||||
}
|
||||
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapItemTemplate(CarouselViewHandler handler, CarouselView carouselView)
|
||||
{
|
||||
// Re-map items when template changes
|
||||
MapItemsSource(handler, carouselView);
|
||||
}
|
||||
|
||||
public static void MapEmptyView(CarouselViewHandler handler, CarouselView carouselView)
|
||||
{
|
||||
// CarouselView doesn't typically show empty view - handled by ItemsSource
|
||||
}
|
||||
|
||||
public static void MapPosition(CarouselViewHandler handler, CarouselView carouselView)
|
||||
{
|
||||
if (handler.PlatformView is null || handler._isUpdatingPosition) return;
|
||||
|
||||
try
|
||||
{
|
||||
handler._isUpdatingPosition = true;
|
||||
if (handler.PlatformView.Position != carouselView.Position)
|
||||
{
|
||||
handler.PlatformView.Position = carouselView.Position;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
handler._isUpdatingPosition = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapCurrentItem(CarouselViewHandler handler, CarouselView carouselView)
|
||||
{
|
||||
if (handler.PlatformView is null || handler._isUpdatingPosition) return;
|
||||
|
||||
// Find position of current item
|
||||
if (carouselView.ItemsSource is System.Collections.IList list && carouselView.CurrentItem != null)
|
||||
{
|
||||
int index = list.IndexOf(carouselView.CurrentItem);
|
||||
if (index >= 0 && index != handler.PlatformView.Position)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler._isUpdatingPosition = true;
|
||||
handler.PlatformView.Position = index;
|
||||
}
|
||||
finally
|
||||
{
|
||||
handler._isUpdatingPosition = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIsBounceEnabled(CarouselViewHandler handler, CarouselView carouselView)
|
||||
{
|
||||
// SkiaCarouselView handles bounce internally
|
||||
// Could add IsBounceEnabled property if needed
|
||||
}
|
||||
|
||||
public static void MapIsSwipeEnabled(CarouselViewHandler handler, CarouselView carouselView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.IsSwipeEnabled = carouselView.IsSwipeEnabled;
|
||||
}
|
||||
|
||||
public static void MapLoop(CarouselViewHandler handler, CarouselView carouselView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.Loop = carouselView.Loop;
|
||||
}
|
||||
|
||||
public static void MapPeekAreaInsets(CarouselViewHandler handler, CarouselView carouselView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
// PeekAreaInsets is a Thickness in MAUI, use Left for horizontal peek
|
||||
handler.PlatformView.PeekAreaInsets = (float)carouselView.PeekAreaInsets.Left;
|
||||
}
|
||||
|
||||
public static void MapBackground(CarouselViewHandler handler, CarouselView carouselView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (carouselView.Background is SolidColorBrush solidBrush)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapScrollTo(CarouselViewHandler handler, CarouselView carouselView, object? args)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (args is ScrollToRequestEventArgs scrollArgs)
|
||||
{
|
||||
handler.PlatformView.ScrollTo(scrollArgs.Index, scrollArgs.IsAnimated);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for CheckBox control.
|
||||
/// </summary>
|
||||
public partial class CheckBoxHandler : ViewHandler<ICheckBox, SkiaCheckBox>
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps the property mapper for the handler.
|
||||
/// </summary>
|
||||
public static IPropertyMapper<ICheckBox, CheckBoxHandler> Mapper = new PropertyMapper<ICheckBox, CheckBoxHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(ICheckBox.IsChecked)] = MapIsChecked,
|
||||
[nameof(ICheckBox.Foreground)] = MapForeground,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Maps the command mapper for the handler.
|
||||
/// </summary>
|
||||
public static CommandMapper<ICheckBox, CheckBoxHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
{
|
||||
};
|
||||
|
||||
public CheckBoxHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public CheckBoxHandler(IPropertyMapper? mapper)
|
||||
: base(mapper ?? Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public CheckBoxHandler(IPropertyMapper? mapper, CommandMapper? commandMapper)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaCheckBox CreatePlatformView()
|
||||
{
|
||||
return new SkiaCheckBox();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaCheckBox platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.CheckedChanged += OnCheckedChanged;
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaCheckBox platformView)
|
||||
{
|
||||
platformView.CheckedChanged -= OnCheckedChanged;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnCheckedChanged(object? sender, CheckedChangedEventArgs e)
|
||||
{
|
||||
if (VirtualView != null && VirtualView.IsChecked != e.IsChecked)
|
||||
{
|
||||
VirtualView.IsChecked = e.IsChecked;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIsChecked(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
{
|
||||
if (handler.PlatformView.IsChecked != checkBox.IsChecked)
|
||||
{
|
||||
handler.PlatformView.IsChecked = checkBox.IsChecked;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapForeground(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
{
|
||||
var foreground = checkBox.Foreground;
|
||||
if (foreground is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
handler.PlatformView.BoxColor = solidBrush.Color.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
{
|
||||
handler.PlatformView.IsEnabled = checkBox.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackground(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
{
|
||||
if (checkBox.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
{
|
||||
if (checkBox is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ public partial class CheckBoxHandler : ViewHandler<ICheckBox, SkiaCheckBox>
|
||||
[nameof(ICheckBox.IsChecked)] = MapIsChecked,
|
||||
[nameof(ICheckBox.Foreground)] = MapForeground,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
[nameof(IView.VerticalLayoutAlignment)] = MapVerticalLayoutAlignment,
|
||||
[nameof(IView.HorizontalLayoutAlignment)] = MapHorizontalLayoutAlignment,
|
||||
};
|
||||
@@ -72,7 +73,7 @@ public partial class CheckBoxHandler : ViewHandler<ICheckBox, SkiaCheckBox>
|
||||
|
||||
if (checkBox.Foreground is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.CheckColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.CheckColor = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,10 +83,16 @@ public partial class CheckBoxHandler : ViewHandler<ICheckBox, SkiaCheckBox>
|
||||
|
||||
if (checkBox.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.Color = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.IsEnabled = checkBox.IsEnabled;
|
||||
}
|
||||
|
||||
public static void MapVerticalLayoutAlignment(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Platform;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
@@ -123,7 +124,49 @@ public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCol
|
||||
|
||||
private void OnItemTapped(object? sender, ItemsViewItemTappedEventArgs e)
|
||||
{
|
||||
// Item tap is handled through selection
|
||||
if (VirtualView is null || _isUpdatingSelection) return;
|
||||
|
||||
try
|
||||
{
|
||||
_isUpdatingSelection = true;
|
||||
|
||||
Console.WriteLine($"[CollectionViewHandler] OnItemTapped index={e.Index}, item={e.Item}, SelectionMode={VirtualView.SelectionMode}");
|
||||
|
||||
// Try to get the item view and process gestures
|
||||
var skiaView = PlatformView?.GetItemView(e.Index);
|
||||
Console.WriteLine($"[CollectionViewHandler] GetItemView({e.Index}) returned: {skiaView?.GetType().Name ?? "null"}, MauiView={skiaView?.MauiView?.GetType().Name ?? "null"}");
|
||||
|
||||
if (skiaView?.MauiView != null)
|
||||
{
|
||||
Console.WriteLine($"[CollectionViewHandler] Found MauiView: {skiaView.MauiView.GetType().Name}, GestureRecognizers={skiaView.MauiView.GestureRecognizers?.Count ?? 0}");
|
||||
if (GestureManager.ProcessTap(skiaView.MauiView, 0, 0))
|
||||
{
|
||||
Console.WriteLine("[CollectionViewHandler] Gesture processed successfully");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle selection if gesture wasn't processed
|
||||
if (VirtualView.SelectionMode == SelectionMode.Single)
|
||||
{
|
||||
VirtualView.SelectedItem = e.Item;
|
||||
}
|
||||
else if (VirtualView.SelectionMode == SelectionMode.Multiple)
|
||||
{
|
||||
if (VirtualView.SelectedItems.Contains(e.Item))
|
||||
{
|
||||
VirtualView.SelectedItems.Remove(e.Item);
|
||||
}
|
||||
else
|
||||
{
|
||||
VirtualView.SelectedItems.Add(e.Item);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isUpdatingSelection = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapItemsSource(CollectionViewHandler handler, CollectionView collectionView)
|
||||
@@ -158,11 +201,14 @@ public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCol
|
||||
// Create handler for the view
|
||||
if (view.Handler == null && handler.MauiContext != null)
|
||||
{
|
||||
view.Handler = view.ToHandler(handler.MauiContext);
|
||||
view.Handler = view.ToViewHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (view.Handler?.PlatformView is SkiaView skiaView)
|
||||
{
|
||||
// Set MauiView so gestures can be processed
|
||||
skiaView.MauiView = view;
|
||||
Console.WriteLine($"[CollectionViewHandler.ItemViewCreator] Set MauiView={view.GetType().Name} on {skiaView.GetType().Name}, GestureRecognizers={view.GestureRecognizers?.Count ?? 0}");
|
||||
return skiaView;
|
||||
}
|
||||
}
|
||||
@@ -174,7 +220,7 @@ public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCol
|
||||
{
|
||||
if (cellView.Handler == null && handler.MauiContext != null)
|
||||
{
|
||||
cellView.Handler = cellView.ToHandler(handler.MauiContext);
|
||||
cellView.Handler = cellView.ToViewHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (cellView.Handler?.PlatformView is SkiaView skiaView)
|
||||
@@ -315,7 +361,7 @@ public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCol
|
||||
|
||||
if (collectionView.Background is SolidColorBrush solidBrush)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,7 +371,7 @@ public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCol
|
||||
|
||||
if (collectionView.BackgroundColor is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = collectionView.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = collectionView.BackgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ public partial class DatePickerHandler : ViewHandler<IDatePicker, SkiaDatePicker
|
||||
[nameof(IDatePicker.Format)] = MapFormat,
|
||||
[nameof(IDatePicker.TextColor)] = MapTextColor,
|
||||
[nameof(IDatePicker.CharacterSpacing)] = MapCharacterSpacing,
|
||||
[nameof(ITextStyle.Font)] = MapFont,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
};
|
||||
|
||||
@@ -49,6 +50,17 @@ public partial class DatePickerHandler : ViewHandler<IDatePicker, SkiaDatePicker
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.DateSelected += OnDateSelected;
|
||||
|
||||
// Apply dark theme colors if dark mode is active
|
||||
var current = Application.Current;
|
||||
if (current != null && (int)current.UserAppTheme == 2) // Dark theme
|
||||
{
|
||||
platformView.CalendarBackgroundColor = Color.FromRgb(30, 30, 30);
|
||||
platformView.TextColor = Color.FromRgb(224, 224, 224);
|
||||
platformView.BorderColor = Color.FromRgb(97, 97, 97);
|
||||
platformView.DisabledDayColor = Color.FromRgb(97, 97, 97);
|
||||
platformView.BackgroundColor = Color.FromRgb(45, 45, 45);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaDatePicker platformView)
|
||||
@@ -57,11 +69,11 @@ public partial class DatePickerHandler : ViewHandler<IDatePicker, SkiaDatePicker
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnDateSelected(object? sender, EventArgs e)
|
||||
private void OnDateSelected(object? sender, DateChangedEventArgs e)
|
||||
{
|
||||
if (VirtualView is null || PlatformView is null) return;
|
||||
|
||||
VirtualView.Date = PlatformView.Date;
|
||||
VirtualView.Date = e.NewDate;
|
||||
}
|
||||
|
||||
public static void MapDate(DatePickerHandler handler, IDatePicker datePicker)
|
||||
@@ -93,13 +105,33 @@ public partial class DatePickerHandler : ViewHandler<IDatePicker, SkiaDatePicker
|
||||
if (handler.PlatformView is null) return;
|
||||
if (datePicker.TextColor is not null)
|
||||
{
|
||||
handler.PlatformView.TextColor = datePicker.TextColor.ToSKColor();
|
||||
handler.PlatformView.TextColor = datePicker.TextColor;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapCharacterSpacing(DatePickerHandler handler, IDatePicker datePicker)
|
||||
{
|
||||
// Character spacing would require custom text rendering
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.CharacterSpacing = datePicker.CharacterSpacing;
|
||||
}
|
||||
|
||||
public static void MapFont(DatePickerHandler handler, IDatePicker datePicker)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
var font = datePicker.Font;
|
||||
if (font.Size > 0)
|
||||
handler.PlatformView.FontSize = font.Size;
|
||||
|
||||
if (!string.IsNullOrEmpty(font.Family))
|
||||
handler.PlatformView.FontFamily = font.Family;
|
||||
|
||||
// Map FontAttributes from the Font weight/slant
|
||||
var attrs = FontAttributes.None;
|
||||
if (font.Weight >= FontWeight.Bold)
|
||||
attrs |= FontAttributes.Bold;
|
||||
// Note: Font.Slant for italic would require checking FontSlant
|
||||
handler.PlatformView.FontAttributes = attrs;
|
||||
}
|
||||
|
||||
public static void MapBackground(DatePickerHandler handler, IDatePicker datePicker)
|
||||
@@ -108,7 +140,7 @@ public partial class DatePickerHandler : ViewHandler<IDatePicker, SkiaDatePicker
|
||||
|
||||
if (datePicker.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,11 @@ public partial class EditorHandler : ViewHandler<IEditor, SkiaEditor>
|
||||
[nameof(IEditor.Placeholder)] = MapPlaceholder,
|
||||
[nameof(IEditor.PlaceholderColor)] = MapPlaceholderColor,
|
||||
[nameof(IEditor.TextColor)] = MapTextColor,
|
||||
[nameof(ITextStyle.Font)] = MapFont,
|
||||
[nameof(IEditor.CharacterSpacing)] = MapCharacterSpacing,
|
||||
[nameof(IEditor.IsReadOnly)] = MapIsReadOnly,
|
||||
[nameof(IEditor.IsTextPredictionEnabled)] = MapIsTextPredictionEnabled,
|
||||
[nameof(IEditor.IsSpellCheckEnabled)] = MapIsSpellCheckEnabled,
|
||||
[nameof(IEditor.MaxLength)] = MapMaxLength,
|
||||
[nameof(IEditor.CursorPosition)] = MapCursorPosition,
|
||||
[nameof(IEditor.SelectionLength)] = MapSelectionLength,
|
||||
@@ -97,7 +99,7 @@ public partial class EditorHandler : ViewHandler<IEditor, SkiaEditor>
|
||||
if (handler.PlatformView is null) return;
|
||||
if (editor.PlaceholderColor is not null)
|
||||
{
|
||||
handler.PlatformView.PlaceholderColor = editor.PlaceholderColor.ToSKColor();
|
||||
handler.PlatformView.PlaceholderColor = editor.PlaceholderColor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,13 +108,34 @@ public partial class EditorHandler : ViewHandler<IEditor, SkiaEditor>
|
||||
if (handler.PlatformView is null) return;
|
||||
if (editor.TextColor is not null)
|
||||
{
|
||||
handler.PlatformView.TextColor = editor.TextColor.ToSKColor();
|
||||
handler.PlatformView.TextColor = editor.TextColor;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapFont(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
var font = editor.Font;
|
||||
if (font.Size > 0)
|
||||
handler.PlatformView.FontSize = font.Size;
|
||||
|
||||
if (!string.IsNullOrEmpty(font.Family))
|
||||
handler.PlatformView.FontFamily = font.Family;
|
||||
|
||||
// Convert Font weight/slant to FontAttributes
|
||||
FontAttributes attrs = FontAttributes.None;
|
||||
if (font.Weight >= FontWeight.Bold)
|
||||
attrs |= FontAttributes.Bold;
|
||||
if (font.Slant == FontSlant.Italic || font.Slant == FontSlant.Oblique)
|
||||
attrs |= FontAttributes.Italic;
|
||||
handler.PlatformView.FontAttributes = attrs;
|
||||
}
|
||||
|
||||
public static void MapCharacterSpacing(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
// Character spacing would require custom text rendering
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.CharacterSpacing = editor.CharacterSpacing;
|
||||
}
|
||||
|
||||
public static void MapIsReadOnly(EditorHandler handler, IEditor editor)
|
||||
@@ -123,7 +146,14 @@ public partial class EditorHandler : ViewHandler<IEditor, SkiaEditor>
|
||||
|
||||
public static void MapIsTextPredictionEnabled(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
// Text prediction not applicable to desktop
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.IsTextPredictionEnabled = editor.IsTextPredictionEnabled;
|
||||
}
|
||||
|
||||
public static void MapIsSpellCheckEnabled(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.IsSpellCheckEnabled = editor.IsSpellCheckEnabled;
|
||||
}
|
||||
|
||||
public static void MapMaxLength(EditorHandler handler, IEditor editor)
|
||||
@@ -140,22 +170,39 @@ public partial class EditorHandler : ViewHandler<IEditor, SkiaEditor>
|
||||
|
||||
public static void MapSelectionLength(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
// Selection would need to be added to SkiaEditor
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.SelectionLength = editor.SelectionLength;
|
||||
}
|
||||
|
||||
public static void MapKeyboard(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
// Virtual keyboard type not applicable to desktop
|
||||
// Virtual keyboard type not applicable to desktop - stored for future use
|
||||
}
|
||||
|
||||
public static void MapHorizontalTextAlignment(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
// Text alignment would require changes to SkiaEditor drawing
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
handler.PlatformView.HorizontalTextAlignment = editor.HorizontalTextAlignment switch
|
||||
{
|
||||
Microsoft.Maui.TextAlignment.Start => TextAlignment.Start,
|
||||
Microsoft.Maui.TextAlignment.Center => TextAlignment.Center,
|
||||
Microsoft.Maui.TextAlignment.End => TextAlignment.End,
|
||||
_ => TextAlignment.Start
|
||||
};
|
||||
}
|
||||
|
||||
public static void MapVerticalTextAlignment(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
// Text alignment would require changes to SkiaEditor drawing
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
handler.PlatformView.VerticalTextAlignment = editor.VerticalTextAlignment switch
|
||||
{
|
||||
Microsoft.Maui.TextAlignment.Start => TextAlignment.Start,
|
||||
Microsoft.Maui.TextAlignment.Center => TextAlignment.Center,
|
||||
Microsoft.Maui.TextAlignment.End => TextAlignment.End,
|
||||
_ => TextAlignment.Start
|
||||
};
|
||||
}
|
||||
|
||||
public static void MapBackground(EditorHandler handler, IEditor editor)
|
||||
@@ -164,7 +211,7 @@ public partial class EditorHandler : ViewHandler<IEditor, SkiaEditor>
|
||||
|
||||
if (editor.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.EditorBackgroundColor = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,9 +219,9 @@ public partial class EditorHandler : ViewHandler<IEditor, SkiaEditor>
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (editor is VisualElement ve && ve.BackgroundColor != null)
|
||||
if (editor is Editor ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.EditorBackgroundColor = ve.BackgroundColor;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for Entry control.
|
||||
/// </summary>
|
||||
public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps the property mapper for the handler.
|
||||
/// </summary>
|
||||
public static IPropertyMapper<IEntry, EntryHandler> Mapper = new PropertyMapper<IEntry, EntryHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(IEntry.Text)] = MapText,
|
||||
[nameof(IEntry.TextColor)] = MapTextColor,
|
||||
[nameof(IEntry.Placeholder)] = MapPlaceholder,
|
||||
[nameof(IEntry.PlaceholderColor)] = MapPlaceholderColor,
|
||||
[nameof(IEntry.Font)] = MapFont,
|
||||
[nameof(IEntry.IsPassword)] = MapIsPassword,
|
||||
[nameof(IEntry.MaxLength)] = MapMaxLength,
|
||||
[nameof(IEntry.IsReadOnly)] = MapIsReadOnly,
|
||||
[nameof(IEntry.HorizontalTextAlignment)] = MapHorizontalTextAlignment,
|
||||
[nameof(IEntry.CursorPosition)] = MapCursorPosition,
|
||||
[nameof(IEntry.SelectionLength)] = MapSelectionLength,
|
||||
[nameof(IEntry.ReturnType)] = MapReturnType,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
[nameof(IEntry.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Maps the command mapper for the handler.
|
||||
/// </summary>
|
||||
public static CommandMapper<IEntry, EntryHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
{
|
||||
};
|
||||
|
||||
public EntryHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public EntryHandler(IPropertyMapper? mapper)
|
||||
: base(mapper ?? Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public EntryHandler(IPropertyMapper? mapper, CommandMapper? commandMapper)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaEntry CreatePlatformView()
|
||||
{
|
||||
return new SkiaEntry();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaEntry platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.TextChanged += OnTextChanged;
|
||||
platformView.Completed += OnCompleted;
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaEntry platformView)
|
||||
{
|
||||
platformView.TextChanged -= OnTextChanged;
|
||||
platformView.Completed -= OnCompleted;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnTextChanged(object? sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (VirtualView != null && VirtualView.Text != e.NewText)
|
||||
{
|
||||
VirtualView.Text = e.NewText;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCompleted(object? sender, EventArgs e)
|
||||
{
|
||||
VirtualView?.Completed();
|
||||
}
|
||||
|
||||
public static void MapText(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView.Text != entry.Text)
|
||||
{
|
||||
handler.PlatformView.Text = entry.Text ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapTextColor(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (entry.TextColor != null)
|
||||
{
|
||||
handler.PlatformView.TextColor = entry.TextColor.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapPlaceholder(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
handler.PlatformView.Placeholder = entry.Placeholder ?? "";
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapPlaceholderColor(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (entry.PlaceholderColor != null)
|
||||
{
|
||||
handler.PlatformView.PlaceholderColor = entry.PlaceholderColor.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapFont(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
var font = entry.Font;
|
||||
if (font.Family != null)
|
||||
{
|
||||
handler.PlatformView.FontFamily = font.Family;
|
||||
}
|
||||
handler.PlatformView.FontSize = (float)font.Size;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapIsPassword(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
handler.PlatformView.IsPassword = entry.IsPassword;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapMaxLength(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
handler.PlatformView.MaxLength = entry.MaxLength;
|
||||
}
|
||||
|
||||
public static void MapIsReadOnly(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
handler.PlatformView.IsReadOnly = entry.IsReadOnly;
|
||||
}
|
||||
|
||||
public static void MapHorizontalTextAlignment(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
handler.PlatformView.HorizontalTextAlignment = entry.HorizontalTextAlignment switch
|
||||
{
|
||||
Microsoft.Maui.TextAlignment.Start => TextAlignment.Start,
|
||||
Microsoft.Maui.TextAlignment.Center => TextAlignment.Center,
|
||||
Microsoft.Maui.TextAlignment.End => TextAlignment.End,
|
||||
_ => TextAlignment.Start
|
||||
};
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapCursorPosition(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
handler.PlatformView.CursorPosition = entry.CursorPosition;
|
||||
}
|
||||
|
||||
public static void MapSelectionLength(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
// Selection length is handled internally by SkiaEntry
|
||||
}
|
||||
|
||||
public static void MapReturnType(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
// Return type affects keyboard on mobile; on desktop, Enter always completes
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
handler.PlatformView.IsEnabled = entry.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackground(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
var background = entry.Background;
|
||||
if (background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (entry is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Controls;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
@@ -28,9 +29,13 @@ public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
||||
[nameof(IEntry.IsPassword)] = MapIsPassword,
|
||||
[nameof(IEntry.ReturnType)] = MapReturnType,
|
||||
[nameof(IEntry.ClearButtonVisibility)] = MapClearButtonVisibility,
|
||||
[nameof(IEntry.IsTextPredictionEnabled)] = MapIsTextPredictionEnabled,
|
||||
[nameof(IEntry.IsSpellCheckEnabled)] = MapIsSpellCheckEnabled,
|
||||
[nameof(ITextAlignment.HorizontalTextAlignment)] = MapHorizontalTextAlignment,
|
||||
[nameof(ITextAlignment.VerticalTextAlignment)] = MapVerticalTextAlignment,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
["SelectAllOnDoubleClick"] = MapSelectAllOnDoubleClick,
|
||||
};
|
||||
|
||||
public static CommandMapper<IEntry, EntryHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -96,7 +101,7 @@ public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (entry.TextColor is not null)
|
||||
handler.PlatformView.TextColor = entry.TextColor.ToSKColor();
|
||||
handler.PlatformView.TextColor = entry.TextColor;
|
||||
}
|
||||
|
||||
public static void MapFont(EntryHandler handler, IEntry entry)
|
||||
@@ -105,19 +110,24 @@ public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
||||
|
||||
var font = entry.Font;
|
||||
if (font.Size > 0)
|
||||
handler.PlatformView.FontSize = (float)font.Size;
|
||||
handler.PlatformView.FontSize = font.Size;
|
||||
|
||||
if (!string.IsNullOrEmpty(font.Family))
|
||||
handler.PlatformView.FontFamily = font.Family;
|
||||
|
||||
handler.PlatformView.IsBold = font.Weight >= FontWeight.Bold;
|
||||
handler.PlatformView.IsItalic = font.Slant == FontSlant.Italic || font.Slant == FontSlant.Oblique;
|
||||
// Convert Font weight/slant to FontAttributes
|
||||
FontAttributes attrs = FontAttributes.None;
|
||||
if (font.Weight >= FontWeight.Bold)
|
||||
attrs |= FontAttributes.Bold;
|
||||
if (font.Slant == FontSlant.Italic || font.Slant == FontSlant.Oblique)
|
||||
attrs |= FontAttributes.Italic;
|
||||
handler.PlatformView.FontAttributes = attrs;
|
||||
}
|
||||
|
||||
public static void MapCharacterSpacing(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.CharacterSpacing = (float)entry.CharacterSpacing;
|
||||
handler.PlatformView.CharacterSpacing = entry.CharacterSpacing;
|
||||
}
|
||||
|
||||
public static void MapPlaceholder(EntryHandler handler, IEntry entry)
|
||||
@@ -131,7 +141,7 @@ public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (entry.PlaceholderColor is not null)
|
||||
handler.PlatformView.PlaceholderColor = entry.PlaceholderColor.ToSKColor();
|
||||
handler.PlatformView.PlaceholderColor = entry.PlaceholderColor;
|
||||
}
|
||||
|
||||
public static void MapIsReadOnly(EntryHandler handler, IEntry entry)
|
||||
@@ -177,16 +187,28 @@ public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
||||
handler.PlatformView.ShowClearButton = entry.ClearButtonVisibility == ClearButtonVisibility.WhileEditing;
|
||||
}
|
||||
|
||||
public static void MapIsTextPredictionEnabled(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.IsTextPredictionEnabled = entry.IsTextPredictionEnabled;
|
||||
}
|
||||
|
||||
public static void MapIsSpellCheckEnabled(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.IsSpellCheckEnabled = entry.IsSpellCheckEnabled;
|
||||
}
|
||||
|
||||
public static void MapHorizontalTextAlignment(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
handler.PlatformView.HorizontalTextAlignment = entry.HorizontalTextAlignment switch
|
||||
{
|
||||
Microsoft.Maui.TextAlignment.Start => Platform.TextAlignment.Start,
|
||||
Microsoft.Maui.TextAlignment.Center => Platform.TextAlignment.Center,
|
||||
Microsoft.Maui.TextAlignment.End => Platform.TextAlignment.End,
|
||||
_ => Platform.TextAlignment.Start
|
||||
Microsoft.Maui.TextAlignment.Start => TextAlignment.Start,
|
||||
Microsoft.Maui.TextAlignment.Center => TextAlignment.Center,
|
||||
Microsoft.Maui.TextAlignment.End => TextAlignment.End,
|
||||
_ => TextAlignment.Start
|
||||
};
|
||||
}
|
||||
|
||||
@@ -196,10 +218,10 @@ public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
||||
|
||||
handler.PlatformView.VerticalTextAlignment = entry.VerticalTextAlignment switch
|
||||
{
|
||||
Microsoft.Maui.TextAlignment.Start => Platform.TextAlignment.Start,
|
||||
Microsoft.Maui.TextAlignment.Center => Platform.TextAlignment.Center,
|
||||
Microsoft.Maui.TextAlignment.End => Platform.TextAlignment.End,
|
||||
_ => Platform.TextAlignment.Center
|
||||
Microsoft.Maui.TextAlignment.Start => TextAlignment.Start,
|
||||
Microsoft.Maui.TextAlignment.Center => TextAlignment.Center,
|
||||
Microsoft.Maui.TextAlignment.End => TextAlignment.End,
|
||||
_ => TextAlignment.Center
|
||||
};
|
||||
}
|
||||
|
||||
@@ -209,7 +231,29 @@ public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
||||
|
||||
if (entry.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (entry is Entry ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.EntryBackgroundColor = ve.BackgroundColor;
|
||||
// Also set base BackgroundColor so SkiaView.DrawBackground() respects transparency
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapSelectAllOnDoubleClick(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (entry is BindableObject bindable)
|
||||
{
|
||||
handler.PlatformView.SelectAllOnDoubleClick = EntryExtensions.GetSelectAllOnDoubleClick(bindable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
105
Handlers/FlexLayoutHandler.cs
Normal file
105
Handlers/FlexLayoutHandler.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Layouts;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
public class FlexLayoutHandler : LayoutHandler
|
||||
{
|
||||
public new static IPropertyMapper<FlexLayout, FlexLayoutHandler> Mapper = new PropertyMapper<FlexLayout, FlexLayoutHandler>(LayoutHandler.Mapper)
|
||||
{
|
||||
["Direction"] = MapDirection,
|
||||
["Wrap"] = MapWrap,
|
||||
["JustifyContent"] = MapJustifyContent,
|
||||
["AlignItems"] = MapAlignItems,
|
||||
["AlignContent"] = MapAlignContent
|
||||
};
|
||||
|
||||
public FlexLayoutHandler() : base(Mapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaLayoutView CreatePlatformView()
|
||||
{
|
||||
return new SkiaFlexLayout();
|
||||
}
|
||||
|
||||
public static void MapDirection(FlexLayoutHandler handler, FlexLayout layout)
|
||||
{
|
||||
if (handler.PlatformView is SkiaFlexLayout flexLayout)
|
||||
{
|
||||
flexLayout.Direction = layout.Direction switch
|
||||
{
|
||||
Microsoft.Maui.Layouts.FlexDirection.Row => FlexDirection.Row,
|
||||
Microsoft.Maui.Layouts.FlexDirection.RowReverse => FlexDirection.RowReverse,
|
||||
Microsoft.Maui.Layouts.FlexDirection.Column => FlexDirection.Column,
|
||||
Microsoft.Maui.Layouts.FlexDirection.ColumnReverse => FlexDirection.ColumnReverse,
|
||||
_ => FlexDirection.Row,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapWrap(FlexLayoutHandler handler, FlexLayout layout)
|
||||
{
|
||||
if (handler.PlatformView is SkiaFlexLayout flexLayout)
|
||||
{
|
||||
flexLayout.Wrap = layout.Wrap switch
|
||||
{
|
||||
Microsoft.Maui.Layouts.FlexWrap.NoWrap => FlexWrap.NoWrap,
|
||||
Microsoft.Maui.Layouts.FlexWrap.Wrap => FlexWrap.Wrap,
|
||||
Microsoft.Maui.Layouts.FlexWrap.Reverse => FlexWrap.WrapReverse,
|
||||
_ => FlexWrap.NoWrap,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapJustifyContent(FlexLayoutHandler handler, FlexLayout layout)
|
||||
{
|
||||
if (handler.PlatformView is SkiaFlexLayout flexLayout)
|
||||
{
|
||||
flexLayout.JustifyContent = layout.JustifyContent switch
|
||||
{
|
||||
Microsoft.Maui.Layouts.FlexJustify.Start => FlexJustify.Start,
|
||||
Microsoft.Maui.Layouts.FlexJustify.Center => FlexJustify.Center,
|
||||
Microsoft.Maui.Layouts.FlexJustify.End => FlexJustify.End,
|
||||
Microsoft.Maui.Layouts.FlexJustify.SpaceBetween => FlexJustify.SpaceBetween,
|
||||
Microsoft.Maui.Layouts.FlexJustify.SpaceAround => FlexJustify.SpaceAround,
|
||||
Microsoft.Maui.Layouts.FlexJustify.SpaceEvenly => FlexJustify.SpaceEvenly,
|
||||
_ => FlexJustify.Start,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapAlignItems(FlexLayoutHandler handler, FlexLayout layout)
|
||||
{
|
||||
if (handler.PlatformView is SkiaFlexLayout flexLayout)
|
||||
{
|
||||
flexLayout.AlignItems = layout.AlignItems switch
|
||||
{
|
||||
Microsoft.Maui.Layouts.FlexAlignItems.Start => FlexAlignItems.Start,
|
||||
Microsoft.Maui.Layouts.FlexAlignItems.Center => FlexAlignItems.Center,
|
||||
Microsoft.Maui.Layouts.FlexAlignItems.End => FlexAlignItems.End,
|
||||
Microsoft.Maui.Layouts.FlexAlignItems.Stretch => FlexAlignItems.Stretch,
|
||||
_ => FlexAlignItems.Stretch,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapAlignContent(FlexLayoutHandler handler, FlexLayout layout)
|
||||
{
|
||||
if (handler.PlatformView is SkiaFlexLayout flexLayout)
|
||||
{
|
||||
flexLayout.AlignContent = layout.AlignContent switch
|
||||
{
|
||||
Microsoft.Maui.Layouts.FlexAlignContent.Start => FlexAlignContent.Start,
|
||||
Microsoft.Maui.Layouts.FlexAlignContent.Center => FlexAlignContent.Center,
|
||||
Microsoft.Maui.Layouts.FlexAlignContent.End => FlexAlignContent.End,
|
||||
Microsoft.Maui.Layouts.FlexAlignContent.Stretch => FlexAlignContent.Stretch,
|
||||
Microsoft.Maui.Layouts.FlexAlignContent.SpaceBetween => FlexAlignContent.SpaceBetween,
|
||||
Microsoft.Maui.Layouts.FlexAlignContent.SpaceAround => FlexAlignContent.SpaceAround,
|
||||
Microsoft.Maui.Layouts.FlexAlignContent.SpaceEvenly => FlexAlignContent.SpaceEvenly,
|
||||
_ => FlexAlignContent.Stretch,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
@@ -13,12 +15,17 @@ namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
/// </summary>
|
||||
public partial class FlyoutPageHandler : ViewHandler<IFlyoutView, SkiaFlyoutPage>
|
||||
{
|
||||
private bool _isUpdatingPresented;
|
||||
|
||||
public static IPropertyMapper<IFlyoutView, FlyoutPageHandler> Mapper = new PropertyMapper<IFlyoutView, FlyoutPageHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(IFlyoutView.Flyout)] = MapFlyout,
|
||||
[nameof(IFlyoutView.Detail)] = MapDetail,
|
||||
[nameof(IFlyoutView.IsPresented)] = MapIsPresented,
|
||||
[nameof(IFlyoutView.FlyoutWidth)] = MapFlyoutWidth,
|
||||
[nameof(IFlyoutView.IsGestureEnabled)] = MapIsGestureEnabled,
|
||||
[nameof(IFlyoutView.FlyoutBehavior)] = MapFlyoutBehavior,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
};
|
||||
|
||||
public static CommandMapper<IFlyoutView, FlyoutPageHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -55,14 +62,83 @@ public partial class FlyoutPageHandler : ViewHandler<IFlyoutView, SkiaFlyoutPage
|
||||
|
||||
private void OnIsPresentedChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (VirtualView is null || PlatformView is null || _isUpdatingPresented) return;
|
||||
|
||||
try
|
||||
{
|
||||
_isUpdatingPresented = true;
|
||||
// Sync back to the virtual view
|
||||
if (VirtualView is FlyoutPage flyoutPage)
|
||||
{
|
||||
flyoutPage.IsPresented = PlatformView.IsPresented;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isUpdatingPresented = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapFlyout(FlyoutPageHandler handler, IFlyoutView flyoutView)
|
||||
{
|
||||
if (handler.PlatformView is null || handler.MauiContext is null) return;
|
||||
|
||||
var flyout = flyoutView.Flyout;
|
||||
if (flyout == null)
|
||||
{
|
||||
handler.PlatformView.Flyout = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create handler for flyout content
|
||||
if (flyout.Handler == null)
|
||||
{
|
||||
flyout.Handler = flyout.ToViewHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (flyout.Handler?.PlatformView is SkiaView skiaFlyout)
|
||||
{
|
||||
handler.PlatformView.Flyout = skiaFlyout;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapDetail(FlyoutPageHandler handler, IFlyoutView flyoutView)
|
||||
{
|
||||
if (handler.PlatformView is null || handler.MauiContext is null) return;
|
||||
|
||||
var detail = flyoutView.Detail;
|
||||
if (detail == null)
|
||||
{
|
||||
handler.PlatformView.Detail = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create handler for detail content
|
||||
if (detail.Handler == null)
|
||||
{
|
||||
detail.Handler = detail.ToViewHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (detail.Handler?.PlatformView is SkiaView skiaDetail)
|
||||
{
|
||||
handler.PlatformView.Detail = skiaDetail;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIsPresented(FlyoutPageHandler handler, IFlyoutView flyoutView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
if (handler.PlatformView is null || handler._isUpdatingPresented) return;
|
||||
|
||||
try
|
||||
{
|
||||
handler._isUpdatingPresented = true;
|
||||
handler.PlatformView.IsPresented = flyoutView.IsPresented;
|
||||
}
|
||||
finally
|
||||
{
|
||||
handler._isUpdatingPresented = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapFlyoutWidth(FlyoutPageHandler handler, IFlyoutView flyoutView)
|
||||
{
|
||||
@@ -88,4 +164,14 @@ public partial class FlyoutPageHandler : ViewHandler<IFlyoutView, SkiaFlyoutPage
|
||||
_ => FlyoutLayoutBehavior.Default
|
||||
};
|
||||
}
|
||||
|
||||
public static void MapBackground(FlyoutPageHandler handler, IFlyoutView flyoutView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (flyoutView is FlyoutPage flyoutPage && flyoutPage.Background is SolidColorBrush solidBrush)
|
||||
{
|
||||
handler.PlatformView.ScrimColor = solidBrush.Color.WithAlpha(100f / 255f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
@@ -37,15 +38,36 @@ public partial class FrameHandler : ViewHandler<Frame, SkiaFrame>
|
||||
return new SkiaFrame();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaFrame platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
if (VirtualView is View view)
|
||||
{
|
||||
platformView.MauiView = view;
|
||||
}
|
||||
platformView.Tapped += OnPlatformViewTapped;
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaFrame platformView)
|
||||
{
|
||||
platformView.Tapped -= OnPlatformViewTapped;
|
||||
platformView.MauiView = null;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnPlatformViewTapped(object? sender, EventArgs e)
|
||||
{
|
||||
if (VirtualView is View view)
|
||||
{
|
||||
GestureManager.ProcessTap(view, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBorderColor(FrameHandler handler, Frame frame)
|
||||
{
|
||||
if (frame.BorderColor != null)
|
||||
{
|
||||
handler.PlatformView.Stroke = new SKColor(
|
||||
(byte)(frame.BorderColor.Red * 255),
|
||||
(byte)(frame.BorderColor.Green * 255),
|
||||
(byte)(frame.BorderColor.Blue * 255),
|
||||
(byte)(frame.BorderColor.Alpha * 255));
|
||||
handler.PlatformView.Stroke = frame.BorderColor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,11 +85,7 @@ public partial class FrameHandler : ViewHandler<Frame, SkiaFrame>
|
||||
{
|
||||
if (frame.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = new SKColor(
|
||||
(byte)(frame.BackgroundColor.Red * 255),
|
||||
(byte)(frame.BackgroundColor.Green * 255),
|
||||
(byte)(frame.BackgroundColor.Blue * 255),
|
||||
(byte)(frame.BackgroundColor.Alpha * 255));
|
||||
handler.PlatformView.BackgroundColor = frame.BackgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +110,7 @@ public partial class FrameHandler : ViewHandler<Frame, SkiaFrame>
|
||||
// Create handler for content if it doesn't exist
|
||||
if (content.Handler == null)
|
||||
{
|
||||
content.Handler = content.ToHandler(handler.MauiContext);
|
||||
content.Handler = content.ToViewHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (content.Handler?.PlatformView is SkiaView skiaContent)
|
||||
|
||||
845
Handlers/GestureManager.cs
Normal file
845
Handlers/GestureManager.cs
Normal file
@@ -0,0 +1,845 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows.Input;
|
||||
using Microsoft.Maui.Controls;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Manages gesture recognition and processing for MAUI views on Linux.
|
||||
/// Handles tap, pan, swipe, pinch, and pointer gestures.
|
||||
/// </summary>
|
||||
public static class GestureManager
|
||||
{
|
||||
private class GestureTrackingState
|
||||
{
|
||||
public double StartX { get; set; }
|
||||
public double StartY { get; set; }
|
||||
public double CurrentX { get; set; }
|
||||
public double CurrentY { get; set; }
|
||||
public DateTime StartTime { get; set; }
|
||||
public bool IsPanning { get; set; }
|
||||
public bool IsPressed { get; set; }
|
||||
public bool IsPinching { get; set; }
|
||||
public double PinchScale { get; set; } = 1.0;
|
||||
}
|
||||
|
||||
private enum PointerEventType
|
||||
{
|
||||
Entered,
|
||||
Exited,
|
||||
Pressed,
|
||||
Moved,
|
||||
Released
|
||||
}
|
||||
|
||||
private static MethodInfo? _sendTappedMethod;
|
||||
private static MethodInfo? _sendPinchMethod;
|
||||
private static readonly Dictionary<View, (DateTime lastTap, int tapCount)> _tapTracking = new Dictionary<View, (DateTime, int)>();
|
||||
private static readonly Dictionary<View, GestureTrackingState> _gestureState = new Dictionary<View, GestureTrackingState>();
|
||||
|
||||
private const double SwipeMinDistance = 50.0;
|
||||
private const double SwipeMaxTime = 500.0;
|
||||
private const double SwipeDirectionThreshold = 0.5;
|
||||
private const double PanMinDistance = 10.0;
|
||||
private const double PinchScrollScale = 0.1; // Scale factor per scroll unit
|
||||
|
||||
/// <summary>
|
||||
/// Processes a tap gesture on the specified view.
|
||||
/// </summary>
|
||||
public static bool ProcessTap(View? view, double x, double y)
|
||||
{
|
||||
if (view == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var current = view;
|
||||
while (current != null)
|
||||
{
|
||||
var recognizers = current.GestureRecognizers;
|
||||
if (recognizers != null && recognizers.Count > 0 && ProcessTapOnView(current, x, y))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var parent = current.Parent;
|
||||
current = (parent is View parentView) ? parentView : null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ProcessTapOnView(View view, double x, double y)
|
||||
{
|
||||
var recognizers = view.GestureRecognizers;
|
||||
if (recognizers == null || recognizers.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool result = false;
|
||||
foreach (var item in recognizers)
|
||||
{
|
||||
var tapRecognizer = (item is TapGestureRecognizer) ? (TapGestureRecognizer)item : null;
|
||||
if (tapRecognizer == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Console.WriteLine($"[GestureManager] Processing TapGestureRecognizer on {view.GetType().Name}, CommandParameter={tapRecognizer.CommandParameter}, NumberOfTapsRequired={tapRecognizer.NumberOfTapsRequired}");
|
||||
int numberOfTapsRequired = tapRecognizer.NumberOfTapsRequired;
|
||||
if (numberOfTapsRequired > 1)
|
||||
{
|
||||
DateTime utcNow = DateTime.UtcNow;
|
||||
if (!_tapTracking.TryGetValue(view, out var tracking))
|
||||
{
|
||||
_tapTracking[view] = (utcNow, 1);
|
||||
Console.WriteLine($"[GestureManager] First tap 1/{numberOfTapsRequired}");
|
||||
continue;
|
||||
}
|
||||
if (!((utcNow - tracking.lastTap).TotalMilliseconds < 300.0))
|
||||
{
|
||||
_tapTracking[view] = (utcNow, 1);
|
||||
Console.WriteLine($"[GestureManager] Tap timeout, reset to 1/{numberOfTapsRequired}");
|
||||
continue;
|
||||
}
|
||||
int tapCount = tracking.tapCount + 1;
|
||||
if (tapCount < numberOfTapsRequired)
|
||||
{
|
||||
_tapTracking[view] = (utcNow, tapCount);
|
||||
Console.WriteLine($"[GestureManager] Tap {tapCount}/{numberOfTapsRequired}, waiting for more taps");
|
||||
continue;
|
||||
}
|
||||
_tapTracking.Remove(view);
|
||||
}
|
||||
bool eventFired = false;
|
||||
try
|
||||
{
|
||||
if (_sendTappedMethod == null)
|
||||
{
|
||||
_sendTappedMethod = typeof(TapGestureRecognizer).GetMethod("SendTapped", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
}
|
||||
if (_sendTappedMethod != null)
|
||||
{
|
||||
Console.WriteLine($"[GestureManager] Found SendTapped method with {_sendTappedMethod.GetParameters().Length} params");
|
||||
var args = new TappedEventArgs(tapRecognizer.CommandParameter);
|
||||
_sendTappedMethod.Invoke(tapRecognizer, new object[] { view, args });
|
||||
Console.WriteLine("[GestureManager] SendTapped invoked successfully");
|
||||
eventFired = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("[GestureManager] SendTapped failed: " + ex.Message);
|
||||
}
|
||||
if (!eventFired)
|
||||
{
|
||||
try
|
||||
{
|
||||
var field = typeof(TapGestureRecognizer).GetField("Tapped", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
?? typeof(TapGestureRecognizer).GetField("_tapped", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (field != null && field.GetValue(tapRecognizer) is EventHandler<TappedEventArgs> handler)
|
||||
{
|
||||
Console.WriteLine("[GestureManager] Invoking Tapped event directly");
|
||||
var args = new TappedEventArgs(tapRecognizer.CommandParameter);
|
||||
handler(tapRecognizer, args);
|
||||
eventFired = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("[GestureManager] Direct event invoke failed: " + ex.Message);
|
||||
}
|
||||
}
|
||||
if (!eventFired)
|
||||
{
|
||||
try
|
||||
{
|
||||
string[] fieldNames = new string[] { "TappedEvent", "_TappedHandler", "<Tapped>k__BackingField" };
|
||||
foreach (string fieldName in fieldNames)
|
||||
{
|
||||
var field = typeof(TapGestureRecognizer).GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (field != null)
|
||||
{
|
||||
Console.WriteLine("[GestureManager] Found field: " + fieldName);
|
||||
if (field.GetValue(tapRecognizer) is EventHandler<TappedEventArgs> handler)
|
||||
{
|
||||
var args = new TappedEventArgs(tapRecognizer.CommandParameter);
|
||||
handler(tapRecognizer, args);
|
||||
Console.WriteLine("[GestureManager] Event fired via " + fieldName);
|
||||
eventFired = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("[GestureManager] Backing field approach failed: " + ex.Message);
|
||||
}
|
||||
}
|
||||
if (!eventFired)
|
||||
{
|
||||
Console.WriteLine("[GestureManager] Could not fire event, dumping type info...");
|
||||
var methods = typeof(TapGestureRecognizer).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
foreach (var method in methods)
|
||||
{
|
||||
if (method.Name.Contains("Tap", StringComparison.OrdinalIgnoreCase) || method.Name.Contains("Send", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Console.WriteLine($"[GestureManager] Method: {method.Name}({string.Join(", ", from p in method.GetParameters() select p.ParameterType.Name)})");
|
||||
}
|
||||
}
|
||||
}
|
||||
ICommand? command = tapRecognizer.Command;
|
||||
if (command != null && command.CanExecute(tapRecognizer.CommandParameter))
|
||||
{
|
||||
Console.WriteLine("[GestureManager] Executing Command");
|
||||
command.Execute(tapRecognizer.CommandParameter);
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the view has any gesture recognizers.
|
||||
/// </summary>
|
||||
public static bool HasGestureRecognizers(View? view)
|
||||
{
|
||||
if (view == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return view.GestureRecognizers?.Count > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the view has a tap gesture recognizer.
|
||||
/// </summary>
|
||||
public static bool HasTapGestureRecognizer(View? view)
|
||||
{
|
||||
if (view?.GestureRecognizers == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (var recognizer in view.GestureRecognizers)
|
||||
{
|
||||
if (recognizer is TapGestureRecognizer)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a pointer down event.
|
||||
/// </summary>
|
||||
public static void ProcessPointerDown(View? view, double x, double y)
|
||||
{
|
||||
if (view != null)
|
||||
{
|
||||
_gestureState[view] = new GestureTrackingState
|
||||
{
|
||||
StartX = x,
|
||||
StartY = y,
|
||||
CurrentX = x,
|
||||
CurrentY = y,
|
||||
StartTime = DateTime.UtcNow,
|
||||
IsPanning = false,
|
||||
IsPressed = true
|
||||
};
|
||||
ProcessPointerEvent(view, x, y, PointerEventType.Pressed);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a pointer move event.
|
||||
/// </summary>
|
||||
public static void ProcessPointerMove(View? view, double x, double y)
|
||||
{
|
||||
if (view == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!_gestureState.TryGetValue(view, out var state))
|
||||
{
|
||||
ProcessPointerEvent(view, x, y, PointerEventType.Moved);
|
||||
return;
|
||||
}
|
||||
state.CurrentX = x;
|
||||
state.CurrentY = y;
|
||||
if (!state.IsPressed)
|
||||
{
|
||||
ProcessPointerEvent(view, x, y, PointerEventType.Moved);
|
||||
return;
|
||||
}
|
||||
double deltaX = x - state.StartX;
|
||||
double deltaY = y - state.StartY;
|
||||
if (Math.Sqrt(deltaX * deltaX + deltaY * deltaY) >= 10.0)
|
||||
{
|
||||
ProcessPanGesture(view, deltaX, deltaY, (GestureStatus)(state.IsPanning ? 1 : 0));
|
||||
state.IsPanning = true;
|
||||
}
|
||||
ProcessPointerEvent(view, x, y, PointerEventType.Moved);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a pointer up event.
|
||||
/// </summary>
|
||||
public static void ProcessPointerUp(View? view, double x, double y)
|
||||
{
|
||||
if (view == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_gestureState.TryGetValue(view, out var state))
|
||||
{
|
||||
state.CurrentX = x;
|
||||
state.CurrentY = y;
|
||||
double deltaX = x - state.StartX;
|
||||
double deltaY = y - state.StartY;
|
||||
double distance = Math.Sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
double elapsed = (DateTime.UtcNow - state.StartTime).TotalMilliseconds;
|
||||
if (distance >= 50.0 && elapsed <= 500.0)
|
||||
{
|
||||
var direction = DetermineSwipeDirection(deltaX, deltaY);
|
||||
if (direction != SwipeDirection.Right)
|
||||
{
|
||||
ProcessSwipeGesture(view, direction);
|
||||
}
|
||||
else if (Math.Abs(deltaX) > Math.Abs(deltaY) * 0.5)
|
||||
{
|
||||
ProcessSwipeGesture(view, (deltaX > 0.0) ? SwipeDirection.Right : SwipeDirection.Left);
|
||||
}
|
||||
}
|
||||
if (state.IsPanning)
|
||||
{
|
||||
ProcessPanGesture(view, deltaX, deltaY, (GestureStatus)2);
|
||||
}
|
||||
else if (distance < 15.0 && elapsed < 500.0)
|
||||
{
|
||||
Console.WriteLine($"[GestureManager] Detected tap on {view.GetType().Name} (distance={distance:F1}, elapsed={elapsed:F0}ms)");
|
||||
ProcessTap(view, x, y);
|
||||
}
|
||||
_gestureState.Remove(view);
|
||||
}
|
||||
ProcessPointerEvent(view, x, y, PointerEventType.Released);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a pointer entered event.
|
||||
/// </summary>
|
||||
public static void ProcessPointerEntered(View? view, double x, double y)
|
||||
{
|
||||
if (view != null)
|
||||
{
|
||||
ProcessPointerEvent(view, x, y, PointerEventType.Entered);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a pointer exited event.
|
||||
/// </summary>
|
||||
public static void ProcessPointerExited(View? view, double x, double y)
|
||||
{
|
||||
if (view != null)
|
||||
{
|
||||
ProcessPointerEvent(view, x, y, PointerEventType.Exited);
|
||||
}
|
||||
}
|
||||
|
||||
private static SwipeDirection DetermineSwipeDirection(double deltaX, double deltaY)
|
||||
{
|
||||
double absX = Math.Abs(deltaX);
|
||||
double absY = Math.Abs(deltaY);
|
||||
if (absX > absY * 0.5)
|
||||
{
|
||||
if (deltaX > 0.0)
|
||||
{
|
||||
return SwipeDirection.Right;
|
||||
}
|
||||
return SwipeDirection.Left;
|
||||
}
|
||||
if (absY > absX * 0.5)
|
||||
{
|
||||
if (deltaY > 0.0)
|
||||
{
|
||||
return SwipeDirection.Down;
|
||||
}
|
||||
return SwipeDirection.Up;
|
||||
}
|
||||
if (deltaX > 0.0)
|
||||
{
|
||||
return SwipeDirection.Right;
|
||||
}
|
||||
return SwipeDirection.Left;
|
||||
}
|
||||
|
||||
private static void ProcessSwipeGesture(View view, SwipeDirection direction)
|
||||
{
|
||||
var recognizers = view.GestureRecognizers;
|
||||
if (recognizers == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (var item in recognizers)
|
||||
{
|
||||
var swipeRecognizer = (item is SwipeGestureRecognizer) ? (SwipeGestureRecognizer)item : null;
|
||||
if (swipeRecognizer == null || !swipeRecognizer.Direction.HasFlag(direction))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Console.WriteLine($"[GestureManager] Swipe detected: {direction}");
|
||||
try
|
||||
{
|
||||
var method = typeof(SwipeGestureRecognizer).GetMethod("SendSwiped", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (method != null)
|
||||
{
|
||||
method.Invoke(swipeRecognizer, new object[] { view, direction });
|
||||
Console.WriteLine("[GestureManager] SendSwiped invoked successfully");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("[GestureManager] SendSwiped failed: " + ex.Message);
|
||||
}
|
||||
ICommand? command = swipeRecognizer.Command;
|
||||
if (command != null && command.CanExecute(swipeRecognizer.CommandParameter))
|
||||
{
|
||||
swipeRecognizer.Command.Execute(swipeRecognizer.CommandParameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessPanGesture(View view, double totalX, double totalY, GestureStatus status)
|
||||
{
|
||||
var recognizers = view.GestureRecognizers;
|
||||
if (recognizers == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (var item in recognizers)
|
||||
{
|
||||
var panRecognizer = (item is PanGestureRecognizer) ? (PanGestureRecognizer)item : null;
|
||||
if (panRecognizer == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Console.WriteLine($"[GestureManager] Pan gesture: status={status}, totalX={totalX:F1}, totalY={totalY:F1}");
|
||||
try
|
||||
{
|
||||
var method = typeof(PanGestureRecognizer).GetMethod("SendPan", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (method != null)
|
||||
{
|
||||
method.Invoke(panRecognizer, new object[]
|
||||
{
|
||||
view,
|
||||
totalX,
|
||||
totalY,
|
||||
(int)status
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("[GestureManager] SendPan failed: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessPointerEvent(View view, double x, double y, PointerEventType eventType)
|
||||
{
|
||||
var recognizers = view.GestureRecognizers;
|
||||
if (recognizers == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (var item in recognizers)
|
||||
{
|
||||
var pointerRecognizer = (item is PointerGestureRecognizer) ? (PointerGestureRecognizer)item : null;
|
||||
if (pointerRecognizer == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
try
|
||||
{
|
||||
string? methodName = eventType switch
|
||||
{
|
||||
PointerEventType.Entered => "SendPointerEntered",
|
||||
PointerEventType.Exited => "SendPointerExited",
|
||||
PointerEventType.Pressed => "SendPointerPressed",
|
||||
PointerEventType.Moved => "SendPointerMoved",
|
||||
PointerEventType.Released => "SendPointerReleased",
|
||||
_ => null,
|
||||
};
|
||||
if (methodName != null)
|
||||
{
|
||||
var method = typeof(PointerGestureRecognizer).GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (method != null)
|
||||
{
|
||||
var args = CreatePointerEventArgs(view, x, y);
|
||||
method.Invoke(pointerRecognizer, new object[] { view, args });
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("[GestureManager] Pointer event failed: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static object CreatePointerEventArgs(View view, double x, double y)
|
||||
{
|
||||
try
|
||||
{
|
||||
var type = typeof(PointerGestureRecognizer).Assembly.GetType("Microsoft.Maui.Controls.PointerEventArgs");
|
||||
if (type != null)
|
||||
{
|
||||
var ctor = type.GetConstructors().FirstOrDefault();
|
||||
if (ctor != null)
|
||||
{
|
||||
return ctor.Invoke(new object[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return null!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a scroll event that may be a pinch gesture (Ctrl+Scroll).
|
||||
/// Returns true if the scroll was consumed as a pinch gesture.
|
||||
/// </summary>
|
||||
public static bool ProcessScrollAsPinch(View? view, double x, double y, double deltaY, bool isCtrlPressed)
|
||||
{
|
||||
if (view == null || !isCtrlPressed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if view has a pinch gesture recognizer
|
||||
if (!HasPinchGestureRecognizer(view))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get or create gesture state
|
||||
if (!_gestureState.TryGetValue(view, out var state))
|
||||
{
|
||||
state = new GestureTrackingState
|
||||
{
|
||||
StartX = x,
|
||||
StartY = y,
|
||||
CurrentX = x,
|
||||
CurrentY = y,
|
||||
StartTime = DateTime.UtcNow,
|
||||
PinchScale = 1.0
|
||||
};
|
||||
_gestureState[view] = state;
|
||||
}
|
||||
|
||||
// Calculate new scale based on scroll delta
|
||||
double scaleDelta = 1.0 + (deltaY * PinchScrollScale);
|
||||
state.PinchScale *= scaleDelta;
|
||||
|
||||
// Clamp scale to reasonable bounds
|
||||
state.PinchScale = Math.Clamp(state.PinchScale, 0.1, 10.0);
|
||||
|
||||
GestureStatus status;
|
||||
if (!state.IsPinching)
|
||||
{
|
||||
state.IsPinching = true;
|
||||
status = GestureStatus.Started;
|
||||
}
|
||||
else
|
||||
{
|
||||
status = GestureStatus.Running;
|
||||
}
|
||||
|
||||
ProcessPinchGesture(view, state.PinchScale, x, y, status);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends an ongoing pinch gesture.
|
||||
/// </summary>
|
||||
public static void EndPinchGesture(View? view)
|
||||
{
|
||||
if (view == null) return;
|
||||
|
||||
if (_gestureState.TryGetValue(view, out var state) && state.IsPinching)
|
||||
{
|
||||
ProcessPinchGesture(view, state.PinchScale, state.CurrentX, state.CurrentY, GestureStatus.Completed);
|
||||
state.IsPinching = false;
|
||||
state.PinchScale = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessPinchGesture(View view, double scale, double originX, double originY, GestureStatus status)
|
||||
{
|
||||
var recognizers = view.GestureRecognizers;
|
||||
if (recognizers == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var item in recognizers)
|
||||
{
|
||||
var pinchRecognizer = item as PinchGestureRecognizer;
|
||||
if (pinchRecognizer == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Console.WriteLine($"[GestureManager] Pinch gesture: status={status}, scale={scale:F2}, origin=({originX:F0},{originY:F0})");
|
||||
|
||||
try
|
||||
{
|
||||
// Cache the method lookup
|
||||
if (_sendPinchMethod == null)
|
||||
{
|
||||
_sendPinchMethod = typeof(PinchGestureRecognizer).GetMethod("SendPinch",
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
}
|
||||
|
||||
if (_sendPinchMethod != null)
|
||||
{
|
||||
// SendPinch(IView sender, double scale, Point scaleOrigin, GestureStatus status)
|
||||
var scaleOrigin = new Point(originX / view.Width, originY / view.Height);
|
||||
_sendPinchMethod.Invoke(pinchRecognizer, new object[]
|
||||
{
|
||||
view,
|
||||
scale,
|
||||
scaleOrigin,
|
||||
status
|
||||
});
|
||||
Console.WriteLine("[GestureManager] SendPinch invoked successfully");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[GestureManager] SendPinch failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the view has a pinch gesture recognizer.
|
||||
/// </summary>
|
||||
public static bool HasPinchGestureRecognizer(View? view)
|
||||
{
|
||||
if (view?.GestureRecognizers == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (var recognizer in view.GestureRecognizers)
|
||||
{
|
||||
if (recognizer is PinchGestureRecognizer)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the view has a swipe gesture recognizer.
|
||||
/// </summary>
|
||||
public static bool HasSwipeGestureRecognizer(View? view)
|
||||
{
|
||||
if (view?.GestureRecognizers == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (var recognizer in view.GestureRecognizers)
|
||||
{
|
||||
if (recognizer is SwipeGestureRecognizer)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the view has a pan gesture recognizer.
|
||||
/// </summary>
|
||||
public static bool HasPanGestureRecognizer(View? view)
|
||||
{
|
||||
if (view?.GestureRecognizers == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (var recognizer in view.GestureRecognizers)
|
||||
{
|
||||
if (recognizer is PanGestureRecognizer)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the view has a pointer gesture recognizer.
|
||||
/// </summary>
|
||||
public static bool HasPointerGestureRecognizer(View? view)
|
||||
{
|
||||
if (view?.GestureRecognizers == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (var recognizer in view.GestureRecognizers)
|
||||
{
|
||||
if (recognizer is PointerGestureRecognizer)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the view has a drag gesture recognizer.
|
||||
/// </summary>
|
||||
public static bool HasDragGestureRecognizer(View? view)
|
||||
{
|
||||
if (view?.GestureRecognizers == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (var recognizer in view.GestureRecognizers)
|
||||
{
|
||||
if (recognizer is DragGestureRecognizer)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the view has a drop gesture recognizer.
|
||||
/// </summary>
|
||||
public static bool HasDropGestureRecognizer(View? view)
|
||||
{
|
||||
if (view?.GestureRecognizers == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (var recognizer in view.GestureRecognizers)
|
||||
{
|
||||
if (recognizer is DropGestureRecognizer)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initiates a drag operation from the specified view.
|
||||
/// </summary>
|
||||
public static void StartDrag(View? view, double x, double y)
|
||||
{
|
||||
if (view == null) return;
|
||||
|
||||
var recognizers = view.GestureRecognizers;
|
||||
if (recognizers == null) return;
|
||||
|
||||
foreach (var item in recognizers)
|
||||
{
|
||||
var dragRecognizer = item as DragGestureRecognizer;
|
||||
if (dragRecognizer == null) continue;
|
||||
|
||||
Console.WriteLine($"[GestureManager] Starting drag from {view.GetType().Name}");
|
||||
|
||||
try
|
||||
{
|
||||
// Create DragStartingEventArgs and invoke SendDragStarting
|
||||
var method = typeof(DragGestureRecognizer).GetMethod("SendDragStarting",
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
if (method != null)
|
||||
{
|
||||
method.Invoke(dragRecognizer, new object[] { view });
|
||||
Console.WriteLine("[GestureManager] SendDragStarting invoked successfully");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[GestureManager] SendDragStarting failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a drag enter event on the specified view.
|
||||
/// </summary>
|
||||
public static void ProcessDragEnter(View? view, double x, double y, object? data)
|
||||
{
|
||||
if (view == null) return;
|
||||
|
||||
var recognizers = view.GestureRecognizers;
|
||||
if (recognizers == null) return;
|
||||
|
||||
foreach (var item in recognizers)
|
||||
{
|
||||
var dropRecognizer = item as DropGestureRecognizer;
|
||||
if (dropRecognizer == null) continue;
|
||||
|
||||
Console.WriteLine($"[GestureManager] Drag enter on {view.GetType().Name}");
|
||||
|
||||
try
|
||||
{
|
||||
var method = typeof(DropGestureRecognizer).GetMethod("SendDragOver",
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
if (method != null)
|
||||
{
|
||||
method.Invoke(dropRecognizer, new object[] { view });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[GestureManager] SendDragOver failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a drop event on the specified view.
|
||||
/// </summary>
|
||||
public static void ProcessDrop(View? view, double x, double y, object? data)
|
||||
{
|
||||
if (view == null) return;
|
||||
|
||||
var recognizers = view.GestureRecognizers;
|
||||
if (recognizers == null) return;
|
||||
|
||||
foreach (var item in recognizers)
|
||||
{
|
||||
var dropRecognizer = item as DropGestureRecognizer;
|
||||
if (dropRecognizer == null) continue;
|
||||
|
||||
Console.WriteLine($"[GestureManager] Drop on {view.GetType().Name}");
|
||||
|
||||
try
|
||||
{
|
||||
var method = typeof(DropGestureRecognizer).GetMethod("SendDrop",
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
if (method != null)
|
||||
{
|
||||
method.Invoke(dropRecognizer, new object[] { view });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[GestureManager] SendDrop failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ public partial class GraphicsViewHandler : ViewHandler<IGraphicsView, SkiaGraphi
|
||||
|
||||
if (graphicsView.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
265
Handlers/GtkWebViewHandler.cs
Normal file
265
Handlers/GtkWebViewHandler.cs
Normal file
@@ -0,0 +1,265 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Platform;
|
||||
using Microsoft.Maui.Platform.Linux.Native;
|
||||
using Microsoft.Maui.Platform.Linux.Services;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for WebView using native GTK WebKitGTK widget.
|
||||
/// </summary>
|
||||
public class GtkWebViewHandler : ViewHandler<IWebView, GtkWebViewProxy>
|
||||
{
|
||||
private GtkWebViewPlatformView? _platformWebView;
|
||||
private bool _isRegisteredWithHost;
|
||||
private SKRect _lastBounds;
|
||||
|
||||
public static IPropertyMapper<IWebView, GtkWebViewHandler> Mapper = new PropertyMapper<IWebView, GtkWebViewHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(IWebView.Source)] = MapSource,
|
||||
};
|
||||
|
||||
public static CommandMapper<IWebView, GtkWebViewHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
{
|
||||
[nameof(IWebView.GoBack)] = MapGoBack,
|
||||
[nameof(IWebView.GoForward)] = MapGoForward,
|
||||
[nameof(IWebView.Reload)] = MapReload,
|
||||
};
|
||||
|
||||
public GtkWebViewHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public GtkWebViewHandler(IPropertyMapper? mapper = null, CommandMapper? commandMapper = null)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override GtkWebViewProxy CreatePlatformView()
|
||||
{
|
||||
_platformWebView = new GtkWebViewPlatformView();
|
||||
return new GtkWebViewProxy(this, _platformWebView);
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(GtkWebViewProxy platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
if (_platformWebView != null)
|
||||
{
|
||||
_platformWebView.NavigationStarted += OnNavigationStarted;
|
||||
_platformWebView.NavigationCompleted += OnNavigationCompleted;
|
||||
_platformWebView.ScriptDialogRequested += OnScriptDialogRequested;
|
||||
}
|
||||
Console.WriteLine("[GtkWebViewHandler] ConnectHandler - WebView ready");
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(GtkWebViewProxy platformView)
|
||||
{
|
||||
if (_platformWebView != null)
|
||||
{
|
||||
_platformWebView.NavigationStarted -= OnNavigationStarted;
|
||||
_platformWebView.NavigationCompleted -= OnNavigationCompleted;
|
||||
_platformWebView.ScriptDialogRequested -= OnScriptDialogRequested;
|
||||
UnregisterFromHost();
|
||||
_platformWebView.Dispose();
|
||||
_platformWebView = null;
|
||||
}
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private async void OnScriptDialogRequested(object? sender,
|
||||
(ScriptDialogType Type, string Message, Action<bool> Callback) e)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] Script dialog requested: type={e.Type}, message={e.Message}");
|
||||
|
||||
string title = e.Type switch
|
||||
{
|
||||
ScriptDialogType.Alert => "Alert",
|
||||
ScriptDialogType.Confirm => "Confirm",
|
||||
ScriptDialogType.Prompt => "Prompt",
|
||||
_ => "Message"
|
||||
};
|
||||
|
||||
string? acceptButton = e.Type == ScriptDialogType.Alert ? "OK" : "OK";
|
||||
string? cancelButton = e.Type == ScriptDialogType.Alert ? null : "Cancel";
|
||||
|
||||
try
|
||||
{
|
||||
bool result = await LinuxDialogService.ShowAlertAsync(title, e.Message, acceptButton, cancelButton);
|
||||
e.Callback(result);
|
||||
Console.WriteLine($"[GtkWebViewHandler] Dialog result: {result}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] Error showing dialog: {ex.Message}");
|
||||
e.Callback(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNavigationStarted(object? sender, string uri)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] Navigation started: {uri}");
|
||||
try
|
||||
{
|
||||
GLibNative.IdleAdd(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (VirtualView is IWebViewController controller)
|
||||
{
|
||||
var args = new Microsoft.Maui.Controls.WebNavigatingEventArgs(
|
||||
WebNavigationEvent.NewPage, null, uri);
|
||||
controller.SendNavigating(args);
|
||||
Console.WriteLine("[GtkWebViewHandler] Sent Navigating event to VirtualView");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] Error in SendNavigating: {ex.Message}");
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] Error dispatching navigation started: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNavigationCompleted(object? sender, (string Url, bool Success) e)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] Navigation completed: {e.Url} (Success: {e.Success})");
|
||||
try
|
||||
{
|
||||
GLibNative.IdleAdd(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (VirtualView is IWebViewController controller)
|
||||
{
|
||||
var result = e.Success ? WebNavigationResult.Success : WebNavigationResult.Failure;
|
||||
var args = new Microsoft.Maui.Controls.WebNavigatedEventArgs(
|
||||
WebNavigationEvent.NewPage, null, e.Url, result);
|
||||
controller.SendNavigated(args);
|
||||
|
||||
bool canGoBack = _platformWebView?.CanGoBack() ?? false;
|
||||
bool canGoForward = _platformWebView?.CanGoForward() ?? false;
|
||||
controller.CanGoBack = canGoBack;
|
||||
controller.CanGoForward = canGoForward;
|
||||
Console.WriteLine($"[GtkWebViewHandler] Sent Navigated, CanGoBack={canGoBack}, CanGoForward={canGoForward}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] Error in SendNavigated: {ex.Message}");
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] Error dispatching navigation completed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
internal void RegisterWithHost(SKRect bounds)
|
||||
{
|
||||
if (_platformWebView == null)
|
||||
return;
|
||||
|
||||
var hostService = GtkHostService.Instance;
|
||||
if (hostService.HostWindow == null || hostService.WebViewManager == null)
|
||||
{
|
||||
Console.WriteLine("[GtkWebViewHandler] Warning: GTK host not initialized, cannot register WebView");
|
||||
return;
|
||||
}
|
||||
|
||||
int x = (int)bounds.Left;
|
||||
int y = (int)bounds.Top;
|
||||
int width = (int)bounds.Width;
|
||||
int height = (int)bounds.Height;
|
||||
|
||||
if (width <= 0 || height <= 0)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] Skipping invalid bounds: {bounds}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_isRegisteredWithHost)
|
||||
{
|
||||
hostService.HostWindow.AddWebView(_platformWebView.Widget, x, y, width, height);
|
||||
_isRegisteredWithHost = true;
|
||||
Console.WriteLine($"[GtkWebViewHandler] Registered WebView at ({x}, {y}) size {width}x{height}");
|
||||
}
|
||||
else if (bounds != _lastBounds)
|
||||
{
|
||||
hostService.HostWindow.MoveResizeWebView(_platformWebView.Widget, x, y, width, height);
|
||||
Console.WriteLine($"[GtkWebViewHandler] Updated WebView to ({x}, {y}) size {width}x{height}");
|
||||
}
|
||||
|
||||
_lastBounds = bounds;
|
||||
}
|
||||
|
||||
private void UnregisterFromHost()
|
||||
{
|
||||
if (_isRegisteredWithHost && _platformWebView != null)
|
||||
{
|
||||
var hostService = GtkHostService.Instance;
|
||||
if (hostService.HostWindow != null)
|
||||
{
|
||||
hostService.HostWindow.RemoveWebView(_platformWebView.Widget);
|
||||
Console.WriteLine("[GtkWebViewHandler] Unregistered WebView from host");
|
||||
}
|
||||
_isRegisteredWithHost = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapSource(GtkWebViewHandler handler, IWebView webView)
|
||||
{
|
||||
if (handler._platformWebView == null)
|
||||
return;
|
||||
|
||||
var source = webView.Source;
|
||||
Console.WriteLine($"[GtkWebViewHandler] MapSource: {source?.GetType().Name ?? "null"}");
|
||||
|
||||
if (source is UrlWebViewSource urlSource)
|
||||
{
|
||||
var url = urlSource.Url;
|
||||
if (!string.IsNullOrEmpty(url))
|
||||
{
|
||||
handler._platformWebView.Navigate(url);
|
||||
}
|
||||
}
|
||||
else if (source is HtmlWebViewSource htmlSource)
|
||||
{
|
||||
var html = htmlSource.Html;
|
||||
if (!string.IsNullOrEmpty(html))
|
||||
{
|
||||
handler._platformWebView.LoadHtml(html, htmlSource.BaseUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapGoBack(GtkWebViewHandler handler, IWebView webView, object? args)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] MapGoBack called, CanGoBack={handler._platformWebView?.CanGoBack()}");
|
||||
handler._platformWebView?.GoBack();
|
||||
}
|
||||
|
||||
public static void MapGoForward(GtkWebViewHandler handler, IWebView webView, object? args)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewHandler] MapGoForward called, CanGoForward={handler._platformWebView?.CanGoForward()}");
|
||||
handler._platformWebView?.GoForward();
|
||||
}
|
||||
|
||||
public static void MapReload(GtkWebViewHandler handler, IWebView webView, object? args)
|
||||
{
|
||||
Console.WriteLine("[GtkWebViewHandler] MapReload called");
|
||||
handler._platformWebView?.Reload();
|
||||
}
|
||||
}
|
||||
60
Handlers/GtkWebViewManager.cs
Normal file
60
Handlers/GtkWebViewManager.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Maui.Platform.Linux.Window;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Manages WebView instances within the GTK host window.
|
||||
/// Handles creation, layout updates, and cleanup of WebKit-based web views.
|
||||
/// </summary>
|
||||
public sealed class GtkWebViewManager
|
||||
{
|
||||
private readonly GtkHostWindow _host;
|
||||
private readonly Dictionary<object, GtkWebViewPlatformView> _webViews = new();
|
||||
|
||||
public GtkWebViewManager(GtkHostWindow host)
|
||||
{
|
||||
_host = host;
|
||||
}
|
||||
|
||||
public GtkWebViewPlatformView CreateWebView(object key, int x, int y, int width, int height)
|
||||
{
|
||||
var webView = new GtkWebViewPlatformView();
|
||||
_webViews[key] = webView;
|
||||
_host.AddWebView(webView.Widget, x, y, width, height);
|
||||
return webView;
|
||||
}
|
||||
|
||||
public void UpdateLayout(object key, int x, int y, int width, int height)
|
||||
{
|
||||
if (_webViews.TryGetValue(key, out var webView))
|
||||
{
|
||||
_host.MoveResizeWebView(webView.Widget, x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
public GtkWebViewPlatformView? GetWebView(object key)
|
||||
{
|
||||
return _webViews.TryGetValue(key, out var webView) ? webView : null;
|
||||
}
|
||||
|
||||
public void RemoveWebView(object key)
|
||||
{
|
||||
if (_webViews.TryGetValue(key, out var webView))
|
||||
{
|
||||
_host.RemoveWebView(webView.Widget);
|
||||
webView.Dispose();
|
||||
_webViews.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var kvp in _webViews)
|
||||
{
|
||||
_host.RemoveWebView(kvp.Value.Widget);
|
||||
kvp.Value.Dispose();
|
||||
}
|
||||
_webViews.Clear();
|
||||
}
|
||||
}
|
||||
546
Handlers/GtkWebViewPlatformView.cs
Normal file
546
Handlers/GtkWebViewPlatformView.cs
Normal file
@@ -0,0 +1,546 @@
|
||||
using System;
|
||||
using Microsoft.Maui.Platform.Linux.Native;
|
||||
using Microsoft.Maui.Platform.Linux.Services;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Type of JavaScript dialog.
|
||||
/// </summary>
|
||||
public enum ScriptDialogType
|
||||
{
|
||||
Alert = 0,
|
||||
Confirm = 1,
|
||||
Prompt = 2,
|
||||
BeforeUnloadConfirm = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GTK-based WebView platform view using WebKitGTK.
|
||||
/// Provides web browsing capabilities within MAUI applications.
|
||||
/// </summary>
|
||||
public sealed class GtkWebViewPlatformView : IDisposable
|
||||
{
|
||||
private IntPtr _widget;
|
||||
private bool _disposed;
|
||||
private string? _currentUri;
|
||||
private ulong _loadChangedSignalId;
|
||||
private ulong _scriptDialogSignalId;
|
||||
private WebKitNative.LoadChangedCallback? _loadChangedCallback;
|
||||
private WebKitNative.ScriptDialogCallback? _scriptDialogCallback;
|
||||
private EventHandler<Microsoft.Maui.Controls.AppThemeChangedEventArgs>? _themeChangedHandler;
|
||||
|
||||
public IntPtr Widget => _widget;
|
||||
public string? CurrentUri => _currentUri;
|
||||
|
||||
public event EventHandler<string>? NavigationStarted;
|
||||
public event EventHandler<(string Url, bool Success)>? NavigationCompleted;
|
||||
public event EventHandler<string>? TitleChanged;
|
||||
public event EventHandler<(ScriptDialogType Type, string Message, Action<bool> Callback)>? ScriptDialogRequested;
|
||||
|
||||
public GtkWebViewPlatformView()
|
||||
{
|
||||
if (!WebKitNative.Initialize())
|
||||
{
|
||||
throw new InvalidOperationException("Failed to initialize WebKitGTK. Is libwebkit2gtk-4.x installed?");
|
||||
}
|
||||
_widget = WebKitNative.WebViewNew();
|
||||
if (_widget == IntPtr.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create WebKitWebView widget");
|
||||
}
|
||||
WebKitNative.ConfigureSettings(_widget);
|
||||
_loadChangedCallback = OnLoadChanged;
|
||||
_loadChangedSignalId = WebKitNative.ConnectLoadChanged(_widget, _loadChangedCallback);
|
||||
|
||||
// Connect to script-dialog signal to intercept JavaScript alerts/confirms/prompts
|
||||
_scriptDialogCallback = OnScriptDialog;
|
||||
_scriptDialogSignalId = WebKitNative.ConnectScriptDialog(_widget, _scriptDialogCallback);
|
||||
|
||||
// Set initial background color based on theme
|
||||
UpdateBackgroundForTheme();
|
||||
|
||||
// Subscribe to theme changes to update background color
|
||||
_themeChangedHandler = (sender, args) =>
|
||||
{
|
||||
GLibNative.IdleAdd(() =>
|
||||
{
|
||||
UpdateBackgroundForTheme();
|
||||
return false;
|
||||
});
|
||||
};
|
||||
if (Microsoft.Maui.Controls.Application.Current != null)
|
||||
{
|
||||
Microsoft.Maui.Controls.Application.Current.RequestedThemeChanged += _themeChangedHandler;
|
||||
}
|
||||
|
||||
Console.WriteLine("[GtkWebViewPlatformView] Created WebKitWebView widget");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the WebView background color based on the current app theme.
|
||||
/// </summary>
|
||||
public void UpdateBackgroundForTheme()
|
||||
{
|
||||
if (_widget == IntPtr.Zero) return;
|
||||
|
||||
var isDark = Microsoft.Maui.Controls.Application.Current?.RequestedTheme == Microsoft.Maui.ApplicationModel.AppTheme.Dark;
|
||||
if (isDark)
|
||||
{
|
||||
// Dark theme: use a dark gray background
|
||||
WebKitNative.SetBackgroundColor(_widget, 0.12, 0.12, 0.12, 1.0); // #1E1E1E
|
||||
}
|
||||
else
|
||||
{
|
||||
// Light theme: use white background
|
||||
WebKitNative.SetBackgroundColor(_widget, 1.0, 1.0, 1.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
private bool OnScriptDialog(IntPtr webView, IntPtr dialog, IntPtr userData)
|
||||
{
|
||||
try
|
||||
{
|
||||
var webkitDialogType = WebKitNative.GetScriptDialogType(dialog);
|
||||
var dialogType = (ScriptDialogType)(int)webkitDialogType;
|
||||
var message = WebKitNative.GetScriptDialogMessage(dialog) ?? "";
|
||||
|
||||
Console.WriteLine($"[GtkWebViewPlatformView] Script dialog: type={dialogType}, message={message}");
|
||||
|
||||
// Get the parent window for proper modal behavior
|
||||
IntPtr parentWindow = GtkHostService.Instance.HostWindow?.Window ?? IntPtr.Zero;
|
||||
|
||||
// Handle prompt dialogs specially - they need a text entry
|
||||
if (dialogType == ScriptDialogType.Prompt)
|
||||
{
|
||||
return HandlePromptDialog(dialog, message, parentWindow);
|
||||
}
|
||||
|
||||
// Determine dialog type and buttons based on JavaScript dialog type
|
||||
int messageType = GtkNative.GTK_MESSAGE_INFO;
|
||||
int buttons = GtkNative.GTK_BUTTONS_OK;
|
||||
|
||||
switch (dialogType)
|
||||
{
|
||||
case ScriptDialogType.Alert:
|
||||
messageType = GtkNative.GTK_MESSAGE_INFO;
|
||||
buttons = GtkNative.GTK_BUTTONS_OK;
|
||||
break;
|
||||
case ScriptDialogType.Confirm:
|
||||
case ScriptDialogType.BeforeUnloadConfirm:
|
||||
messageType = GtkNative.GTK_MESSAGE_QUESTION;
|
||||
buttons = GtkNative.GTK_BUTTONS_OK_CANCEL;
|
||||
break;
|
||||
}
|
||||
|
||||
// Create and show native GTK message dialog
|
||||
IntPtr gtkDialog = GtkNative.gtk_message_dialog_new(
|
||||
parentWindow,
|
||||
GtkNative.GTK_DIALOG_MODAL | GtkNative.GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
messageType,
|
||||
buttons,
|
||||
message,
|
||||
IntPtr.Zero);
|
||||
|
||||
if (gtkDialog != IntPtr.Zero)
|
||||
{
|
||||
// Set dialog title based on type
|
||||
string title = dialogType switch
|
||||
{
|
||||
ScriptDialogType.Alert => "Alert",
|
||||
ScriptDialogType.Confirm => "Confirm",
|
||||
ScriptDialogType.BeforeUnloadConfirm => "Leave Page?",
|
||||
_ => "Message"
|
||||
};
|
||||
GtkNative.gtk_window_set_title(gtkDialog, title);
|
||||
|
||||
// Apply theme-aware CSS styling based on app's current theme
|
||||
ApplyDialogTheme(gtkDialog);
|
||||
|
||||
// Make dialog modal to parent if we have a parent
|
||||
if (parentWindow != IntPtr.Zero)
|
||||
{
|
||||
GtkNative.gtk_window_set_transient_for(gtkDialog, parentWindow);
|
||||
GtkNative.gtk_window_set_modal(gtkDialog, true);
|
||||
}
|
||||
|
||||
// Run the dialog synchronously - this blocks until user responds
|
||||
int response = GtkNative.gtk_dialog_run(gtkDialog);
|
||||
Console.WriteLine($"[GtkWebViewPlatformView] Dialog response: {response}");
|
||||
|
||||
// Set the confirmed state for confirm dialogs
|
||||
if (dialogType == ScriptDialogType.Confirm || dialogType == ScriptDialogType.BeforeUnloadConfirm)
|
||||
{
|
||||
bool confirmed = response == GtkNative.GTK_RESPONSE_OK || response == GtkNative.GTK_RESPONSE_YES;
|
||||
WebKitNative.SetScriptDialogConfirmed(dialog, confirmed);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
GtkNative.gtk_widget_destroy(gtkDialog);
|
||||
}
|
||||
|
||||
// Return true to indicate we handled the dialog (prevents WebKitGTK's default)
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewPlatformView] Error in OnScriptDialog: {ex.Message}");
|
||||
// Return false on error to let WebKitGTK try its default handling
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool HandlePromptDialog(IntPtr webkitDialog, string message, IntPtr parentWindow)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get the default text for the prompt
|
||||
string? defaultText = WebKitNative.GetScriptDialogPromptDefaultText(webkitDialog) ?? "";
|
||||
|
||||
// Create a custom dialog with OK/Cancel buttons
|
||||
IntPtr gtkDialog = GtkNative.gtk_dialog_new_with_buttons(
|
||||
"Prompt",
|
||||
parentWindow,
|
||||
GtkNative.GTK_DIALOG_MODAL | GtkNative.GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
"_Cancel",
|
||||
GtkNative.GTK_RESPONSE_CANCEL,
|
||||
"_OK",
|
||||
GtkNative.GTK_RESPONSE_OK,
|
||||
IntPtr.Zero);
|
||||
|
||||
if (gtkDialog == IntPtr.Zero)
|
||||
{
|
||||
Console.WriteLine("[GtkWebViewPlatformView] Failed to create prompt dialog");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Apply theme-aware CSS styling
|
||||
ApplyDialogTheme(gtkDialog);
|
||||
|
||||
// Get the content area
|
||||
IntPtr contentArea = GtkNative.gtk_dialog_get_content_area(gtkDialog);
|
||||
|
||||
// Create a vertical box for the content
|
||||
IntPtr vbox = GtkNative.gtk_box_new(GtkNative.GTK_ORIENTATION_VERTICAL, 10);
|
||||
GtkNative.gtk_widget_set_margin_start(vbox, 12);
|
||||
GtkNative.gtk_widget_set_margin_end(vbox, 12);
|
||||
GtkNative.gtk_widget_set_margin_top(vbox, 12);
|
||||
GtkNative.gtk_widget_set_margin_bottom(vbox, 12);
|
||||
|
||||
// Add the message label
|
||||
IntPtr label = GtkNative.gtk_label_new(message);
|
||||
GtkNative.gtk_box_pack_start(vbox, label, false, false, 0);
|
||||
|
||||
// Add the text entry
|
||||
IntPtr entry = GtkNative.gtk_entry_new();
|
||||
GtkNative.gtk_entry_set_text(entry, defaultText);
|
||||
GtkNative.gtk_box_pack_start(vbox, entry, false, false, 0);
|
||||
|
||||
// Add the vbox to content area
|
||||
GtkNative.gtk_box_pack_start(contentArea, vbox, true, true, 0);
|
||||
|
||||
// Make dialog modal
|
||||
if (parentWindow != IntPtr.Zero)
|
||||
{
|
||||
GtkNative.gtk_window_set_transient_for(gtkDialog, parentWindow);
|
||||
GtkNative.gtk_window_set_modal(gtkDialog, true);
|
||||
}
|
||||
|
||||
// Show all widgets
|
||||
GtkNative.gtk_widget_show_all(gtkDialog);
|
||||
|
||||
// Run the dialog
|
||||
int response = GtkNative.gtk_dialog_run(gtkDialog);
|
||||
Console.WriteLine($"[GtkWebViewPlatformView] Prompt dialog response: {response}");
|
||||
|
||||
if (response == GtkNative.GTK_RESPONSE_OK)
|
||||
{
|
||||
// Get the text from the entry
|
||||
IntPtr textPtr = GtkNative.gtk_entry_get_text(entry);
|
||||
string? enteredText = textPtr != IntPtr.Zero
|
||||
? System.Runtime.InteropServices.Marshal.PtrToStringUTF8(textPtr)
|
||||
: "";
|
||||
|
||||
Console.WriteLine($"[GtkWebViewPlatformView] Prompt text: {enteredText}");
|
||||
|
||||
// Set the prompt response
|
||||
WebKitNative.SetScriptDialogPromptText(webkitDialog, enteredText ?? "");
|
||||
}
|
||||
else
|
||||
{
|
||||
// User cancelled - for prompts, not confirming means returning null
|
||||
// WebKit handles this by not calling prompt_set_text
|
||||
}
|
||||
|
||||
// Clean up
|
||||
GtkNative.gtk_widget_destroy(gtkDialog);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewPlatformView] Error in HandlePromptDialog: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies theme-aware CSS styling to a GTK dialog based on the app's current theme.
|
||||
/// </summary>
|
||||
private void ApplyDialogTheme(IntPtr gtkDialog)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check the app's current theme (not the system theme)
|
||||
bool isDark = Microsoft.Maui.Controls.Application.Current?.UserAppTheme == Microsoft.Maui.ApplicationModel.AppTheme.Dark;
|
||||
|
||||
// If UserAppTheme is Unspecified, fall back to RequestedTheme
|
||||
if (Microsoft.Maui.Controls.Application.Current?.UserAppTheme == Microsoft.Maui.ApplicationModel.AppTheme.Unspecified)
|
||||
{
|
||||
isDark = Microsoft.Maui.Controls.Application.Current?.RequestedTheme == Microsoft.Maui.ApplicationModel.AppTheme.Dark;
|
||||
}
|
||||
|
||||
Console.WriteLine($"[GtkWebViewPlatformView] ApplyDialogTheme: isDark={isDark}, UserAppTheme={Microsoft.Maui.Controls.Application.Current?.UserAppTheme}");
|
||||
|
||||
// Create comprehensive CSS based on the theme - targeting all dialog elements
|
||||
string css = isDark
|
||||
? @"
|
||||
* {
|
||||
background-color: #303030;
|
||||
color: #E0E0E0;
|
||||
}
|
||||
window, dialog, messagedialog, .background {
|
||||
background-color: #303030;
|
||||
color: #E0E0E0;
|
||||
}
|
||||
headerbar, headerbar *, .titlebar, .titlebar * {
|
||||
background-color: #252525;
|
||||
background-image: none;
|
||||
color: #E0E0E0;
|
||||
border-color: #404040;
|
||||
box-shadow: none;
|
||||
}
|
||||
headerbar button, .titlebar button {
|
||||
background-color: #353535;
|
||||
background-image: none;
|
||||
color: #E0E0E0;
|
||||
}
|
||||
.dialog-action-area, .dialog-action-box, actionbar {
|
||||
background-color: #303030;
|
||||
}
|
||||
label, .message-dialog-message, .message-dialog-secondary-message {
|
||||
color: #E0E0E0;
|
||||
}
|
||||
button {
|
||||
background-image: none;
|
||||
background-color: #505050;
|
||||
color: #E0E0E0;
|
||||
border-color: #606060;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #606060;
|
||||
}
|
||||
entry {
|
||||
background-color: #404040;
|
||||
color: #E0E0E0;
|
||||
}
|
||||
"
|
||||
: @"
|
||||
* {
|
||||
background-color: #FFFFFF;
|
||||
color: #212121;
|
||||
}
|
||||
window, dialog, messagedialog, .background {
|
||||
background-color: #FFFFFF;
|
||||
color: #212121;
|
||||
}
|
||||
headerbar, headerbar *, .titlebar, .titlebar * {
|
||||
background-color: #F5F5F5;
|
||||
background-image: none;
|
||||
color: #212121;
|
||||
border-color: #E0E0E0;
|
||||
box-shadow: none;
|
||||
}
|
||||
headerbar button, .titlebar button {
|
||||
background-color: #EBEBEB;
|
||||
background-image: none;
|
||||
color: #212121;
|
||||
}
|
||||
.dialog-action-area, .dialog-action-box, actionbar {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
label, .message-dialog-message, .message-dialog-secondary-message {
|
||||
color: #212121;
|
||||
}
|
||||
button {
|
||||
background-image: none;
|
||||
background-color: #F5F5F5;
|
||||
color: #212121;
|
||||
border-color: #E0E0E0;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #E0E0E0;
|
||||
}
|
||||
entry {
|
||||
background-color: #FFFFFF;
|
||||
color: #212121;
|
||||
}
|
||||
";
|
||||
|
||||
// Create CSS provider and apply to the screen so all child widgets inherit it
|
||||
IntPtr cssProvider = GtkNative.gtk_css_provider_new();
|
||||
if (cssProvider != IntPtr.Zero)
|
||||
{
|
||||
GtkNative.gtk_css_provider_load_from_data(cssProvider, css, -1, IntPtr.Zero);
|
||||
|
||||
// Get the screen from the dialog and apply CSS to entire screen for this dialog
|
||||
IntPtr screen = GtkNative.gtk_widget_get_screen(gtkDialog);
|
||||
if (screen == IntPtr.Zero)
|
||||
{
|
||||
screen = GtkNative.gdk_screen_get_default();
|
||||
}
|
||||
|
||||
if (screen != IntPtr.Zero)
|
||||
{
|
||||
GtkNative.gtk_style_context_add_provider_for_screen(screen, cssProvider, GtkNative.GTK_STYLE_PROVIDER_PRIORITY_USER);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[GtkWebViewPlatformView] Error applying dialog theme: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLoadChanged(IntPtr webView, int loadEvent, IntPtr userData)
|
||||
{
|
||||
try
|
||||
{
|
||||
string uri = WebKitNative.GetUri(webView) ?? _currentUri ?? "";
|
||||
switch ((WebKitNative.WebKitLoadEvent)loadEvent)
|
||||
{
|
||||
case WebKitNative.WebKitLoadEvent.Started:
|
||||
Console.WriteLine("[GtkWebViewPlatformView] Load started: " + uri);
|
||||
NavigationStarted?.Invoke(this, uri);
|
||||
break;
|
||||
case WebKitNative.WebKitLoadEvent.Finished:
|
||||
_currentUri = uri;
|
||||
Console.WriteLine("[GtkWebViewPlatformView] Load finished: " + uri);
|
||||
NavigationCompleted?.Invoke(this, (uri, true));
|
||||
break;
|
||||
case WebKitNative.WebKitLoadEvent.Committed:
|
||||
_currentUri = uri;
|
||||
Console.WriteLine("[GtkWebViewPlatformView] Load committed: " + uri);
|
||||
break;
|
||||
case WebKitNative.WebKitLoadEvent.Redirected:
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("[GtkWebViewPlatformView] Error in OnLoadChanged: " + ex.Message);
|
||||
Console.WriteLine("[GtkWebViewPlatformView] Stack trace: " + ex.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
public void Navigate(string uri)
|
||||
{
|
||||
if (_widget != IntPtr.Zero)
|
||||
{
|
||||
WebKitNative.LoadUri(_widget, uri);
|
||||
Console.WriteLine("[GtkWebViewPlatformView] Navigate to: " + uri);
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadHtml(string html, string? baseUri = null)
|
||||
{
|
||||
if (_widget != IntPtr.Zero)
|
||||
{
|
||||
WebKitNative.LoadHtml(_widget, html, baseUri);
|
||||
Console.WriteLine("[GtkWebViewPlatformView] Load HTML content");
|
||||
}
|
||||
}
|
||||
|
||||
public void GoBack()
|
||||
{
|
||||
if (_widget != IntPtr.Zero)
|
||||
{
|
||||
WebKitNative.GoBack(_widget);
|
||||
}
|
||||
}
|
||||
|
||||
public void GoForward()
|
||||
{
|
||||
if (_widget != IntPtr.Zero)
|
||||
{
|
||||
WebKitNative.GoForward(_widget);
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanGoBack()
|
||||
{
|
||||
return _widget != IntPtr.Zero && WebKitNative.CanGoBack(_widget);
|
||||
}
|
||||
|
||||
public bool CanGoForward()
|
||||
{
|
||||
return _widget != IntPtr.Zero && WebKitNative.CanGoForward(_widget);
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
if (_widget != IntPtr.Zero)
|
||||
{
|
||||
WebKitNative.Reload(_widget);
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (_widget != IntPtr.Zero)
|
||||
{
|
||||
WebKitNative.StopLoading(_widget);
|
||||
}
|
||||
}
|
||||
|
||||
public string? GetTitle()
|
||||
{
|
||||
return _widget == IntPtr.Zero ? null : WebKitNative.GetTitle(_widget);
|
||||
}
|
||||
|
||||
public string? GetUri()
|
||||
{
|
||||
return _widget == IntPtr.Zero ? null : WebKitNative.GetUri(_widget);
|
||||
}
|
||||
|
||||
public void SetJavascriptEnabled(bool enabled)
|
||||
{
|
||||
if (_widget != IntPtr.Zero)
|
||||
{
|
||||
WebKitNative.SetJavascriptEnabled(_widget, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
// Unsubscribe from theme changes
|
||||
if (_themeChangedHandler != null && Microsoft.Maui.Controls.Application.Current != null)
|
||||
{
|
||||
Microsoft.Maui.Controls.Application.Current.RequestedThemeChanged -= _themeChangedHandler;
|
||||
_themeChangedHandler = null;
|
||||
}
|
||||
|
||||
if (_widget != IntPtr.Zero)
|
||||
{
|
||||
WebKitNative.DisconnectLoadChanged(_widget);
|
||||
WebKitNative.DisconnectScriptDialog(_widget);
|
||||
}
|
||||
_widget = IntPtr.Zero;
|
||||
_loadChangedCallback = null;
|
||||
_scriptDialogCallback = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
71
Handlers/GtkWebViewProxy.cs
Normal file
71
Handlers/GtkWebViewProxy.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Platform;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Proxy view that bridges SkiaView layout to GTK WebView positioning.
|
||||
/// </summary>
|
||||
public class GtkWebViewProxy : SkiaView
|
||||
{
|
||||
private readonly GtkWebViewHandler _handler;
|
||||
private readonly GtkWebViewPlatformView _platformView;
|
||||
|
||||
public GtkWebViewPlatformView PlatformView => _platformView;
|
||||
public bool CanGoBack => _platformView.CanGoBack();
|
||||
public bool CanGoForward => _platformView.CanGoForward();
|
||||
|
||||
public GtkWebViewProxy(GtkWebViewHandler handler, GtkWebViewPlatformView platformView)
|
||||
{
|
||||
_handler = handler;
|
||||
_platformView = platformView;
|
||||
}
|
||||
|
||||
public override void Arrange(Rect bounds)
|
||||
{
|
||||
base.Arrange(bounds);
|
||||
// Bounds are already in absolute window coordinates - use them directly
|
||||
// The Skia layout system uses absolute coordinates throughout
|
||||
_handler.RegisterWithHost(new SKRect((float)Bounds.Left, (float)Bounds.Top, (float)Bounds.Right, (float)Bounds.Bottom));
|
||||
}
|
||||
|
||||
public override void Draw(SKCanvas canvas)
|
||||
{
|
||||
// Draw transparent placeholder - actual WebView is rendered by GTK
|
||||
using var paint = new SKPaint
|
||||
{
|
||||
Color = new SKColor(0, 0, 0, 0),
|
||||
Style = SKPaintStyle.Fill
|
||||
};
|
||||
canvas.DrawRect(new SKRect((float)Bounds.Left, (float)Bounds.Top, (float)Bounds.Right, (float)Bounds.Bottom), paint);
|
||||
}
|
||||
|
||||
public void Navigate(string url)
|
||||
{
|
||||
_platformView.Navigate(url);
|
||||
}
|
||||
|
||||
public void LoadHtml(string html, string? baseUrl = null)
|
||||
{
|
||||
_platformView.LoadHtml(html, baseUrl);
|
||||
}
|
||||
|
||||
public void GoBack()
|
||||
{
|
||||
_platformView.GoBack();
|
||||
}
|
||||
|
||||
public void GoForward()
|
||||
{
|
||||
_platformView.GoForward();
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
_platformView.Reload();
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,11 @@ public partial class ImageButtonHandler : ViewHandler<IImageButton, SkiaImageBut
|
||||
[nameof(IButtonStroke.CornerRadius)] = MapCornerRadius,
|
||||
[nameof(IPadding.Padding)] = MapPadding,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
[nameof(IView.Width)] = MapWidth,
|
||||
[nameof(IView.Height)] = MapHeight,
|
||||
["VerticalOptions"] = MapVerticalOptions,
|
||||
["HorizontalOptions"] = MapHorizontalOptions,
|
||||
};
|
||||
|
||||
public static CommandMapper<IImageButton, ImageButtonHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -118,13 +123,13 @@ public partial class ImageButtonHandler : ViewHandler<IImageButton, SkiaImageBut
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (imageButton.StrokeColor is not null)
|
||||
handler.PlatformView.StrokeColor = imageButton.StrokeColor.ToSKColor();
|
||||
handler.PlatformView.StrokeColor = imageButton.StrokeColor;
|
||||
}
|
||||
|
||||
public static void MapStrokeThickness(ImageButtonHandler handler, IImageButton imageButton)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.StrokeThickness = (float)imageButton.StrokeThickness;
|
||||
handler.PlatformView.StrokeThickness = imageButton.StrokeThickness;
|
||||
}
|
||||
|
||||
public static void MapCornerRadius(ImageButtonHandler handler, IImageButton imageButton)
|
||||
@@ -136,12 +141,7 @@ public partial class ImageButtonHandler : ViewHandler<IImageButton, SkiaImageBut
|
||||
public static void MapPadding(ImageButtonHandler handler, IImageButton imageButton)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
var padding = imageButton.Padding;
|
||||
handler.PlatformView.PaddingLeft = (float)padding.Left;
|
||||
handler.PlatformView.PaddingTop = (float)padding.Top;
|
||||
handler.PlatformView.PaddingRight = (float)padding.Right;
|
||||
handler.PlatformView.PaddingBottom = (float)padding.Bottom;
|
||||
handler.PlatformView.Padding = imageButton.Padding;
|
||||
}
|
||||
|
||||
public static void MapBackground(ImageButtonHandler handler, IImageButton imageButton)
|
||||
@@ -150,7 +150,59 @@ public partial class ImageButtonHandler : ViewHandler<IImageButton, SkiaImageBut
|
||||
|
||||
if (imageButton.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.ImageBackgroundColor = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(ImageButtonHandler handler, IImageButton imageButton)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (imageButton is Microsoft.Maui.Controls.ImageButton imgBtn && imgBtn.BackgroundColor is not null)
|
||||
{
|
||||
handler.PlatformView.ImageBackgroundColor = imgBtn.BackgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapWidth(ImageButtonHandler handler, IImageButton imageButton)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
// Map WidthRequest from the MAUI ImageButton to the platform view
|
||||
if (imageButton is Microsoft.Maui.Controls.ImageButton imgBtn && imgBtn.WidthRequest > 0)
|
||||
{
|
||||
handler.PlatformView.WidthRequest = imgBtn.WidthRequest;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapHeight(ImageButtonHandler handler, IImageButton imageButton)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
// Map HeightRequest from the MAUI ImageButton to the platform view
|
||||
if (imageButton is Microsoft.Maui.Controls.ImageButton imgBtn && imgBtn.HeightRequest > 0)
|
||||
{
|
||||
handler.PlatformView.HeightRequest = imgBtn.HeightRequest;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapVerticalOptions(ImageButtonHandler handler, IImageButton imageButton)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (imageButton is Microsoft.Maui.Controls.ImageButton imgBtn)
|
||||
{
|
||||
handler.PlatformView.VerticalOptions = imgBtn.VerticalOptions;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapHorizontalOptions(ImageButtonHandler handler, IImageButton imageButton)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (imageButton is Microsoft.Maui.Controls.ImageButton imgBtn)
|
||||
{
|
||||
handler.PlatformView.HorizontalOptions = imgBtn.HorizontalOptions;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.IO;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using SkiaSharp;
|
||||
@@ -20,6 +22,10 @@ public partial class ImageHandler : ViewHandler<IImage, SkiaImage>
|
||||
[nameof(IImage.IsOpaque)] = MapIsOpaque,
|
||||
[nameof(IImageSourcePart.Source)] = MapSource,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["Width"] = MapWidth,
|
||||
["Height"] = MapHeight,
|
||||
["HorizontalOptions"] = MapHorizontalOptions,
|
||||
["VerticalOptions"] = MapVerticalOptions,
|
||||
};
|
||||
|
||||
public static CommandMapper<IImage, ImageHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -88,6 +94,19 @@ public partial class ImageHandler : ViewHandler<IImage, SkiaImage>
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
// Extract width/height requests from Image control
|
||||
if (image is Image img)
|
||||
{
|
||||
if (img.WidthRequest > 0)
|
||||
{
|
||||
handler.PlatformView.WidthRequest = img.WidthRequest;
|
||||
}
|
||||
if (img.HeightRequest > 0)
|
||||
{
|
||||
handler.PlatformView.HeightRequest = img.HeightRequest;
|
||||
}
|
||||
}
|
||||
|
||||
handler.SourceLoader.UpdateImageSourceAsync();
|
||||
}
|
||||
|
||||
@@ -97,7 +116,57 @@ public partial class ImageHandler : ViewHandler<IImage, SkiaImage>
|
||||
|
||||
if (image.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.ImageBackgroundColor = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapWidth(ImageHandler handler, IImage image)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (image is Image img && img.WidthRequest > 0)
|
||||
{
|
||||
handler.PlatformView.WidthRequest = img.WidthRequest;
|
||||
Console.WriteLine($"[ImageHandler] MapWidth: {img.WidthRequest}");
|
||||
}
|
||||
else if (image.Width > 0)
|
||||
{
|
||||
handler.PlatformView.WidthRequest = image.Width;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapHeight(ImageHandler handler, IImage image)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (image is Image img && img.HeightRequest > 0)
|
||||
{
|
||||
handler.PlatformView.HeightRequest = img.HeightRequest;
|
||||
Console.WriteLine($"[ImageHandler] MapHeight: {img.HeightRequest}");
|
||||
}
|
||||
else if (image.Height > 0)
|
||||
{
|
||||
handler.PlatformView.HeightRequest = image.Height;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapHorizontalOptions(ImageHandler handler, IImage image)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (image is Image img)
|
||||
{
|
||||
handler.PlatformView.HorizontalOptions = img.HorizontalOptions;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapVerticalOptions(ImageHandler handler, IImage image)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (image is Image img)
|
||||
{
|
||||
handler.PlatformView.VerticalOptions = img.VerticalOptions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +231,14 @@ public partial class ImageHandler : ViewHandler<IImage, SkiaImage>
|
||||
await _handler.PlatformView!.LoadFromStreamAsync(stream);
|
||||
}
|
||||
}
|
||||
else if (source is FontImageSource fontSource)
|
||||
{
|
||||
var bitmap = RenderFontImageSource(fontSource, _handler.PlatformView!.WidthRequest, _handler.PlatformView.HeightRequest);
|
||||
if (bitmap != null)
|
||||
{
|
||||
_handler.PlatformView.LoadFromBitmap(bitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -176,5 +253,73 @@ public partial class ImageHandler : ViewHandler<IImage, SkiaImage>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static SKBitmap? RenderFontImageSource(FontImageSource fontSource, double requestedWidth, double requestedHeight)
|
||||
{
|
||||
string glyph = fontSource.Glyph;
|
||||
if (string.IsNullOrEmpty(glyph))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int size = (int)Math.Max(requestedWidth > 0 ? requestedWidth : 24.0, requestedHeight > 0 ? requestedHeight : 24.0);
|
||||
size = Math.Max(size, 16);
|
||||
|
||||
SKColor color = fontSource.Color?.ToSKColor() ?? SKColors.Black;
|
||||
SKBitmap bitmap = new SKBitmap(size, size, false);
|
||||
using SKCanvas canvas = new SKCanvas(bitmap);
|
||||
canvas.Clear(SKColors.Transparent);
|
||||
|
||||
SKTypeface? typeface = null;
|
||||
if (!string.IsNullOrEmpty(fontSource.FontFamily))
|
||||
{
|
||||
string[] fontPaths = new string[]
|
||||
{
|
||||
"/usr/share/fonts/truetype/" + fontSource.FontFamily + ".ttf",
|
||||
"/usr/share/fonts/opentype/" + fontSource.FontFamily + ".otf",
|
||||
"/usr/local/share/fonts/" + fontSource.FontFamily + ".ttf",
|
||||
Path.Combine(AppContext.BaseDirectory, fontSource.FontFamily + ".ttf")
|
||||
};
|
||||
|
||||
foreach (string path in fontPaths)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
typeface = SKTypeface.FromFile(path, 0);
|
||||
if (typeface != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeface == null)
|
||||
{
|
||||
typeface = SKTypeface.FromFamilyName(fontSource.FontFamily);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeface == null)
|
||||
{
|
||||
typeface = SKTypeface.Default;
|
||||
}
|
||||
|
||||
float fontSize = size * 0.8f;
|
||||
using SKFont font = new SKFont(typeface, fontSize, 1f, 0f);
|
||||
using SKPaint paint = new SKPaint(font)
|
||||
{
|
||||
Color = color,
|
||||
IsAntialias = true,
|
||||
TextAlign = SKTextAlign.Center
|
||||
};
|
||||
|
||||
SKRect bounds = default;
|
||||
paint.MeasureText(glyph, ref bounds);
|
||||
float x = size / 2f;
|
||||
float y = (size - bounds.Top - bounds.Bottom) / 2f;
|
||||
canvas.DrawText(glyph, x, y, paint);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
157
Handlers/IndicatorViewHandler.cs
Normal file
157
Handlers/IndicatorViewHandler.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Platform;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for IndicatorView on Linux using Skia rendering.
|
||||
/// Maps IndicatorView to SkiaIndicatorView platform view.
|
||||
/// </summary>
|
||||
public partial class IndicatorViewHandler : ViewHandler<IndicatorView, SkiaIndicatorView>
|
||||
{
|
||||
private bool _isUpdatingPosition;
|
||||
|
||||
public static IPropertyMapper<IndicatorView, IndicatorViewHandler> Mapper =
|
||||
new PropertyMapper<IndicatorView, IndicatorViewHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(IndicatorView.Count)] = MapCount,
|
||||
[nameof(IndicatorView.Position)] = MapPosition,
|
||||
[nameof(IndicatorView.IndicatorColor)] = MapIndicatorColor,
|
||||
[nameof(IndicatorView.SelectedIndicatorColor)] = MapSelectedIndicatorColor,
|
||||
[nameof(IndicatorView.IndicatorSize)] = MapIndicatorSize,
|
||||
[nameof(IndicatorView.IndicatorsShape)] = MapIndicatorsShape,
|
||||
[nameof(IndicatorView.MaximumVisible)] = MapMaximumVisible,
|
||||
[nameof(IndicatorView.HideSingle)] = MapHideSingle,
|
||||
[nameof(IndicatorView.ItemsSource)] = MapItemsSource,
|
||||
};
|
||||
|
||||
public static CommandMapper<IndicatorView, IndicatorViewHandler> CommandMapper =
|
||||
new(ViewHandler.ViewCommandMapper)
|
||||
{
|
||||
};
|
||||
|
||||
public IndicatorViewHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public IndicatorViewHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaIndicatorView CreatePlatformView()
|
||||
{
|
||||
return new SkiaIndicatorView();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaIndicatorView platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
// SkiaIndicatorView doesn't have position changed event, but we can add one if needed
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaIndicatorView platformView)
|
||||
{
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
public static void MapCount(IndicatorViewHandler handler, IndicatorView indicatorView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.Count = indicatorView.Count;
|
||||
}
|
||||
|
||||
public static void MapPosition(IndicatorViewHandler handler, IndicatorView indicatorView)
|
||||
{
|
||||
if (handler.PlatformView is null || handler._isUpdatingPosition) return;
|
||||
|
||||
try
|
||||
{
|
||||
handler._isUpdatingPosition = true;
|
||||
handler.PlatformView.Position = indicatorView.Position;
|
||||
}
|
||||
finally
|
||||
{
|
||||
handler._isUpdatingPosition = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIndicatorColor(IndicatorViewHandler handler, IndicatorView indicatorView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (indicatorView.IndicatorColor is not null)
|
||||
{
|
||||
handler.PlatformView.IndicatorColor = indicatorView.IndicatorColor;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapSelectedIndicatorColor(IndicatorViewHandler handler, IndicatorView indicatorView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (indicatorView.SelectedIndicatorColor is not null)
|
||||
{
|
||||
handler.PlatformView.SelectedIndicatorColor = indicatorView.SelectedIndicatorColor;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIndicatorSize(IndicatorViewHandler handler, IndicatorView indicatorView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.IndicatorSize = (float)indicatorView.IndicatorSize;
|
||||
handler.PlatformView.SelectedIndicatorSize = (float)indicatorView.IndicatorSize;
|
||||
}
|
||||
|
||||
public static void MapIndicatorsShape(IndicatorViewHandler handler, IndicatorView indicatorView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
handler.PlatformView.IndicatorShape = indicatorView.IndicatorsShape switch
|
||||
{
|
||||
Controls.IndicatorShape.Circle => Platform.IndicatorShape.Circle,
|
||||
Controls.IndicatorShape.Square => Platform.IndicatorShape.Square,
|
||||
_ => Platform.IndicatorShape.Circle
|
||||
};
|
||||
}
|
||||
|
||||
public static void MapMaximumVisible(IndicatorViewHandler handler, IndicatorView indicatorView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.MaximumVisible = indicatorView.MaximumVisible;
|
||||
}
|
||||
|
||||
public static void MapHideSingle(IndicatorViewHandler handler, IndicatorView indicatorView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.HideSingle = indicatorView.HideSingle;
|
||||
}
|
||||
|
||||
public static void MapItemsSource(IndicatorViewHandler handler, IndicatorView indicatorView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
// Count items from ItemsSource
|
||||
int count = 0;
|
||||
if (indicatorView.ItemsSource is System.Collections.ICollection collection)
|
||||
{
|
||||
count = collection.Count;
|
||||
}
|
||||
else if (indicatorView.ItemsSource is System.Collections.IEnumerable enumerable)
|
||||
{
|
||||
foreach (var _ in enumerable)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
handler.PlatformView.Count = count;
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ public partial class ItemsViewHandler<TItemsView> : ViewHandler<TItemsView, Skia
|
||||
|
||||
if (itemsView.Background is SolidColorBrush solidBrush)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for Label control.
|
||||
/// </summary>
|
||||
public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps the property mapper for the handler.
|
||||
/// </summary>
|
||||
public static IPropertyMapper<ILabel, LabelHandler> Mapper = new PropertyMapper<ILabel, LabelHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(ILabel.Text)] = MapText,
|
||||
[nameof(ILabel.TextColor)] = MapTextColor,
|
||||
[nameof(ILabel.Font)] = MapFont,
|
||||
[nameof(ILabel.HorizontalTextAlignment)] = MapHorizontalTextAlignment,
|
||||
[nameof(ILabel.VerticalTextAlignment)] = MapVerticalTextAlignment,
|
||||
[nameof(ILabel.LineBreakMode)] = MapLineBreakMode,
|
||||
[nameof(ILabel.MaxLines)] = MapMaxLines,
|
||||
[nameof(ILabel.Padding)] = MapPadding,
|
||||
[nameof(ILabel.TextDecorations)] = MapTextDecorations,
|
||||
[nameof(ILabel.LineHeight)] = MapLineHeight,
|
||||
[nameof(ILabel.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Maps the command mapper for the handler.
|
||||
/// </summary>
|
||||
public static CommandMapper<ILabel, LabelHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
{
|
||||
};
|
||||
|
||||
public LabelHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public LabelHandler(IPropertyMapper? mapper)
|
||||
: base(mapper ?? Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public LabelHandler(IPropertyMapper? mapper, CommandMapper? commandMapper)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaLabel CreatePlatformView()
|
||||
{
|
||||
return new SkiaLabel();
|
||||
}
|
||||
|
||||
public static void MapText(LabelHandler handler, ILabel label)
|
||||
{
|
||||
handler.PlatformView.Text = label.Text ?? "";
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapTextColor(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (label.TextColor != null)
|
||||
{
|
||||
handler.PlatformView.TextColor = label.TextColor.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapFont(LabelHandler handler, ILabel label)
|
||||
{
|
||||
var font = label.Font;
|
||||
if (font.Family != null)
|
||||
{
|
||||
handler.PlatformView.FontFamily = font.Family;
|
||||
}
|
||||
handler.PlatformView.FontSize = (float)font.Size;
|
||||
handler.PlatformView.IsBold = font.Weight == FontWeight.Bold;
|
||||
handler.PlatformView.IsItalic = font.Slant == FontSlant.Italic;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapHorizontalTextAlignment(LabelHandler handler, ILabel label)
|
||||
{
|
||||
handler.PlatformView.HorizontalTextAlignment = label.HorizontalTextAlignment switch
|
||||
{
|
||||
Microsoft.Maui.TextAlignment.Start => TextAlignment.Start,
|
||||
Microsoft.Maui.TextAlignment.Center => TextAlignment.Center,
|
||||
Microsoft.Maui.TextAlignment.End => TextAlignment.End,
|
||||
_ => TextAlignment.Start
|
||||
};
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapVerticalTextAlignment(LabelHandler handler, ILabel label)
|
||||
{
|
||||
handler.PlatformView.VerticalTextAlignment = label.VerticalTextAlignment switch
|
||||
{
|
||||
Microsoft.Maui.TextAlignment.Start => TextAlignment.Start,
|
||||
Microsoft.Maui.TextAlignment.Center => TextAlignment.Center,
|
||||
Microsoft.Maui.TextAlignment.End => TextAlignment.End,
|
||||
_ => TextAlignment.Center
|
||||
};
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapLineBreakMode(LabelHandler handler, ILabel label)
|
||||
{
|
||||
handler.PlatformView.LineBreakMode = label.LineBreakMode switch
|
||||
{
|
||||
Microsoft.Maui.LineBreakMode.NoWrap => LineBreakMode.NoWrap,
|
||||
Microsoft.Maui.LineBreakMode.WordWrap => LineBreakMode.WordWrap,
|
||||
Microsoft.Maui.LineBreakMode.CharacterWrap => LineBreakMode.CharacterWrap,
|
||||
Microsoft.Maui.LineBreakMode.HeadTruncation => LineBreakMode.HeadTruncation,
|
||||
Microsoft.Maui.LineBreakMode.TailTruncation => LineBreakMode.TailTruncation,
|
||||
Microsoft.Maui.LineBreakMode.MiddleTruncation => LineBreakMode.MiddleTruncation,
|
||||
_ => LineBreakMode.TailTruncation
|
||||
};
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapMaxLines(LabelHandler handler, ILabel label)
|
||||
{
|
||||
handler.PlatformView.MaxLines = label.MaxLines;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapPadding(LabelHandler handler, ILabel label)
|
||||
{
|
||||
var padding = label.Padding;
|
||||
handler.PlatformView.Padding = new SKRect(
|
||||
(float)padding.Left,
|
||||
(float)padding.Top,
|
||||
(float)padding.Right,
|
||||
(float)padding.Bottom);
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapTextDecorations(LabelHandler handler, ILabel label)
|
||||
{
|
||||
var decorations = label.TextDecorations;
|
||||
handler.PlatformView.IsUnderline = decorations.HasFlag(TextDecorations.Underline);
|
||||
handler.PlatformView.IsStrikethrough = decorations.HasFlag(TextDecorations.Strikethrough);
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapLineHeight(LabelHandler handler, ILabel label)
|
||||
{
|
||||
handler.PlatformView.LineHeight = (float)label.LineHeight;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackground(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (label.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (label is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Platform.Linux.Window;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
@@ -29,6 +32,7 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(IView.VerticalLayoutAlignment)] = MapVerticalLayoutAlignment,
|
||||
[nameof(IView.HorizontalLayoutAlignment)] = MapHorizontalLayoutAlignment,
|
||||
["FormattedText"] = MapFormattedText,
|
||||
};
|
||||
|
||||
public static CommandMapper<ILabel, LabelHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -49,6 +53,45 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
return new SkiaLabel();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaLabel platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
|
||||
if (VirtualView is View view)
|
||||
{
|
||||
platformView.MauiView = view;
|
||||
|
||||
// Set hand cursor if the label has tap gesture recognizers
|
||||
if (view.GestureRecognizers.OfType<TapGestureRecognizer>().Any())
|
||||
{
|
||||
platformView.CursorType = CursorType.Hand;
|
||||
}
|
||||
}
|
||||
|
||||
// Explicitly map LineBreakMode on connect - MAUI may not trigger property change for defaults
|
||||
if (VirtualView is Microsoft.Maui.Controls.Label mauiLabel)
|
||||
{
|
||||
platformView.LineBreakMode = mauiLabel.LineBreakMode;
|
||||
}
|
||||
|
||||
platformView.Tapped += OnPlatformViewTapped;
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaLabel platformView)
|
||||
{
|
||||
platformView.Tapped -= OnPlatformViewTapped;
|
||||
platformView.MauiView = null;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnPlatformViewTapped(object? sender, EventArgs e)
|
||||
{
|
||||
if (VirtualView is View view)
|
||||
{
|
||||
GestureManager.ProcessTap(view, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapText(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
@@ -60,7 +103,7 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (label.TextColor is not null)
|
||||
handler.PlatformView.TextColor = label.TextColor.ToSKColor();
|
||||
handler.PlatformView.TextColor = label.TextColor;
|
||||
}
|
||||
|
||||
public static void MapFont(LabelHandler handler, ILabel label)
|
||||
@@ -69,32 +112,37 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
|
||||
var font = label.Font;
|
||||
if (font.Size > 0)
|
||||
handler.PlatformView.FontSize = (float)font.Size;
|
||||
handler.PlatformView.FontSize = font.Size;
|
||||
|
||||
if (!string.IsNullOrEmpty(font.Family))
|
||||
handler.PlatformView.FontFamily = font.Family;
|
||||
|
||||
handler.PlatformView.IsBold = font.Weight >= FontWeight.Bold;
|
||||
handler.PlatformView.IsItalic = font.Slant == FontSlant.Italic || font.Slant == FontSlant.Oblique;
|
||||
// Convert Font weight/slant to FontAttributes
|
||||
FontAttributes attrs = FontAttributes.None;
|
||||
if (font.Weight >= FontWeight.Bold)
|
||||
attrs |= FontAttributes.Bold;
|
||||
if (font.Slant == FontSlant.Italic || font.Slant == FontSlant.Oblique)
|
||||
attrs |= FontAttributes.Italic;
|
||||
handler.PlatformView.FontAttributes = attrs;
|
||||
}
|
||||
|
||||
public static void MapCharacterSpacing(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.CharacterSpacing = (float)label.CharacterSpacing;
|
||||
handler.PlatformView.CharacterSpacing = label.CharacterSpacing;
|
||||
}
|
||||
|
||||
public static void MapHorizontalTextAlignment(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
// Map MAUI TextAlignment to our internal TextAlignment
|
||||
// Map MAUI TextAlignment to our TextAlignment
|
||||
handler.PlatformView.HorizontalTextAlignment = label.HorizontalTextAlignment switch
|
||||
{
|
||||
Microsoft.Maui.TextAlignment.Start => Platform.TextAlignment.Start,
|
||||
Microsoft.Maui.TextAlignment.Center => Platform.TextAlignment.Center,
|
||||
Microsoft.Maui.TextAlignment.End => Platform.TextAlignment.End,
|
||||
_ => Platform.TextAlignment.Start
|
||||
Microsoft.Maui.TextAlignment.Start => TextAlignment.Start,
|
||||
Microsoft.Maui.TextAlignment.Center => TextAlignment.Center,
|
||||
Microsoft.Maui.TextAlignment.End => TextAlignment.End,
|
||||
_ => TextAlignment.Start
|
||||
};
|
||||
}
|
||||
|
||||
@@ -104,25 +152,23 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
|
||||
handler.PlatformView.VerticalTextAlignment = label.VerticalTextAlignment switch
|
||||
{
|
||||
Microsoft.Maui.TextAlignment.Start => Platform.TextAlignment.Start,
|
||||
Microsoft.Maui.TextAlignment.Center => Platform.TextAlignment.Center,
|
||||
Microsoft.Maui.TextAlignment.End => Platform.TextAlignment.End,
|
||||
_ => Platform.TextAlignment.Center
|
||||
Microsoft.Maui.TextAlignment.Start => TextAlignment.Start,
|
||||
Microsoft.Maui.TextAlignment.Center => TextAlignment.Center,
|
||||
Microsoft.Maui.TextAlignment.End => TextAlignment.End,
|
||||
_ => TextAlignment.Center
|
||||
};
|
||||
}
|
||||
|
||||
public static void MapTextDecorations(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
handler.PlatformView.IsUnderline = (label.TextDecorations & TextDecorations.Underline) != 0;
|
||||
handler.PlatformView.IsStrikethrough = (label.TextDecorations & TextDecorations.Strikethrough) != 0;
|
||||
handler.PlatformView.TextDecorations = label.TextDecorations;
|
||||
}
|
||||
|
||||
public static void MapLineHeight(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.LineHeight = (float)label.LineHeight;
|
||||
handler.PlatformView.LineHeight = label.LineHeight;
|
||||
}
|
||||
|
||||
public static void MapLineBreakMode(LabelHandler handler, ILabel label)
|
||||
@@ -132,16 +178,7 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
// LineBreakMode is on Label control, not ILabel interface
|
||||
if (label is Microsoft.Maui.Controls.Label mauiLabel)
|
||||
{
|
||||
handler.PlatformView.LineBreakMode = mauiLabel.LineBreakMode switch
|
||||
{
|
||||
Microsoft.Maui.LineBreakMode.NoWrap => Platform.LineBreakMode.NoWrap,
|
||||
Microsoft.Maui.LineBreakMode.WordWrap => Platform.LineBreakMode.WordWrap,
|
||||
Microsoft.Maui.LineBreakMode.CharacterWrap => Platform.LineBreakMode.CharacterWrap,
|
||||
Microsoft.Maui.LineBreakMode.HeadTruncation => Platform.LineBreakMode.HeadTruncation,
|
||||
Microsoft.Maui.LineBreakMode.TailTruncation => Platform.LineBreakMode.TailTruncation,
|
||||
Microsoft.Maui.LineBreakMode.MiddleTruncation => Platform.LineBreakMode.MiddleTruncation,
|
||||
_ => Platform.LineBreakMode.TailTruncation
|
||||
};
|
||||
handler.PlatformView.LineBreakMode = mauiLabel.LineBreakMode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,11 +198,11 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
var padding = label.Padding;
|
||||
handler.PlatformView.Padding = new SKRect(
|
||||
(float)padding.Left,
|
||||
(float)padding.Top,
|
||||
(float)padding.Right,
|
||||
(float)padding.Bottom);
|
||||
handler.PlatformView.Padding = new Thickness(
|
||||
padding.Left,
|
||||
padding.Top,
|
||||
padding.Right,
|
||||
padding.Bottom);
|
||||
}
|
||||
|
||||
public static void MapBackground(LabelHandler handler, ILabel label)
|
||||
@@ -174,7 +211,7 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
|
||||
if (label.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,4 +242,17 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
_ => LayoutOptions.Start
|
||||
};
|
||||
}
|
||||
|
||||
public static void MapFormattedText(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (label is not Label mauiLabel)
|
||||
{
|
||||
handler.PlatformView.FormattedText = null;
|
||||
return;
|
||||
}
|
||||
|
||||
handler.PlatformView.FormattedText = mauiLabel.FormattedText;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform;
|
||||
@@ -63,7 +64,7 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
||||
// (e.g., in ItemTemplates for CollectionView)
|
||||
if (VirtualView is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
platformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
platformView.BackgroundColor = ve.BackgroundColor;
|
||||
platformView.Invalidate();
|
||||
}
|
||||
|
||||
@@ -78,7 +79,7 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
||||
// Create handler for child if it doesn't exist
|
||||
if (child.Handler == null)
|
||||
{
|
||||
child.Handler = child.ToHandler(MauiContext);
|
||||
child.Handler = child.ToViewHandler(MauiContext);
|
||||
}
|
||||
|
||||
if (child.Handler?.PlatformView is SkiaView skiaChild)
|
||||
@@ -98,7 +99,7 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
||||
var background = layout.Background;
|
||||
if (background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color;
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
@@ -107,7 +108,7 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
||||
{
|
||||
if (layout is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
@@ -299,7 +300,7 @@ public partial class GridHandler : LayoutHandler
|
||||
// Create handler for child if it doesn't exist
|
||||
if (child.Handler == null)
|
||||
{
|
||||
child.Handler = child.ToHandler(MauiContext);
|
||||
child.Handler = child.ToViewHandler(MauiContext);
|
||||
Console.WriteLine($"[GridHandler.ConnectHandler] Created handler for child[{i}]: {child.Handler?.GetType().Name ?? "failed"}");
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
@@ -53,7 +54,7 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
||||
// Explicitly map BackgroundColor since it may be set before handler creation
|
||||
if (VirtualView is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
platformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
platformView.BackgroundColor = ve.BackgroundColor;
|
||||
}
|
||||
|
||||
for (int i = 0; i < VirtualView.Count; i++)
|
||||
@@ -64,7 +65,7 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
||||
// Create handler for child if it doesn't exist
|
||||
if (child.Handler == null)
|
||||
{
|
||||
child.Handler = child.ToHandler(MauiContext);
|
||||
child.Handler = child.ToViewHandler(MauiContext);
|
||||
}
|
||||
|
||||
// Add child's platform view to our layout
|
||||
@@ -87,7 +88,7 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
||||
|
||||
if (layout.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,12 +143,7 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
||||
|
||||
if (layout is IPadding paddable)
|
||||
{
|
||||
var padding = paddable.Padding;
|
||||
handler.PlatformView.Padding = new SKRect(
|
||||
(float)padding.Left,
|
||||
(float)padding.Top,
|
||||
(float)padding.Right,
|
||||
(float)padding.Bottom);
|
||||
handler.PlatformView.Padding = paddable.Padding;
|
||||
handler.PlatformView.InvalidateMeasure();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
@@ -254,18 +250,14 @@ public partial class GridHandler : LayoutHandler
|
||||
// Explicitly map BackgroundColor since it may be set before handler creation
|
||||
if (VirtualView is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
platformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
platformView.BackgroundColor = ve.BackgroundColor;
|
||||
}
|
||||
|
||||
// Explicitly map Padding since it may be set before handler creation
|
||||
if (VirtualView is IPadding paddable)
|
||||
{
|
||||
var padding = paddable.Padding;
|
||||
platformView.Padding = new SKRect(
|
||||
(float)padding.Left,
|
||||
(float)padding.Top,
|
||||
(float)padding.Right,
|
||||
(float)padding.Bottom);
|
||||
platformView.Padding = padding;
|
||||
Console.WriteLine($"[GridHandler] Applied Padding: L={padding.Left}, T={padding.Top}, R={padding.Right}, B={padding.Bottom}");
|
||||
}
|
||||
|
||||
@@ -284,7 +276,7 @@ public partial class GridHandler : LayoutHandler
|
||||
// Create handler for child if it doesn't exist
|
||||
if (child.Handler == null)
|
||||
{
|
||||
child.Handler = child.ToHandler(MauiContext);
|
||||
child.Handler = child.ToViewHandler(MauiContext);
|
||||
}
|
||||
|
||||
// Get grid position from attached properties
|
||||
|
||||
376
Handlers/MenuBarHandler.cs
Normal file
376
Handlers/MenuBarHandler.cs
Normal file
@@ -0,0 +1,376 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for MenuBar on Linux using Skia rendering.
|
||||
/// Maps MenuBar to SkiaMenuBar platform view.
|
||||
/// </summary>
|
||||
public partial class MenuBarHandler : ElementHandler<IMenuBar, SkiaMenuBar>
|
||||
{
|
||||
public static IPropertyMapper<IMenuBar, MenuBarHandler> Mapper =
|
||||
new PropertyMapper<IMenuBar, MenuBarHandler>()
|
||||
{
|
||||
[nameof(IMenuBar.IsEnabled)] = MapIsEnabled,
|
||||
};
|
||||
|
||||
public static CommandMapper<IMenuBar, MenuBarHandler> CommandMapper =
|
||||
new()
|
||||
{
|
||||
["Add"] = MapAdd,
|
||||
["Remove"] = MapRemove,
|
||||
["Clear"] = MapClear,
|
||||
["Insert"] = MapInsert,
|
||||
};
|
||||
|
||||
public MenuBarHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public MenuBarHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaMenuBar CreatePlatformElement()
|
||||
{
|
||||
return new SkiaMenuBar();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaMenuBar platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
SyncMenuBarItems();
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaMenuBar platformView)
|
||||
{
|
||||
platformView.Items.Clear();
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void SyncMenuBarItems()
|
||||
{
|
||||
if (PlatformView is null || VirtualView is null) return;
|
||||
|
||||
PlatformView.Items.Clear();
|
||||
|
||||
foreach (var menuBarItem in VirtualView)
|
||||
{
|
||||
if (menuBarItem is MenuBarItem mauiItem)
|
||||
{
|
||||
var platformItem = CreatePlatformMenuBarItem(mauiItem);
|
||||
PlatformView.Items.Add(platformItem);
|
||||
}
|
||||
}
|
||||
|
||||
PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
private static Platform.MenuBarItem CreatePlatformMenuBarItem(MenuBarItem mauiItem)
|
||||
{
|
||||
var platformItem = new Platform.MenuBarItem
|
||||
{
|
||||
Text = mauiItem.Text ?? ""
|
||||
};
|
||||
|
||||
// MenuBarItem inherits from BaseMenuItem which has a collection
|
||||
// Use cast to IEnumerable to iterate
|
||||
if (mauiItem is System.Collections.IEnumerable enumerable)
|
||||
{
|
||||
foreach (var child in enumerable)
|
||||
{
|
||||
if (child is MenuFlyoutItem flyoutItem)
|
||||
{
|
||||
var menuItem = CreatePlatformMenuItem(flyoutItem);
|
||||
platformItem.Items.Add(menuItem);
|
||||
}
|
||||
else if (child is MenuFlyoutSubItem subItem)
|
||||
{
|
||||
var menuItem = CreatePlatformMenuItemWithSubs(subItem);
|
||||
platformItem.Items.Add(menuItem);
|
||||
}
|
||||
else if (child is MenuFlyoutSeparator)
|
||||
{
|
||||
platformItem.Items.Add(new Platform.MenuItem { IsSeparator = true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return platformItem;
|
||||
}
|
||||
|
||||
private static Platform.MenuItem CreatePlatformMenuItem(MenuFlyoutItem mauiItem)
|
||||
{
|
||||
var menuItem = new Platform.MenuItem
|
||||
{
|
||||
Text = mauiItem.Text ?? "",
|
||||
IsEnabled = mauiItem.IsEnabled,
|
||||
IconSource = mauiItem.IconImageSource?.ToString()
|
||||
};
|
||||
|
||||
// Map keyboard accelerator
|
||||
if (mauiItem.KeyboardAccelerators.Count > 0)
|
||||
{
|
||||
var accel = mauiItem.KeyboardAccelerators[0];
|
||||
menuItem.Shortcut = FormatKeyboardAccelerator(accel);
|
||||
}
|
||||
|
||||
// Connect click event
|
||||
menuItem.Clicked += (s, e) =>
|
||||
{
|
||||
if (mauiItem.Command?.CanExecute(mauiItem.CommandParameter) == true)
|
||||
{
|
||||
mauiItem.Command.Execute(mauiItem.CommandParameter);
|
||||
}
|
||||
(mauiItem as IMenuFlyoutItem)?.Clicked();
|
||||
};
|
||||
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
private static Platform.MenuItem CreatePlatformMenuItemWithSubs(MenuFlyoutSubItem mauiSubItem)
|
||||
{
|
||||
var menuItem = new Platform.MenuItem
|
||||
{
|
||||
Text = mauiSubItem.Text ?? "",
|
||||
IsEnabled = mauiSubItem.IsEnabled,
|
||||
IconSource = mauiSubItem.IconImageSource?.ToString()
|
||||
};
|
||||
|
||||
// MenuFlyoutSubItem is enumerable
|
||||
if (mauiSubItem is System.Collections.IEnumerable enumerable)
|
||||
{
|
||||
foreach (var child in enumerable)
|
||||
{
|
||||
if (child is MenuFlyoutItem flyoutItem)
|
||||
{
|
||||
menuItem.SubItems.Add(CreatePlatformMenuItem(flyoutItem));
|
||||
}
|
||||
else if (child is MenuFlyoutSubItem nestedSubItem)
|
||||
{
|
||||
menuItem.SubItems.Add(CreatePlatformMenuItemWithSubs(nestedSubItem));
|
||||
}
|
||||
else if (child is MenuFlyoutSeparator)
|
||||
{
|
||||
menuItem.SubItems.Add(new Platform.MenuItem { IsSeparator = true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
private static string FormatKeyboardAccelerator(KeyboardAccelerator accel)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
|
||||
if (accel.Modifiers.HasFlag(KeyboardAcceleratorModifiers.Ctrl))
|
||||
parts.Add("Ctrl");
|
||||
if (accel.Modifiers.HasFlag(KeyboardAcceleratorModifiers.Alt))
|
||||
parts.Add("Alt");
|
||||
if (accel.Modifiers.HasFlag(KeyboardAcceleratorModifiers.Shift))
|
||||
parts.Add("Shift");
|
||||
|
||||
parts.Add(accel.Key ?? "");
|
||||
|
||||
return string.Join("+", parts);
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(MenuBarHandler handler, IMenuBar menuBar)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.IsEnabled = menuBar.IsEnabled;
|
||||
}
|
||||
|
||||
public static void MapAdd(MenuBarHandler handler, IMenuBar menuBar, object? args)
|
||||
{
|
||||
handler.SyncMenuBarItems();
|
||||
}
|
||||
|
||||
public static void MapRemove(MenuBarHandler handler, IMenuBar menuBar, object? args)
|
||||
{
|
||||
handler.SyncMenuBarItems();
|
||||
}
|
||||
|
||||
public static void MapClear(MenuBarHandler handler, IMenuBar menuBar, object? args)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.Items.Clear();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapInsert(MenuBarHandler handler, IMenuBar menuBar, object? args)
|
||||
{
|
||||
handler.SyncMenuBarItems();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for MenuFlyout (context menu) on Linux using Skia rendering.
|
||||
/// Maps IMenuFlyout to SkiaMenuFlyout platform view.
|
||||
/// </summary>
|
||||
public partial class MenuFlyoutHandler : ElementHandler<IMenuFlyout, SkiaMenuFlyout>
|
||||
{
|
||||
public static IPropertyMapper<IMenuFlyout, MenuFlyoutHandler> Mapper =
|
||||
new PropertyMapper<IMenuFlyout, MenuFlyoutHandler>()
|
||||
{
|
||||
};
|
||||
|
||||
public static CommandMapper<IMenuFlyout, MenuFlyoutHandler> CommandMapper =
|
||||
new()
|
||||
{
|
||||
["Add"] = MapAdd,
|
||||
["Remove"] = MapRemove,
|
||||
["Clear"] = MapClear,
|
||||
};
|
||||
|
||||
public MenuFlyoutHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public MenuFlyoutHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaMenuFlyout CreatePlatformElement()
|
||||
{
|
||||
return new SkiaMenuFlyout();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaMenuFlyout platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
SyncMenuItems();
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaMenuFlyout platformView)
|
||||
{
|
||||
platformView.Items.Clear();
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void SyncMenuItems()
|
||||
{
|
||||
if (PlatformView is null || VirtualView is null) return;
|
||||
|
||||
PlatformView.Items.Clear();
|
||||
|
||||
foreach (var item in VirtualView)
|
||||
{
|
||||
if (item is MenuFlyoutItem flyoutItem)
|
||||
{
|
||||
PlatformView.Items.Add(CreatePlatformMenuItem(flyoutItem));
|
||||
}
|
||||
else if (item is MenuFlyoutSubItem subItem)
|
||||
{
|
||||
PlatformView.Items.Add(CreatePlatformMenuItemWithSubs(subItem));
|
||||
}
|
||||
else if (item is MenuFlyoutSeparator)
|
||||
{
|
||||
PlatformView.Items.Add(new Platform.MenuItem { IsSeparator = true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Platform.MenuItem CreatePlatformMenuItem(MenuFlyoutItem mauiItem)
|
||||
{
|
||||
var menuItem = new Platform.MenuItem
|
||||
{
|
||||
Text = mauiItem.Text ?? "",
|
||||
IsEnabled = mauiItem.IsEnabled,
|
||||
IconSource = mauiItem.IconImageSource?.ToString()
|
||||
};
|
||||
|
||||
// Map keyboard accelerator
|
||||
if (mauiItem.KeyboardAccelerators.Count > 0)
|
||||
{
|
||||
var accel = mauiItem.KeyboardAccelerators[0];
|
||||
menuItem.Shortcut = FormatKeyboardAccelerator(accel);
|
||||
}
|
||||
|
||||
// Connect click event
|
||||
menuItem.Clicked += (s, e) =>
|
||||
{
|
||||
if (mauiItem.Command?.CanExecute(mauiItem.CommandParameter) == true)
|
||||
{
|
||||
mauiItem.Command.Execute(mauiItem.CommandParameter);
|
||||
}
|
||||
(mauiItem as IMenuFlyoutItem)?.Clicked();
|
||||
};
|
||||
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
private static Platform.MenuItem CreatePlatformMenuItemWithSubs(MenuFlyoutSubItem mauiSubItem)
|
||||
{
|
||||
var menuItem = new Platform.MenuItem
|
||||
{
|
||||
Text = mauiSubItem.Text ?? "",
|
||||
IsEnabled = mauiSubItem.IsEnabled,
|
||||
IconSource = mauiSubItem.IconImageSource?.ToString()
|
||||
};
|
||||
|
||||
// MenuFlyoutSubItem is enumerable
|
||||
if (mauiSubItem is System.Collections.IEnumerable enumerable)
|
||||
{
|
||||
foreach (var child in enumerable)
|
||||
{
|
||||
if (child is MenuFlyoutItem flyoutItem)
|
||||
{
|
||||
menuItem.SubItems.Add(CreatePlatformMenuItem(flyoutItem));
|
||||
}
|
||||
else if (child is MenuFlyoutSubItem nestedSubItem)
|
||||
{
|
||||
menuItem.SubItems.Add(CreatePlatformMenuItemWithSubs(nestedSubItem));
|
||||
}
|
||||
else if (child is MenuFlyoutSeparator)
|
||||
{
|
||||
menuItem.SubItems.Add(new Platform.MenuItem { IsSeparator = true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
private static string FormatKeyboardAccelerator(KeyboardAccelerator accel)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
|
||||
if (accel.Modifiers.HasFlag(KeyboardAcceleratorModifiers.Ctrl))
|
||||
parts.Add("Ctrl");
|
||||
if (accel.Modifiers.HasFlag(KeyboardAcceleratorModifiers.Alt))
|
||||
parts.Add("Alt");
|
||||
if (accel.Modifiers.HasFlag(KeyboardAcceleratorModifiers.Shift))
|
||||
parts.Add("Shift");
|
||||
|
||||
parts.Add(accel.Key ?? "");
|
||||
|
||||
return string.Join("+", parts);
|
||||
}
|
||||
|
||||
public static void MapAdd(MenuFlyoutHandler handler, IMenuFlyout menuFlyout, object? args)
|
||||
{
|
||||
handler.SyncMenuItems();
|
||||
}
|
||||
|
||||
public static void MapRemove(MenuFlyoutHandler handler, IMenuFlyout menuFlyout, object? args)
|
||||
{
|
||||
handler.SyncMenuItems();
|
||||
}
|
||||
|
||||
public static void MapClear(MenuFlyoutHandler handler, IMenuFlyout menuFlyout, object? args)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.Items.Clear();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.IO;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Platform;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using SkiaSharp;
|
||||
using Svg.Skia;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
@@ -100,7 +103,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
if (page.Handler == null)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] Creating handler for: {page.Title}");
|
||||
page.Handler = page.ToHandler(MauiContext);
|
||||
page.Handler = page.ToViewHandler(MauiContext);
|
||||
}
|
||||
|
||||
Console.WriteLine($"[NavigationPageHandler] Page handler type: {page.Handler?.GetType().Name}");
|
||||
@@ -122,7 +125,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
Console.WriteLine($"[NavigationPageHandler] Content is null, manually creating handler for: {contentPage.Content.GetType().Name}");
|
||||
if (contentPage.Content.Handler == null)
|
||||
{
|
||||
contentPage.Content.Handler = contentPage.Content.ToHandler(MauiContext);
|
||||
contentPage.Content.Handler = contentPage.Content.ToViewHandler(MauiContext);
|
||||
}
|
||||
if (contentPage.Content.Handler?.PlatformView is SkiaView skiaContent)
|
||||
{
|
||||
@@ -163,7 +166,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
contentPage.ToolbarItems.Clear();
|
||||
foreach (var item in page.ToolbarItems)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] Adding toolbar item: '{item.Text}', Order={item.Order}");
|
||||
Console.WriteLine($"[NavigationPageHandler] Adding toolbar item: '{item.Text}', IconImageSource={item.IconImageSource}, Order={item.Order}");
|
||||
// Default and Primary should both be treated as Primary (shown in toolbar)
|
||||
// Only Secondary goes to overflow menu
|
||||
var order = item.Order == ToolbarItemOrder.Secondary
|
||||
@@ -187,9 +190,17 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
}
|
||||
});
|
||||
|
||||
// Load icon if specified
|
||||
SKBitmap? icon = null;
|
||||
if (item.IconImageSource is FileImageSource fileSource && !string.IsNullOrEmpty(fileSource.File))
|
||||
{
|
||||
icon = LoadToolbarIcon(fileSource.File);
|
||||
}
|
||||
|
||||
contentPage.ToolbarItems.Add(new SkiaToolbarItem
|
||||
{
|
||||
Text = item.Text ?? "",
|
||||
Icon = icon,
|
||||
Order = order,
|
||||
Command = clickCommand
|
||||
});
|
||||
@@ -210,6 +221,56 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
}
|
||||
}
|
||||
|
||||
private SKBitmap? LoadToolbarIcon(string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
string baseDirectory = AppContext.BaseDirectory;
|
||||
string pngPath = Path.Combine(baseDirectory, fileName);
|
||||
string svgPath = Path.Combine(baseDirectory, Path.ChangeExtension(fileName, ".svg"));
|
||||
|
||||
Console.WriteLine($"[NavigationPageHandler] LoadToolbarIcon: Looking for {fileName}");
|
||||
Console.WriteLine($"[NavigationPageHandler] Trying PNG: {pngPath} (exists: {File.Exists(pngPath)})");
|
||||
Console.WriteLine($"[NavigationPageHandler] Trying SVG: {svgPath} (exists: {File.Exists(svgPath)})");
|
||||
|
||||
// Try SVG first
|
||||
if (File.Exists(svgPath))
|
||||
{
|
||||
using var svg = new SKSvg();
|
||||
svg.Load(svgPath);
|
||||
if (svg.Picture != null)
|
||||
{
|
||||
var cullRect = svg.Picture.CullRect;
|
||||
float scale = 24f / Math.Max(cullRect.Width, cullRect.Height);
|
||||
var bitmap = new SKBitmap(24, 24, false);
|
||||
using var canvas = new SKCanvas(bitmap);
|
||||
canvas.Clear(SKColors.Transparent);
|
||||
canvas.Scale(scale);
|
||||
canvas.DrawPicture(svg.Picture, null);
|
||||
Console.WriteLine($"[NavigationPageHandler] Loaded SVG icon: {svgPath}");
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
// Try PNG
|
||||
if (File.Exists(pngPath))
|
||||
{
|
||||
using var stream = File.OpenRead(pngPath);
|
||||
var result = SKBitmap.Decode(stream);
|
||||
Console.WriteLine($"[NavigationPageHandler] Loaded PNG icon: {pngPath}");
|
||||
return result;
|
||||
}
|
||||
|
||||
Console.WriteLine($"[NavigationPageHandler] Icon not found: {fileName}");
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] Error loading icon {fileName}: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnVirtualViewPushed(object? sender, Microsoft.Maui.Controls.NavigationEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -221,7 +282,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
if (e.Page.Handler == null)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] Creating handler for page: {e.Page.GetType().Name}");
|
||||
e.Page.Handler = e.Page.ToHandler(MauiContext);
|
||||
e.Page.Handler = e.Page.ToViewHandler(MauiContext);
|
||||
Console.WriteLine($"[NavigationPageHandler] Handler created: {e.Page.Handler?.GetType().Name}");
|
||||
}
|
||||
|
||||
@@ -231,12 +292,30 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
skiaPage.ShowNavigationBar = true;
|
||||
skiaPage.TitleBarColor = PlatformView.BarBackgroundColor;
|
||||
skiaPage.TitleTextColor = PlatformView.BarTextColor;
|
||||
skiaPage.Title = e.Page.Title ?? "";
|
||||
|
||||
// Handle content if null
|
||||
if (skiaPage.Content == null && e.Page is ContentPage contentPage && contentPage.Content != null)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] Content is null, creating handler for: {contentPage.Content.GetType().Name}");
|
||||
if (contentPage.Content.Handler == null)
|
||||
{
|
||||
contentPage.Content.Handler = contentPage.Content.ToViewHandler(MauiContext);
|
||||
}
|
||||
if (contentPage.Content.Handler?.PlatformView is SkiaView skiaContent)
|
||||
{
|
||||
skiaPage.Content = skiaContent;
|
||||
Console.WriteLine($"[NavigationPageHandler] Set content to: {skiaContent.GetType().Name}");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"[NavigationPageHandler] Mapping toolbar items");
|
||||
MapToolbarItems(skiaPage, e.Page);
|
||||
Console.WriteLine($"[NavigationPageHandler] Pushing page to platform");
|
||||
PlatformView.Push(skiaPage, true);
|
||||
Console.WriteLine($"[NavigationPageHandler] Push complete");
|
||||
PlatformView.Push(skiaPage, false);
|
||||
Console.WriteLine($"[NavigationPageHandler] Push complete, thread={Environment.CurrentManagedThreadId}");
|
||||
}
|
||||
Console.WriteLine("[NavigationPageHandler] OnVirtualViewPushed returning");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -250,13 +329,13 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] VirtualView Popped: {e.Page?.Title}");
|
||||
// Pop on the platform side to sync with MAUI navigation
|
||||
PlatformView?.Pop(true);
|
||||
PlatformView?.Pop();
|
||||
}
|
||||
|
||||
private void OnVirtualViewPoppedToRoot(object? sender, Microsoft.Maui.Controls.NavigationEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] VirtualView PoppedToRoot");
|
||||
PlatformView?.PopToRoot(true);
|
||||
PlatformView?.PopToRoot();
|
||||
}
|
||||
|
||||
private void OnPushed(object? sender, NavigationEventArgs e)
|
||||
@@ -285,7 +364,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
|
||||
if (navigationPage.BarBackgroundColor is not null)
|
||||
{
|
||||
handler.PlatformView.BarBackgroundColor = navigationPage.BarBackgroundColor.ToSKColor();
|
||||
handler.PlatformView.BarBackgroundColor = navigationPage.BarBackgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,7 +374,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
|
||||
if (navigationPage.BarBackground is SolidColorBrush solidBrush)
|
||||
{
|
||||
handler.PlatformView.BarBackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.BarBackgroundColor = solidBrush.Color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,7 +384,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
|
||||
if (navigationPage.BarTextColor is not null)
|
||||
{
|
||||
handler.PlatformView.BarTextColor = navigationPage.BarTextColor.ToSKColor();
|
||||
handler.PlatformView.BarTextColor = navigationPage.BarTextColor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,7 +394,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
|
||||
if (navigationPage.Background is SolidColorBrush solidBrush)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +413,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
// Ensure handler exists
|
||||
if (page.Handler == null)
|
||||
{
|
||||
page.Handler = page.ToHandler(handler.MauiContext);
|
||||
page.Handler = page.ToViewHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (page.Handler?.PlatformView is SkiaPage skiaPage)
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Platform;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
@@ -19,8 +20,11 @@ public partial class PageHandler : ViewHandler<Page, SkiaPage>
|
||||
{
|
||||
[nameof(Page.Title)] = MapTitle,
|
||||
[nameof(Page.BackgroundImageSource)] = MapBackgroundImageSource,
|
||||
[nameof(Page.IconImageSource)] = MapIconImageSource,
|
||||
[nameof(Page.Padding)] = MapPadding,
|
||||
[nameof(Page.IsBusy)] = MapIsBusy,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(VisualElement.BackgroundColor)] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
public static CommandMapper<Page, PageHandler> CommandMapper =
|
||||
@@ -45,6 +49,10 @@ public partial class PageHandler : ViewHandler<Page, SkiaPage>
|
||||
protected override void ConnectHandler(SkiaPage platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
|
||||
// Set MauiPage reference for theme refresh support
|
||||
platformView.MauiPage = VirtualView;
|
||||
|
||||
platformView.Appearing += OnAppearing;
|
||||
platformView.Disappearing += OnDisappearing;
|
||||
}
|
||||
@@ -53,6 +61,7 @@ public partial class PageHandler : ViewHandler<Page, SkiaPage>
|
||||
{
|
||||
platformView.Appearing -= OnAppearing;
|
||||
platformView.Disappearing -= OnDisappearing;
|
||||
platformView.MauiPage = null;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
@@ -97,9 +106,34 @@ public partial class PageHandler : ViewHandler<Page, SkiaPage>
|
||||
|
||||
if (page.Background is SolidColorBrush solidBrush)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(PageHandler handler, Page page)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
var backgroundColor = page.BackgroundColor;
|
||||
if (backgroundColor != null && backgroundColor != Colors.Transparent)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = backgroundColor;
|
||||
Console.WriteLine($"[PageHandler] MapBackgroundColor: {backgroundColor}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIconImageSource(PageHandler handler, Page page)
|
||||
{
|
||||
// Icon is typically used by navigation containers (Shell, TabbedPage)
|
||||
// Store for later use but don't render directly on the page
|
||||
handler.PlatformView?.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapIsBusy(PageHandler handler, Page page)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.IsBusy = page.IsBusy;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -111,6 +145,7 @@ public partial class ContentPageHandler : PageHandler
|
||||
new PropertyMapper<ContentPage, ContentPageHandler>(PageHandler.Mapper)
|
||||
{
|
||||
[nameof(ContentPage.Content)] = MapContent,
|
||||
[nameof(ContentPage.ToolbarItems)] = MapToolbarItems,
|
||||
};
|
||||
|
||||
public static new CommandMapper<ContentPage, ContentPageHandler> CommandMapper =
|
||||
@@ -132,6 +167,17 @@ public partial class ContentPageHandler : PageHandler
|
||||
return new SkiaContentPage();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaPage platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
|
||||
// Sync toolbar items initially
|
||||
if (VirtualView is ContentPage contentPage && platformView is SkiaContentPage skiaContentPage)
|
||||
{
|
||||
SyncToolbarItems(skiaContentPage, contentPage);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapContent(ContentPageHandler handler, ContentPage page)
|
||||
{
|
||||
if (handler.PlatformView is null || handler.MauiContext is null) return;
|
||||
@@ -144,7 +190,7 @@ public partial class ContentPageHandler : PageHandler
|
||||
if (content.Handler == null)
|
||||
{
|
||||
Console.WriteLine($"[ContentPageHandler] Creating handler for content: {content.GetType().Name}");
|
||||
content.Handler = content.ToHandler(handler.MauiContext);
|
||||
content.Handler = content.ToViewHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
// The content's handler should provide the platform view
|
||||
@@ -163,4 +209,38 @@ public partial class ContentPageHandler : PageHandler
|
||||
handler.PlatformView.Content = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapToolbarItems(ContentPageHandler handler, ContentPage page)
|
||||
{
|
||||
if (handler.PlatformView is not SkiaContentPage skiaContentPage) return;
|
||||
SyncToolbarItems(skiaContentPage, page);
|
||||
}
|
||||
|
||||
private static void SyncToolbarItems(SkiaContentPage platformView, ContentPage page)
|
||||
{
|
||||
platformView.ToolbarItems.Clear();
|
||||
|
||||
foreach (var item in page.ToolbarItems)
|
||||
{
|
||||
var skiaItem = new SkiaToolbarItem
|
||||
{
|
||||
Text = item.Text ?? "",
|
||||
Command = item.Command,
|
||||
Order = item.Order == ToolbarItemOrder.Primary
|
||||
? SkiaToolbarItemOrder.Primary
|
||||
: SkiaToolbarItemOrder.Secondary
|
||||
};
|
||||
|
||||
// Load icon if present
|
||||
if (item.IconImageSource is FileImageSource fileSource)
|
||||
{
|
||||
// Icon loading would be async - simplified for now
|
||||
Console.WriteLine($"[ContentPageHandler] Toolbar item icon: {fileSource.File}");
|
||||
}
|
||||
|
||||
platformView.ToolbarItems.Add(skiaItem);
|
||||
}
|
||||
|
||||
platformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,13 @@ using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Platform;
|
||||
using SkiaSharp;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for Picker on Linux using Skia rendering.
|
||||
/// Maps IPicker interface to SkiaPicker platform view.
|
||||
/// </summary>
|
||||
public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
||||
{
|
||||
@@ -22,10 +22,12 @@ public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
||||
[nameof(IPicker.TitleColor)] = MapTitleColor,
|
||||
[nameof(IPicker.SelectedIndex)] = MapSelectedIndex,
|
||||
[nameof(IPicker.TextColor)] = MapTextColor,
|
||||
[nameof(ITextStyle.Font)] = MapFont,
|
||||
[nameof(IPicker.CharacterSpacing)] = MapCharacterSpacing,
|
||||
[nameof(IPicker.HorizontalTextAlignment)] = MapHorizontalTextAlignment,
|
||||
[nameof(IPicker.VerticalTextAlignment)] = MapVerticalTextAlignment,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
[nameof(Picker.ItemsSource)] = MapItemsSource,
|
||||
};
|
||||
|
||||
@@ -62,8 +64,17 @@ public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
||||
_itemsCollection.CollectionChanged += OnItemsCollectionChanged;
|
||||
}
|
||||
|
||||
// Load items
|
||||
// Load items and sync properties
|
||||
ReloadItems();
|
||||
|
||||
if (VirtualView != null)
|
||||
{
|
||||
MapTitle(this, VirtualView);
|
||||
MapTitleColor(this, VirtualView);
|
||||
MapTextColor(this, VirtualView);
|
||||
MapSelectedIndex(this, VirtualView);
|
||||
MapIsEnabled(this, VirtualView);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaPicker platformView)
|
||||
@@ -84,11 +95,14 @@ public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
||||
ReloadItems();
|
||||
}
|
||||
|
||||
private void OnSelectedIndexChanged(object? sender, EventArgs e)
|
||||
private void OnSelectedIndexChanged(object? sender, SelectedIndexChangedEventArgs e)
|
||||
{
|
||||
if (VirtualView is null || PlatformView is null) return;
|
||||
|
||||
VirtualView.SelectedIndex = PlatformView.SelectedIndex;
|
||||
if (VirtualView.SelectedIndex != e.NewIndex)
|
||||
{
|
||||
VirtualView.SelectedIndex = e.NewIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReloadItems()
|
||||
@@ -110,38 +124,68 @@ public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
||||
if (handler.PlatformView is null) return;
|
||||
if (picker.TitleColor is not null)
|
||||
{
|
||||
handler.PlatformView.TitleColor = picker.TitleColor.ToSKColor();
|
||||
handler.PlatformView.TitleColor = picker.TitleColor;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapSelectedIndex(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (handler.PlatformView.SelectedIndex != picker.SelectedIndex)
|
||||
{
|
||||
handler.PlatformView.SelectedIndex = picker.SelectedIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapTextColor(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
if (picker.TextColor is not null)
|
||||
{
|
||||
handler.PlatformView.TextColor = picker.TextColor.ToSKColor();
|
||||
handler.PlatformView.TextColor = picker.TextColor;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapFont(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
var font = picker.Font;
|
||||
if (!string.IsNullOrEmpty(font.Family))
|
||||
{
|
||||
handler.PlatformView.FontFamily = font.Family;
|
||||
}
|
||||
if (font.Size > 0)
|
||||
{
|
||||
handler.PlatformView.FontSize = font.Size;
|
||||
}
|
||||
|
||||
// Map FontAttributes from the Font weight
|
||||
var attrs = FontAttributes.None;
|
||||
if (font.Weight >= FontWeight.Bold)
|
||||
attrs |= FontAttributes.Bold;
|
||||
handler.PlatformView.FontAttributes = attrs;
|
||||
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapCharacterSpacing(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
// Character spacing could be implemented with custom text rendering
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.CharacterSpacing = picker.CharacterSpacing;
|
||||
}
|
||||
|
||||
public static void MapHorizontalTextAlignment(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
// Text alignment would require changes to SkiaPicker drawing
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.HorizontalTextAlignment = picker.HorizontalTextAlignment;
|
||||
}
|
||||
|
||||
public static void MapVerticalTextAlignment(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
// Text alignment would require changes to SkiaPicker drawing
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.VerticalTextAlignment = picker.VerticalTextAlignment;
|
||||
}
|
||||
|
||||
public static void MapBackground(PickerHandler handler, IPicker picker)
|
||||
@@ -150,10 +194,17 @@ public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
||||
|
||||
if (picker.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.IsEnabled = picker.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapItemsSource(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
handler.ReloadItems();
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
|
||||
namespace Microsoft.Maui.Platform;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for ProgressBar control.
|
||||
/// </summary>
|
||||
public partial class ProgressBarHandler : ViewHandler<IProgress, SkiaProgressBar>
|
||||
{
|
||||
public static IPropertyMapper<IProgress, ProgressBarHandler> Mapper = new PropertyMapper<IProgress, ProgressBarHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(IProgress.Progress)] = MapProgress,
|
||||
[nameof(IProgress.ProgressColor)] = MapProgressColor,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
public static CommandMapper<IProgress, ProgressBarHandler> CommandMapper = new(ViewHandler.ViewCommandMapper);
|
||||
|
||||
public ProgressBarHandler() : base(Mapper, CommandMapper) { }
|
||||
|
||||
protected override SkiaProgressBar CreatePlatformView() => new SkiaProgressBar();
|
||||
|
||||
public static void MapProgress(ProgressBarHandler handler, IProgress progress)
|
||||
{
|
||||
handler.PlatformView.Progress = progress.Progress;
|
||||
}
|
||||
|
||||
public static void MapProgressColor(ProgressBarHandler handler, IProgress progress)
|
||||
{
|
||||
if (progress.ProgressColor != null)
|
||||
handler.PlatformView.ProgressColor = progress.ProgressColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(ProgressBarHandler handler, IProgress progress)
|
||||
{
|
||||
handler.PlatformView.IsEnabled = progress.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackground(ProgressBarHandler handler, IProgress progress)
|
||||
{
|
||||
if (progress.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(ProgressBarHandler handler, IProgress progress)
|
||||
{
|
||||
if (progress is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using SkiaSharp;
|
||||
using Microsoft.Maui.Platform;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
@@ -18,7 +20,12 @@ public partial class ProgressBarHandler : ViewHandler<IProgress, SkiaProgressBar
|
||||
{
|
||||
[nameof(IProgress.Progress)] = MapProgress,
|
||||
[nameof(IProgress.ProgressColor)] = MapProgressColor,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
[nameof(IView.Height)] = MapHeight,
|
||||
[nameof(IView.Width)] = MapWidth,
|
||||
["VerticalOptions"] = MapVerticalOptions,
|
||||
};
|
||||
|
||||
public static CommandMapper<IProgress, ProgressBarHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -39,6 +46,48 @@ public partial class ProgressBarHandler : ViewHandler<IProgress, SkiaProgressBar
|
||||
return new SkiaProgressBar();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaProgressBar platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
|
||||
if (VirtualView is BindableObject bindable)
|
||||
{
|
||||
bindable.PropertyChanged += OnVirtualViewPropertyChanged;
|
||||
}
|
||||
|
||||
if (VirtualView is VisualElement visualElement)
|
||||
{
|
||||
platformView.IsVisible = visualElement.IsVisible;
|
||||
}
|
||||
|
||||
// Sync properties
|
||||
if (VirtualView != null)
|
||||
{
|
||||
MapProgress(this, VirtualView);
|
||||
MapProgressColor(this, VirtualView);
|
||||
MapIsEnabled(this, VirtualView);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaProgressBar platformView)
|
||||
{
|
||||
if (VirtualView is BindableObject bindable)
|
||||
{
|
||||
bindable.PropertyChanged -= OnVirtualViewPropertyChanged;
|
||||
}
|
||||
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnVirtualViewPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (VirtualView is VisualElement visualElement && e.PropertyName == nameof(VisualElement.IsVisible))
|
||||
{
|
||||
PlatformView.IsVisible = visualElement.IsVisible;
|
||||
PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapProgress(ProgressBarHandler handler, IProgress progress)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
@@ -50,7 +99,18 @@ public partial class ProgressBarHandler : ViewHandler<IProgress, SkiaProgressBar
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (progress.ProgressColor is not null)
|
||||
handler.PlatformView.ProgressColor = progress.ProgressColor.ToSKColor();
|
||||
{
|
||||
handler.PlatformView.ProgressColor = progress.ProgressColor;
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(ProgressBarHandler handler, IProgress progress)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
handler.PlatformView.IsEnabled = progress.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackground(ProgressBarHandler handler, IProgress progress)
|
||||
@@ -59,7 +119,49 @@ public partial class ProgressBarHandler : ViewHandler<IProgress, SkiaProgressBar
|
||||
|
||||
if (progress.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(ProgressBarHandler handler, IProgress progress)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (progress is VisualElement visualElement && visualElement.BackgroundColor is not null)
|
||||
{
|
||||
handler.PlatformView.TrackColor = visualElement.BackgroundColor;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapHeight(ProgressBarHandler handler, IProgress progress)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (progress is VisualElement visualElement && visualElement.HeightRequest >= 0)
|
||||
{
|
||||
handler.PlatformView.HeightRequest = visualElement.HeightRequest;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapWidth(ProgressBarHandler handler, IProgress progress)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (progress is VisualElement visualElement && visualElement.WidthRequest >= 0)
|
||||
{
|
||||
handler.PlatformView.WidthRequest = visualElement.WidthRequest;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapVerticalOptions(ProgressBarHandler handler, IProgress progress)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (progress is Microsoft.Maui.Controls.View view)
|
||||
{
|
||||
handler.PlatformView.VerticalOptions = view.VerticalOptions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ public partial class RadioButtonHandler : ViewHandler<IRadioButton, SkiaRadioBut
|
||||
|
||||
if (radioButton.TextColor is not null)
|
||||
{
|
||||
handler.PlatformView.TextColor = radioButton.TextColor.ToSKColor();
|
||||
handler.PlatformView.TextColor = radioButton.TextColor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ public partial class RadioButtonHandler : ViewHandler<IRadioButton, SkiaRadioBut
|
||||
|
||||
if (radioButton.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
151
Handlers/RefreshViewHandler.cs
Normal file
151
Handlers/RefreshViewHandler.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Platform;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for RefreshView on Linux using Skia rendering.
|
||||
/// Maps RefreshView to SkiaRefreshView platform view.
|
||||
/// </summary>
|
||||
public partial class RefreshViewHandler : ViewHandler<RefreshView, SkiaRefreshView>
|
||||
{
|
||||
private bool _isUpdatingRefreshing;
|
||||
|
||||
public static IPropertyMapper<RefreshView, RefreshViewHandler> Mapper =
|
||||
new PropertyMapper<RefreshView, RefreshViewHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(RefreshView.Content)] = MapContent,
|
||||
[nameof(RefreshView.IsRefreshing)] = MapIsRefreshing,
|
||||
[nameof(RefreshView.RefreshColor)] = MapRefreshColor,
|
||||
[nameof(RefreshView.Command)] = MapCommand,
|
||||
[nameof(RefreshView.CommandParameter)] = MapCommandParameter,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
};
|
||||
|
||||
public static CommandMapper<RefreshView, RefreshViewHandler> CommandMapper =
|
||||
new(ViewHandler.ViewCommandMapper)
|
||||
{
|
||||
};
|
||||
|
||||
public RefreshViewHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public RefreshViewHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaRefreshView CreatePlatformView()
|
||||
{
|
||||
return new SkiaRefreshView();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaRefreshView platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.Refreshing += OnRefreshing;
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaRefreshView platformView)
|
||||
{
|
||||
platformView.Refreshing -= OnRefreshing;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnRefreshing(object? sender, EventArgs e)
|
||||
{
|
||||
if (VirtualView is null || _isUpdatingRefreshing) return;
|
||||
|
||||
try
|
||||
{
|
||||
_isUpdatingRefreshing = true;
|
||||
|
||||
// Notify the virtual view that refreshing has started
|
||||
VirtualView.IsRefreshing = true;
|
||||
|
||||
// The command will be executed by the platform view
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isUpdatingRefreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapContent(RefreshViewHandler handler, RefreshView refreshView)
|
||||
{
|
||||
if (handler.PlatformView is null || handler.MauiContext is null) return;
|
||||
|
||||
var content = refreshView.Content;
|
||||
if (content == null)
|
||||
{
|
||||
handler.PlatformView.Content = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create handler for content
|
||||
if (content.Handler == null)
|
||||
{
|
||||
content.Handler = content.ToViewHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (content.Handler?.PlatformView is SkiaView skiaContent)
|
||||
{
|
||||
handler.PlatformView.Content = skiaContent;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIsRefreshing(RefreshViewHandler handler, RefreshView refreshView)
|
||||
{
|
||||
if (handler.PlatformView is null || handler._isUpdatingRefreshing) return;
|
||||
|
||||
try
|
||||
{
|
||||
handler._isUpdatingRefreshing = true;
|
||||
handler.PlatformView.IsRefreshing = refreshView.IsRefreshing;
|
||||
}
|
||||
finally
|
||||
{
|
||||
handler._isUpdatingRefreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapRefreshColor(RefreshViewHandler handler, RefreshView refreshView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (refreshView.RefreshColor is not null)
|
||||
{
|
||||
handler.PlatformView.RefreshColor = refreshView.RefreshColor;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapCommand(RefreshViewHandler handler, RefreshView refreshView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.Command = refreshView.Command;
|
||||
}
|
||||
|
||||
public static void MapCommandParameter(RefreshViewHandler handler, RefreshView refreshView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.CommandParameter = refreshView.CommandParameter;
|
||||
}
|
||||
|
||||
public static void MapBackground(RefreshViewHandler handler, RefreshView refreshView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (refreshView.Background is SolidColorBrush solidBrush)
|
||||
{
|
||||
handler.PlatformView.RefreshBackgroundColor = solidBrush.Color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
@@ -52,7 +53,7 @@ public partial class ScrollViewHandler : ViewHandler<IScrollView, SkiaScrollView
|
||||
// Create handler for content if it doesn't exist
|
||||
if (content.Handler == null)
|
||||
{
|
||||
content.Handler = content.ToHandler(handler.MauiContext);
|
||||
content.Handler = content.ToViewHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (content.Handler?.PlatformView is SkiaView skiaContent)
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
|
||||
namespace Microsoft.Maui.Platform;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for SearchBar control.
|
||||
/// </summary>
|
||||
public partial class SearchBarHandler : ViewHandler<ISearchBar, SkiaSearchBar>
|
||||
{
|
||||
public static IPropertyMapper<ISearchBar, SearchBarHandler> Mapper = new PropertyMapper<ISearchBar, SearchBarHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(ISearchBar.Text)] = MapText,
|
||||
[nameof(ISearchBar.Placeholder)] = MapPlaceholder,
|
||||
[nameof(ISearchBar.PlaceholderColor)] = MapPlaceholderColor,
|
||||
[nameof(ISearchBar.TextColor)] = MapTextColor,
|
||||
[nameof(ISearchBar.Font)] = MapFont,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
};
|
||||
|
||||
public static CommandMapper<ISearchBar, SearchBarHandler> CommandMapper = new(ViewHandler.ViewCommandMapper);
|
||||
|
||||
public SearchBarHandler() : base(Mapper, CommandMapper) { }
|
||||
|
||||
protected override SkiaSearchBar CreatePlatformView() => new SkiaSearchBar();
|
||||
|
||||
protected override void ConnectHandler(SkiaSearchBar platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.TextChanged += OnTextChanged;
|
||||
platformView.SearchButtonPressed += OnSearchButtonPressed;
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaSearchBar platformView)
|
||||
{
|
||||
platformView.TextChanged -= OnTextChanged;
|
||||
platformView.SearchButtonPressed -= OnSearchButtonPressed;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnTextChanged(object? sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (VirtualView != null && VirtualView.Text != e.NewText)
|
||||
{
|
||||
VirtualView.Text = e.NewText;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSearchButtonPressed(object? sender, EventArgs e)
|
||||
{
|
||||
VirtualView?.SearchButtonPressed();
|
||||
}
|
||||
|
||||
public static void MapText(SearchBarHandler handler, ISearchBar searchBar)
|
||||
{
|
||||
if (handler.PlatformView.Text != searchBar.Text)
|
||||
{
|
||||
handler.PlatformView.Text = searchBar.Text ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapPlaceholder(SearchBarHandler handler, ISearchBar searchBar)
|
||||
{
|
||||
handler.PlatformView.Placeholder = searchBar.Placeholder ?? "";
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapPlaceholderColor(SearchBarHandler handler, ISearchBar searchBar)
|
||||
{
|
||||
if (searchBar.PlaceholderColor != null)
|
||||
handler.PlatformView.PlaceholderColor = searchBar.PlaceholderColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapTextColor(SearchBarHandler handler, ISearchBar searchBar)
|
||||
{
|
||||
if (searchBar.TextColor != null)
|
||||
handler.PlatformView.TextColor = searchBar.TextColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapFont(SearchBarHandler handler, ISearchBar searchBar)
|
||||
{
|
||||
var font = searchBar.Font;
|
||||
if (font.Family != null)
|
||||
handler.PlatformView.FontFamily = font.Family;
|
||||
handler.PlatformView.FontSize = (float)font.Size;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(SearchBarHandler handler, ISearchBar searchBar)
|
||||
{
|
||||
handler.PlatformView.IsEnabled = searchBar.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackground(SearchBarHandler handler, ISearchBar searchBar)
|
||||
{
|
||||
if (searchBar.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,11 @@ public partial class SearchBarHandler : ViewHandler<ISearchBar, SkiaSearchBar>
|
||||
[nameof(ITextInput.Text)] = MapText,
|
||||
[nameof(ITextStyle.TextColor)] = MapTextColor,
|
||||
[nameof(ITextStyle.Font)] = MapFont,
|
||||
[nameof(ITextStyle.CharacterSpacing)] = MapCharacterSpacing,
|
||||
[nameof(IPlaceholder.Placeholder)] = MapPlaceholder,
|
||||
[nameof(IPlaceholder.PlaceholderColor)] = MapPlaceholderColor,
|
||||
[nameof(ISearchBar.CancelButtonColor)] = MapCancelButtonColor,
|
||||
[nameof(ISearchBar.HorizontalTextAlignment)] = MapHorizontalTextAlignment,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
};
|
||||
|
||||
@@ -84,7 +86,7 @@ public partial class SearchBarHandler : ViewHandler<ISearchBar, SkiaSearchBar>
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (searchBar.TextColor is not null)
|
||||
handler.PlatformView.TextColor = searchBar.TextColor.ToSKColor();
|
||||
handler.PlatformView.TextColor = searchBar.TextColor;
|
||||
}
|
||||
|
||||
public static void MapFont(SearchBarHandler handler, ISearchBar searchBar)
|
||||
@@ -93,10 +95,28 @@ public partial class SearchBarHandler : ViewHandler<ISearchBar, SkiaSearchBar>
|
||||
|
||||
var font = searchBar.Font;
|
||||
if (font.Size > 0)
|
||||
handler.PlatformView.FontSize = (float)font.Size;
|
||||
handler.PlatformView.FontSize = font.Size;
|
||||
|
||||
if (!string.IsNullOrEmpty(font.Family))
|
||||
handler.PlatformView.FontFamily = font.Family;
|
||||
|
||||
// Map FontAttributes from the Font weight
|
||||
var attrs = FontAttributes.None;
|
||||
if (font.Weight >= FontWeight.Bold)
|
||||
attrs |= FontAttributes.Bold;
|
||||
handler.PlatformView.FontAttributes = attrs;
|
||||
}
|
||||
|
||||
public static void MapCharacterSpacing(SearchBarHandler handler, ISearchBar searchBar)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.CharacterSpacing = searchBar.CharacterSpacing;
|
||||
}
|
||||
|
||||
public static void MapHorizontalTextAlignment(SearchBarHandler handler, ISearchBar searchBar)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.HorizontalTextAlignment = searchBar.HorizontalTextAlignment;
|
||||
}
|
||||
|
||||
public static void MapPlaceholder(SearchBarHandler handler, ISearchBar searchBar)
|
||||
@@ -110,7 +130,7 @@ public partial class SearchBarHandler : ViewHandler<ISearchBar, SkiaSearchBar>
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (searchBar.PlaceholderColor is not null)
|
||||
handler.PlatformView.PlaceholderColor = searchBar.PlaceholderColor.ToSKColor();
|
||||
handler.PlatformView.PlaceholderColor = searchBar.PlaceholderColor;
|
||||
}
|
||||
|
||||
public static void MapCancelButtonColor(SearchBarHandler handler, ISearchBar searchBar)
|
||||
@@ -119,7 +139,7 @@ public partial class SearchBarHandler : ViewHandler<ISearchBar, SkiaSearchBar>
|
||||
|
||||
// CancelButtonColor maps to ClearButtonColor
|
||||
if (searchBar.CancelButtonColor is not null)
|
||||
handler.PlatformView.ClearButtonColor = searchBar.CancelButtonColor.ToSKColor();
|
||||
handler.PlatformView.ClearButtonColor = searchBar.CancelButtonColor;
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +149,7 @@ public partial class SearchBarHandler : ViewHandler<ISearchBar, SkiaSearchBar>
|
||||
|
||||
if (searchBar.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
@@ -13,12 +14,27 @@ namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
/// </summary>
|
||||
public partial class ShellHandler : ViewHandler<Shell, SkiaShell>
|
||||
{
|
||||
private bool _isUpdatingFlyoutPresented;
|
||||
|
||||
public static IPropertyMapper<Shell, ShellHandler> Mapper = new PropertyMapper<Shell, ShellHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(Shell.FlyoutIsPresented)] = MapFlyoutIsPresented,
|
||||
[nameof(Shell.FlyoutBehavior)] = MapFlyoutBehavior,
|
||||
[nameof(Shell.FlyoutWidth)] = MapFlyoutWidth,
|
||||
[nameof(Shell.FlyoutBackgroundColor)] = MapFlyoutBackgroundColor,
|
||||
[nameof(Shell.FlyoutBackground)] = MapFlyoutBackground,
|
||||
[nameof(Shell.BackgroundColor)] = MapBackgroundColor,
|
||||
[nameof(Shell.FlyoutHeaderBehavior)] = MapFlyoutHeaderBehavior,
|
||||
[nameof(Shell.FlyoutHeader)] = MapFlyoutHeader,
|
||||
[nameof(Shell.FlyoutFooter)] = MapFlyoutFooter,
|
||||
[nameof(Shell.Items)] = MapItems,
|
||||
[nameof(Shell.CurrentItem)] = MapCurrentItem,
|
||||
[nameof(Shell.Title)] = MapTitle,
|
||||
};
|
||||
|
||||
public static CommandMapper<Shell, ShellHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
{
|
||||
["GoToAsync"] = MapGoToAsync,
|
||||
};
|
||||
|
||||
public ShellHandler() : base(Mapper, CommandMapper)
|
||||
@@ -32,20 +48,32 @@ public partial class ShellHandler : ViewHandler<Shell, SkiaShell>
|
||||
|
||||
protected override SkiaShell CreatePlatformView()
|
||||
{
|
||||
Console.WriteLine("[ShellHandler] CreatePlatformView - creating SkiaShell");
|
||||
return new SkiaShell();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaShell platformView)
|
||||
{
|
||||
Console.WriteLine("[ShellHandler] ConnectHandler - connecting to SkiaShell");
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.FlyoutIsPresentedChanged += OnFlyoutIsPresentedChanged;
|
||||
platformView.Navigated += OnNavigated;
|
||||
|
||||
// Store reference to MAUI Shell for callbacks
|
||||
platformView.MauiShell = VirtualView;
|
||||
|
||||
// Set up content renderer
|
||||
platformView.ContentRenderer = RenderShellContent;
|
||||
platformView.ColorRefresher = RefreshShellColors;
|
||||
|
||||
// Subscribe to Shell navigation events
|
||||
if (VirtualView != null)
|
||||
{
|
||||
VirtualView.Navigating += OnShellNavigating;
|
||||
VirtualView.Navigated += OnShellNavigated;
|
||||
|
||||
// Initial sync of shell items
|
||||
SyncShellItems();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +81,9 @@ public partial class ShellHandler : ViewHandler<Shell, SkiaShell>
|
||||
{
|
||||
platformView.FlyoutIsPresentedChanged -= OnFlyoutIsPresentedChanged;
|
||||
platformView.Navigated -= OnNavigated;
|
||||
platformView.MauiShell = null;
|
||||
platformView.ContentRenderer = null;
|
||||
platformView.ColorRefresher = null;
|
||||
|
||||
if (VirtualView != null)
|
||||
{
|
||||
@@ -65,10 +96,20 @@ public partial class ShellHandler : ViewHandler<Shell, SkiaShell>
|
||||
|
||||
private void OnFlyoutIsPresentedChanged(object? sender, EventArgs e)
|
||||
{
|
||||
// Sync flyout state to virtual view
|
||||
if (VirtualView is null || PlatformView is null || _isUpdatingFlyoutPresented) return;
|
||||
|
||||
try
|
||||
{
|
||||
_isUpdatingFlyoutPresented = true;
|
||||
VirtualView.FlyoutIsPresented = PlatformView.FlyoutIsPresented;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isUpdatingFlyoutPresented = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNavigated(object? sender, ShellNavigationEventArgs e)
|
||||
private void OnNavigated(object? sender, Platform.ShellNavigationEventArgs e)
|
||||
{
|
||||
// Handle platform navigation events
|
||||
}
|
||||
@@ -90,4 +131,289 @@ public partial class ShellHandler : ViewHandler<Shell, SkiaShell>
|
||||
{
|
||||
Console.WriteLine($"[ShellHandler] Shell Navigated to: {e.Current?.Location}");
|
||||
}
|
||||
|
||||
private void SyncShellItems()
|
||||
{
|
||||
if (PlatformView is null || VirtualView is null || MauiContext is null) return;
|
||||
|
||||
// Clear existing sections
|
||||
foreach (var section in PlatformView.Sections.ToList())
|
||||
{
|
||||
PlatformView.RemoveSection(section);
|
||||
}
|
||||
|
||||
// Add shell items as sections
|
||||
foreach (var item in VirtualView.Items)
|
||||
{
|
||||
if (item is FlyoutItem flyoutItem)
|
||||
{
|
||||
var section = new Platform.ShellSection
|
||||
{
|
||||
Route = flyoutItem.Route ?? flyoutItem.Title ?? "",
|
||||
Title = flyoutItem.Title ?? "",
|
||||
IconPath = flyoutItem.Icon?.ToString()
|
||||
};
|
||||
|
||||
// Add shell contents as items
|
||||
foreach (var shellSection in flyoutItem.Items)
|
||||
{
|
||||
foreach (var content in shellSection.Items)
|
||||
{
|
||||
var contentItem = new Platform.ShellContent
|
||||
{
|
||||
Route = content.Route ?? content.Title ?? "",
|
||||
Title = content.Title ?? "",
|
||||
IconPath = content.Icon?.ToString(),
|
||||
MauiShellContent = content,
|
||||
Content = RenderShellContent(content)
|
||||
};
|
||||
section.Items.Add(contentItem);
|
||||
}
|
||||
}
|
||||
|
||||
PlatformView.AddSection(section);
|
||||
}
|
||||
else if (item is ShellItem shellItem)
|
||||
{
|
||||
var section = new Platform.ShellSection
|
||||
{
|
||||
Route = shellItem.Route ?? shellItem.Title ?? "",
|
||||
Title = shellItem.Title ?? "",
|
||||
IconPath = shellItem.Icon?.ToString()
|
||||
};
|
||||
|
||||
foreach (var shellSection in shellItem.Items)
|
||||
{
|
||||
foreach (var content in shellSection.Items)
|
||||
{
|
||||
var contentItem = new Platform.ShellContent
|
||||
{
|
||||
Route = content.Route ?? content.Title ?? "",
|
||||
Title = content.Title ?? "",
|
||||
IconPath = content.Icon?.ToString(),
|
||||
MauiShellContent = content,
|
||||
Content = RenderShellContent(content)
|
||||
};
|
||||
section.Items.Add(contentItem);
|
||||
}
|
||||
}
|
||||
|
||||
PlatformView.AddSection(section);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SkiaView? RenderShellContent(Microsoft.Maui.Controls.ShellContent content)
|
||||
{
|
||||
if (MauiContext is null) return null;
|
||||
|
||||
try
|
||||
{
|
||||
var page = content.Content as Page;
|
||||
if (page == null && content.ContentTemplate != null)
|
||||
{
|
||||
page = content.ContentTemplate.CreateContent() as Page;
|
||||
}
|
||||
|
||||
if (page != null)
|
||||
{
|
||||
if (page.Handler == null)
|
||||
{
|
||||
page.Handler = page.ToViewHandler(MauiContext);
|
||||
}
|
||||
|
||||
if (page.Handler?.PlatformView is SkiaView skiaView)
|
||||
{
|
||||
return skiaView;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[ShellHandler] Error rendering content: {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void RefreshShellColors(SkiaShell platformView, Shell shell)
|
||||
{
|
||||
// Sync flyout colors
|
||||
if (shell.FlyoutBackgroundColor is Color flyoutBgColor)
|
||||
{
|
||||
platformView.FlyoutBackgroundColor = flyoutBgColor;
|
||||
}
|
||||
else if (shell.FlyoutBackground is SolidColorBrush flyoutBrush)
|
||||
{
|
||||
platformView.FlyoutBackgroundColor = flyoutBrush.Color;
|
||||
}
|
||||
|
||||
// Sync nav bar colors
|
||||
if (shell.BackgroundColor is Color bgColor)
|
||||
{
|
||||
platformView.NavBarBackgroundColor = bgColor;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapFlyoutIsPresented(ShellHandler handler, Shell shell)
|
||||
{
|
||||
if (handler.PlatformView is null || handler._isUpdatingFlyoutPresented) return;
|
||||
|
||||
try
|
||||
{
|
||||
handler._isUpdatingFlyoutPresented = true;
|
||||
handler.PlatformView.FlyoutIsPresented = shell.FlyoutIsPresented;
|
||||
}
|
||||
finally
|
||||
{
|
||||
handler._isUpdatingFlyoutPresented = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapFlyoutBehavior(ShellHandler handler, Shell shell)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
handler.PlatformView.FlyoutBehavior = shell.FlyoutBehavior switch
|
||||
{
|
||||
Microsoft.Maui.FlyoutBehavior.Disabled => ShellFlyoutBehavior.Disabled,
|
||||
Microsoft.Maui.FlyoutBehavior.Flyout => ShellFlyoutBehavior.Flyout,
|
||||
Microsoft.Maui.FlyoutBehavior.Locked => ShellFlyoutBehavior.Locked,
|
||||
_ => ShellFlyoutBehavior.Flyout
|
||||
};
|
||||
}
|
||||
|
||||
public static void MapFlyoutWidth(ShellHandler handler, Shell shell)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.FlyoutWidth = (float)shell.FlyoutWidth;
|
||||
}
|
||||
|
||||
public static void MapFlyoutBackgroundColor(ShellHandler handler, Shell shell)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (shell.FlyoutBackgroundColor is Color color)
|
||||
{
|
||||
handler.PlatformView.FlyoutBackgroundColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapFlyoutBackground(ShellHandler handler, Shell shell)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (shell.FlyoutBackground is SolidColorBrush solidBrush)
|
||||
{
|
||||
handler.PlatformView.FlyoutBackgroundColor = solidBrush.Color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(ShellHandler handler, Shell shell)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (shell.BackgroundColor is Color color)
|
||||
{
|
||||
handler.PlatformView.NavBarBackgroundColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapFlyoutHeaderBehavior(ShellHandler handler, Shell shell)
|
||||
{
|
||||
// Flyout header behavior - handled by platform view
|
||||
}
|
||||
|
||||
public static void MapFlyoutHeader(ShellHandler handler, Shell shell)
|
||||
{
|
||||
if (handler.PlatformView is null || handler.MauiContext is null) return;
|
||||
|
||||
var header = shell.FlyoutHeader;
|
||||
if (header == null)
|
||||
{
|
||||
handler.PlatformView.FlyoutHeaderView = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (header is View headerView)
|
||||
{
|
||||
if (headerView.Handler == null)
|
||||
{
|
||||
headerView.Handler = headerView.ToViewHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (headerView.Handler?.PlatformView is SkiaView skiaHeader)
|
||||
{
|
||||
handler.PlatformView.FlyoutHeaderView = skiaHeader;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapFlyoutFooter(ShellHandler handler, Shell shell)
|
||||
{
|
||||
if (handler.PlatformView is null || handler.MauiContext is null) return;
|
||||
|
||||
var footer = shell.FlyoutFooter;
|
||||
if (footer == null)
|
||||
{
|
||||
handler.PlatformView.FlyoutFooterText = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Simple text footer support
|
||||
if (footer is Label label)
|
||||
{
|
||||
handler.PlatformView.FlyoutFooterText = label.Text;
|
||||
}
|
||||
else if (footer is string text)
|
||||
{
|
||||
handler.PlatformView.FlyoutFooterText = text;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapItems(ShellHandler handler, Shell shell)
|
||||
{
|
||||
handler.SyncShellItems();
|
||||
}
|
||||
|
||||
public static void MapCurrentItem(ShellHandler handler, Shell shell)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
// Sync current item selection
|
||||
var currentItem = shell.CurrentItem;
|
||||
if (currentItem != null)
|
||||
{
|
||||
// Find matching section index
|
||||
for (int i = 0; i < handler.PlatformView.Sections.Count; i++)
|
||||
{
|
||||
var section = handler.PlatformView.Sections[i];
|
||||
if (section.Route == (currentItem.Route ?? currentItem.Title))
|
||||
{
|
||||
handler.PlatformView.NavigateToSection(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapTitle(ShellHandler handler, Shell shell)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.Title = shell.Title ?? "";
|
||||
}
|
||||
|
||||
public static void MapGoToAsync(ShellHandler handler, Shell shell, object? args)
|
||||
{
|
||||
if (handler.PlatformView is null || args is null) return;
|
||||
|
||||
if (args is ShellNavigationState state)
|
||||
{
|
||||
handler.PlatformView.GoToAsync(state.Location.ToString());
|
||||
}
|
||||
else if (args is string route)
|
||||
{
|
||||
handler.PlatformView.GoToAsync(route);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
|
||||
namespace Microsoft.Maui.Platform;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for Slider control.
|
||||
/// </summary>
|
||||
public partial class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
||||
{
|
||||
public static IPropertyMapper<ISlider, SliderHandler> Mapper = new PropertyMapper<ISlider, SliderHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(ISlider.Minimum)] = MapMinimum,
|
||||
[nameof(ISlider.Maximum)] = MapMaximum,
|
||||
[nameof(ISlider.Value)] = MapValue,
|
||||
[nameof(ISlider.MinimumTrackColor)] = MapMinimumTrackColor,
|
||||
[nameof(ISlider.MaximumTrackColor)] = MapMaximumTrackColor,
|
||||
[nameof(ISlider.ThumbColor)] = MapThumbColor,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
public static CommandMapper<ISlider, SliderHandler> CommandMapper = new(ViewHandler.ViewCommandMapper);
|
||||
|
||||
public SliderHandler() : base(Mapper, CommandMapper) { }
|
||||
|
||||
protected override SkiaSlider CreatePlatformView() => new SkiaSlider();
|
||||
|
||||
protected override void ConnectHandler(SkiaSlider platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.ValueChanged += OnValueChanged;
|
||||
platformView.DragStarted += OnDragStarted;
|
||||
platformView.DragCompleted += OnDragCompleted;
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaSlider platformView)
|
||||
{
|
||||
platformView.ValueChanged -= OnValueChanged;
|
||||
platformView.DragStarted -= OnDragStarted;
|
||||
platformView.DragCompleted -= OnDragCompleted;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnValueChanged(object? sender, SliderValueChangedEventArgs e)
|
||||
{
|
||||
if (VirtualView != null && Math.Abs(VirtualView.Value - e.NewValue) > 0.001)
|
||||
{
|
||||
VirtualView.Value = e.NewValue;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDragStarted(object? sender, EventArgs e) => VirtualView?.DragStarted();
|
||||
private void OnDragCompleted(object? sender, EventArgs e) => VirtualView?.DragCompleted();
|
||||
|
||||
public static void MapMinimum(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
handler.PlatformView.Minimum = slider.Minimum;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapMaximum(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
handler.PlatformView.Maximum = slider.Maximum;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapValue(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
if (Math.Abs(handler.PlatformView.Value - slider.Value) > 0.001)
|
||||
{
|
||||
handler.PlatformView.Value = slider.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapMinimumTrackColor(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
if (slider.MinimumTrackColor != null)
|
||||
handler.PlatformView.ActiveTrackColor = slider.MinimumTrackColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapMaximumTrackColor(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
if (slider.MaximumTrackColor != null)
|
||||
handler.PlatformView.TrackColor = slider.MaximumTrackColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapThumbColor(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
if (slider.ThumbColor != null)
|
||||
handler.PlatformView.ThumbColor = slider.ThumbColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
handler.PlatformView.IsEnabled = slider.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackground(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
if (slider.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
if (slider is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ public partial class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnValueChanged(object? sender, SliderValueChangedEventArgs e)
|
||||
private void OnValueChanged(object? sender, ValueChangedEventArgs e)
|
||||
{
|
||||
if (VirtualView is null || PlatformView is null) return;
|
||||
|
||||
@@ -112,18 +112,16 @@ public partial class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
// MinimumTrackColor maps to ActiveTrackColor (the filled portion)
|
||||
if (slider.MinimumTrackColor is not null)
|
||||
handler.PlatformView.ActiveTrackColor = slider.MinimumTrackColor.ToSKColor();
|
||||
handler.PlatformView.MinimumTrackColor = slider.MinimumTrackColor;
|
||||
}
|
||||
|
||||
public static void MapMaximumTrackColor(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
// MaximumTrackColor maps to TrackColor (the unfilled portion)
|
||||
if (slider.MaximumTrackColor is not null)
|
||||
handler.PlatformView.TrackColor = slider.MaximumTrackColor.ToSKColor();
|
||||
handler.PlatformView.MaximumTrackColor = slider.MaximumTrackColor;
|
||||
}
|
||||
|
||||
public static void MapThumbColor(SliderHandler handler, ISlider slider)
|
||||
@@ -131,7 +129,7 @@ public partial class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (slider.ThumbColor is not null)
|
||||
handler.PlatformView.ThumbColor = slider.ThumbColor.ToSKColor();
|
||||
handler.PlatformView.ThumbColor = slider.ThumbColor;
|
||||
}
|
||||
|
||||
public static void MapBackground(SliderHandler handler, ISlider slider)
|
||||
@@ -140,7 +138,7 @@ public partial class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
||||
|
||||
if (slider.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Platform;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for Stepper on Linux using Skia rendering.
|
||||
/// Maps IStepper interface to SkiaStepper platform view.
|
||||
/// </summary>
|
||||
public partial class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
||||
{
|
||||
@@ -19,7 +20,9 @@ public partial class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
||||
[nameof(IStepper.Value)] = MapValue,
|
||||
[nameof(IStepper.Minimum)] = MapMinimum,
|
||||
[nameof(IStepper.Maximum)] = MapMaximum,
|
||||
["Increment"] = MapIncrement,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
};
|
||||
|
||||
public static CommandMapper<IStepper, StepperHandler> CommandMapper =
|
||||
@@ -45,6 +48,26 @@ public partial class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.ValueChanged += OnValueChanged;
|
||||
|
||||
// Apply dark theme colors if needed
|
||||
if (Application.Current?.UserAppTheme == AppTheme.Dark)
|
||||
{
|
||||
platformView.ButtonBackgroundColor = Color.FromRgb(66, 66, 66);
|
||||
platformView.ButtonPressedColor = Color.FromRgb(97, 97, 97);
|
||||
platformView.ButtonDisabledColor = Color.FromRgb(48, 48, 48);
|
||||
platformView.SymbolColor = Color.FromRgb(224, 224, 224);
|
||||
platformView.SymbolDisabledColor = Color.FromRgb(97, 97, 97);
|
||||
platformView.BorderColor = Color.FromRgb(97, 97, 97);
|
||||
}
|
||||
|
||||
// Sync properties
|
||||
if (VirtualView != null)
|
||||
{
|
||||
MapValue(this, VirtualView);
|
||||
MapMinimum(this, VirtualView);
|
||||
MapMaximum(this, VirtualView);
|
||||
MapIsEnabled(this, VirtualView);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaStepper platformView)
|
||||
@@ -53,15 +76,21 @@ public partial class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnValueChanged(object? sender, EventArgs e)
|
||||
private void OnValueChanged(object? sender, ValueChangedEventArgs e)
|
||||
{
|
||||
if (VirtualView is null || PlatformView is null) return;
|
||||
VirtualView.Value = PlatformView.Value;
|
||||
|
||||
if (Math.Abs(VirtualView.Value - e.NewValue) > 0.0001)
|
||||
{
|
||||
VirtualView.Value = e.NewValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapValue(StepperHandler handler, IStepper stepper)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (Math.Abs(handler.PlatformView.Value - stepper.Value) > 0.0001)
|
||||
handler.PlatformView.Value = stepper.Value;
|
||||
}
|
||||
|
||||
@@ -83,7 +112,24 @@ public partial class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
||||
|
||||
if (stepper.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIncrement(StepperHandler handler, IStepper stepper)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (stepper is Stepper stepperControl)
|
||||
{
|
||||
handler.PlatformView.Increment = stepperControl.Increment;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(StepperHandler handler, IStepper stepper)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.IsEnabled = stepper.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
226
Handlers/SwipeViewHandler.cs
Normal file
226
Handlers/SwipeViewHandler.cs
Normal file
@@ -0,0 +1,226 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Platform;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for SwipeView on Linux using Skia rendering.
|
||||
/// Maps SwipeView to SkiaSwipeView platform view.
|
||||
/// </summary>
|
||||
public partial class SwipeViewHandler : ViewHandler<SwipeView, SkiaSwipeView>
|
||||
{
|
||||
public static IPropertyMapper<SwipeView, SwipeViewHandler> Mapper =
|
||||
new PropertyMapper<SwipeView, SwipeViewHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(SwipeView.Content)] = MapContent,
|
||||
[nameof(SwipeView.LeftItems)] = MapLeftItems,
|
||||
[nameof(SwipeView.RightItems)] = MapRightItems,
|
||||
[nameof(SwipeView.TopItems)] = MapTopItems,
|
||||
[nameof(SwipeView.BottomItems)] = MapBottomItems,
|
||||
[nameof(SwipeView.Threshold)] = MapThreshold,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
};
|
||||
|
||||
public static CommandMapper<SwipeView, SwipeViewHandler> CommandMapper =
|
||||
new(ViewHandler.ViewCommandMapper)
|
||||
{
|
||||
["RequestOpen"] = MapRequestOpen,
|
||||
["RequestClose"] = MapRequestClose,
|
||||
};
|
||||
|
||||
public SwipeViewHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public SwipeViewHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaSwipeView CreatePlatformView()
|
||||
{
|
||||
return new SkiaSwipeView();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaSwipeView platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.SwipeStarted += OnSwipeStarted;
|
||||
platformView.SwipeEnded += OnSwipeEnded;
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaSwipeView platformView)
|
||||
{
|
||||
platformView.SwipeStarted -= OnSwipeStarted;
|
||||
platformView.SwipeEnded -= OnSwipeEnded;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnSwipeStarted(object? sender, Platform.SwipeStartedEventArgs e)
|
||||
{
|
||||
// SwipeView events are handled internally by the platform view
|
||||
}
|
||||
|
||||
private void OnSwipeEnded(object? sender, Platform.SwipeEndedEventArgs e)
|
||||
{
|
||||
// SwipeView events are handled internally by the platform view
|
||||
}
|
||||
|
||||
public static void MapContent(SwipeViewHandler handler, SwipeView swipeView)
|
||||
{
|
||||
if (handler.PlatformView is null || handler.MauiContext is null) return;
|
||||
|
||||
var content = swipeView.Content;
|
||||
if (content == null)
|
||||
{
|
||||
handler.PlatformView.Content = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create handler for content
|
||||
if (content.Handler == null)
|
||||
{
|
||||
content.Handler = content.ToViewHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (content.Handler?.PlatformView is SkiaView skiaContent)
|
||||
{
|
||||
handler.PlatformView.Content = skiaContent;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapLeftItems(SwipeViewHandler handler, SwipeView swipeView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
handler.PlatformView.LeftItems.Clear();
|
||||
|
||||
if (swipeView.LeftItems != null)
|
||||
{
|
||||
foreach (var item in swipeView.LeftItems)
|
||||
{
|
||||
handler.PlatformView.LeftItems.Add(CreatePlatformSwipeItem(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapRightItems(SwipeViewHandler handler, SwipeView swipeView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
handler.PlatformView.RightItems.Clear();
|
||||
|
||||
if (swipeView.RightItems != null)
|
||||
{
|
||||
foreach (var item in swipeView.RightItems)
|
||||
{
|
||||
handler.PlatformView.RightItems.Add(CreatePlatformSwipeItem(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapTopItems(SwipeViewHandler handler, SwipeView swipeView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
handler.PlatformView.TopItems.Clear();
|
||||
|
||||
if (swipeView.TopItems != null)
|
||||
{
|
||||
foreach (var item in swipeView.TopItems)
|
||||
{
|
||||
handler.PlatformView.TopItems.Add(CreatePlatformSwipeItem(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBottomItems(SwipeViewHandler handler, SwipeView swipeView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
handler.PlatformView.BottomItems.Clear();
|
||||
|
||||
if (swipeView.BottomItems != null)
|
||||
{
|
||||
foreach (var item in swipeView.BottomItems)
|
||||
{
|
||||
handler.PlatformView.BottomItems.Add(CreatePlatformSwipeItem(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapThreshold(SwipeViewHandler handler, SwipeView swipeView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
handler.PlatformView.LeftSwipeThreshold = (float)swipeView.Threshold;
|
||||
handler.PlatformView.RightSwipeThreshold = (float)swipeView.Threshold;
|
||||
}
|
||||
|
||||
public static void MapBackground(SwipeViewHandler handler, SwipeView swipeView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (swipeView.Background is SolidColorBrush solidBrush)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapRequestOpen(SwipeViewHandler handler, SwipeView swipeView, object? args)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (args is SwipeViewOpenRequest request)
|
||||
{
|
||||
var direction = request.OpenSwipeItem switch
|
||||
{
|
||||
OpenSwipeItem.LeftItems => Platform.SwipeDirection.Right,
|
||||
OpenSwipeItem.RightItems => Platform.SwipeDirection.Left,
|
||||
OpenSwipeItem.TopItems => Platform.SwipeDirection.Down,
|
||||
OpenSwipeItem.BottomItems => Platform.SwipeDirection.Up,
|
||||
_ => Platform.SwipeDirection.Right
|
||||
};
|
||||
|
||||
handler.PlatformView.Open(direction);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapRequestClose(SwipeViewHandler handler, SwipeView swipeView, object? args)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.Close();
|
||||
}
|
||||
|
||||
private static Platform.SwipeItem CreatePlatformSwipeItem(ISwipeItem item)
|
||||
{
|
||||
var platformItem = new Platform.SwipeItem();
|
||||
|
||||
if (item is Controls.SwipeItem swipeItem)
|
||||
{
|
||||
platformItem.Text = swipeItem.Text ?? "";
|
||||
|
||||
// Get background color
|
||||
var bgColor = swipeItem.BackgroundColor;
|
||||
if (bgColor is not null)
|
||||
{
|
||||
platformItem.BackgroundColor = bgColor;
|
||||
}
|
||||
}
|
||||
else if (item is Controls.SwipeItemView swipeItemView)
|
||||
{
|
||||
// SwipeItemView uses custom content - use a simple representation
|
||||
platformItem.Text = "Action";
|
||||
platformItem.BackgroundColor = Color.FromRgb(100, 100, 100);
|
||||
}
|
||||
|
||||
return platformItem;
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
|
||||
namespace Microsoft.Maui.Platform;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for Switch control.
|
||||
/// </summary>
|
||||
public partial class SwitchHandler : ViewHandler<ISwitch, SkiaSwitch>
|
||||
{
|
||||
public static IPropertyMapper<ISwitch, SwitchHandler> Mapper = new PropertyMapper<ISwitch, SwitchHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(ISwitch.IsOn)] = MapIsOn,
|
||||
[nameof(ISwitch.TrackColor)] = MapTrackColor,
|
||||
[nameof(ISwitch.ThumbColor)] = MapThumbColor,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
public static CommandMapper<ISwitch, SwitchHandler> CommandMapper = new(ViewHandler.ViewCommandMapper);
|
||||
|
||||
public SwitchHandler() : base(Mapper, CommandMapper) { }
|
||||
|
||||
protected override SkiaSwitch CreatePlatformView() => new SkiaSwitch();
|
||||
|
||||
protected override void ConnectHandler(SkiaSwitch platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.Toggled += OnToggled;
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaSwitch platformView)
|
||||
{
|
||||
platformView.Toggled -= OnToggled;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnToggled(object? sender, ToggledEventArgs e)
|
||||
{
|
||||
if (VirtualView != null && VirtualView.IsOn != e.Value)
|
||||
{
|
||||
VirtualView.IsOn = e.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIsOn(SwitchHandler handler, ISwitch @switch)
|
||||
{
|
||||
if (handler.PlatformView.IsOn != @switch.IsOn)
|
||||
{
|
||||
handler.PlatformView.IsOn = @switch.IsOn;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapTrackColor(SwitchHandler handler, ISwitch @switch)
|
||||
{
|
||||
if (@switch.TrackColor != null)
|
||||
handler.PlatformView.OnTrackColor = @switch.TrackColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapThumbColor(SwitchHandler handler, ISwitch @switch)
|
||||
{
|
||||
if (@switch.ThumbColor != null)
|
||||
handler.PlatformView.ThumbColor = @switch.ThumbColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(SwitchHandler handler, ISwitch @switch)
|
||||
{
|
||||
handler.PlatformView.IsEnabled = @switch.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackground(SwitchHandler handler, ISwitch @switch)
|
||||
{
|
||||
if (@switch.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(SwitchHandler handler, ISwitch @switch)
|
||||
{
|
||||
if (@switch is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ public partial class SwitchHandler : ViewHandler<ISwitch, SkiaSwitch>
|
||||
[nameof(ISwitch.TrackColor)] = MapTrackColor,
|
||||
[nameof(ISwitch.ThumbColor)] = MapThumbColor,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
};
|
||||
|
||||
public static CommandMapper<ISwitch, SwitchHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -69,13 +70,12 @@ public partial class SwitchHandler : ViewHandler<ISwitch, SkiaSwitch>
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
// TrackColor sets both On and Off track colors
|
||||
// TrackColor sets the On track color (MAUI's OnColor)
|
||||
if (@switch.TrackColor is not null)
|
||||
{
|
||||
var color = @switch.TrackColor.ToSKColor();
|
||||
handler.PlatformView.OnTrackColor = color;
|
||||
// Off track could be a lighter version
|
||||
handler.PlatformView.OffTrackColor = color.WithAlpha(128);
|
||||
handler.PlatformView.OnTrackColor = @switch.TrackColor;
|
||||
// Off track is a lighter/desaturated version
|
||||
handler.PlatformView.OffTrackColor = @switch.TrackColor.WithAlpha(0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ public partial class SwitchHandler : ViewHandler<ISwitch, SkiaSwitch>
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (@switch.ThumbColor is not null)
|
||||
handler.PlatformView.ThumbColor = @switch.ThumbColor.ToSKColor();
|
||||
handler.PlatformView.ThumbColor = @switch.ThumbColor;
|
||||
}
|
||||
|
||||
public static void MapBackground(SwitchHandler handler, ISwitch @switch)
|
||||
@@ -93,7 +93,14 @@ public partial class SwitchHandler : ViewHandler<ISwitch, SkiaSwitch>
|
||||
|
||||
if (@switch.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
// Background color for the switch container (not the track)
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(SwitchHandler handler, ISwitch @switch)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.IsEnabled = @switch.IsEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
@@ -13,8 +15,14 @@ namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
/// </summary>
|
||||
public partial class TabbedPageHandler : ViewHandler<ITabbedView, SkiaTabbedPage>
|
||||
{
|
||||
private bool _isUpdatingSelection;
|
||||
|
||||
public static IPropertyMapper<ITabbedView, TabbedPageHandler> Mapper = new PropertyMapper<ITabbedView, TabbedPageHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(TabbedPage.BarBackgroundColor)] = MapBarBackgroundColor,
|
||||
[nameof(TabbedPage.BarTextColor)] = MapBarTextColor,
|
||||
[nameof(TabbedPage.SelectedTabColor)] = MapSelectedTabColor,
|
||||
[nameof(TabbedPage.UnselectedTabColor)] = MapUnselectedTabColor,
|
||||
};
|
||||
|
||||
public static CommandMapper<ITabbedView, TabbedPageHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -39,6 +47,9 @@ public partial class TabbedPageHandler : ViewHandler<ITabbedView, SkiaTabbedPage
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.SelectedIndexChanged += OnSelectedIndexChanged;
|
||||
|
||||
// Sync initial tabs
|
||||
SyncTabs();
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaTabbedPage platformView)
|
||||
@@ -50,6 +61,104 @@ public partial class TabbedPageHandler : ViewHandler<ITabbedView, SkiaTabbedPage
|
||||
|
||||
private void OnSelectedIndexChanged(object? sender, EventArgs e)
|
||||
{
|
||||
// Notify the virtual view of selection change
|
||||
if (VirtualView is null || PlatformView is null || _isUpdatingSelection) return;
|
||||
|
||||
try
|
||||
{
|
||||
_isUpdatingSelection = true;
|
||||
|
||||
// Sync selected page back to virtual view
|
||||
if (VirtualView is TabbedPage tabbedPage && PlatformView.SelectedIndex >= 0)
|
||||
{
|
||||
var selectedIndex = PlatformView.SelectedIndex;
|
||||
if (selectedIndex < tabbedPage.Children.Count)
|
||||
{
|
||||
tabbedPage.CurrentPage = tabbedPage.Children[selectedIndex] as Page;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isUpdatingSelection = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void SyncTabs()
|
||||
{
|
||||
if (PlatformView is null || VirtualView is null || MauiContext is null) return;
|
||||
|
||||
PlatformView.ClearTabs();
|
||||
|
||||
if (VirtualView is TabbedPage tabbedPage)
|
||||
{
|
||||
foreach (var child in tabbedPage.Children)
|
||||
{
|
||||
if (child is Page page)
|
||||
{
|
||||
// Create handler for page content
|
||||
if (page.Handler == null)
|
||||
{
|
||||
page.Handler = page.ToViewHandler(MauiContext);
|
||||
}
|
||||
|
||||
if (page.Handler?.PlatformView is SkiaView skiaContent)
|
||||
{
|
||||
PlatformView.AddTab(page.Title ?? "Tab", skiaContent, page.IconImageSource?.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sync selected tab
|
||||
if (tabbedPage.CurrentPage != null)
|
||||
{
|
||||
var index = tabbedPage.Children.IndexOf(tabbedPage.CurrentPage);
|
||||
if (index >= 0)
|
||||
{
|
||||
PlatformView.SelectedIndex = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBarBackgroundColor(TabbedPageHandler handler, ITabbedView tabbedView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (tabbedView is TabbedPage tabbedPage && tabbedPage.BarBackgroundColor is Color color)
|
||||
{
|
||||
handler.PlatformView.TabBarBackgroundColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBarTextColor(TabbedPageHandler handler, ITabbedView tabbedView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (tabbedView is TabbedPage tabbedPage && tabbedPage.BarTextColor is Color color)
|
||||
{
|
||||
// BarTextColor applies to unselected tabs
|
||||
handler.PlatformView.UnselectedTabColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapSelectedTabColor(TabbedPageHandler handler, ITabbedView tabbedView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (tabbedView is TabbedPage tabbedPage && tabbedPage.SelectedTabColor is Color color)
|
||||
{
|
||||
handler.PlatformView.SelectedTabColor = color;
|
||||
handler.PlatformView.IndicatorColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapUnselectedTabColor(TabbedPageHandler handler, ITabbedView tabbedView)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (tabbedView is TabbedPage tabbedPage && tabbedPage.UnselectedTabColor is Color color)
|
||||
{
|
||||
handler.PlatformView.UnselectedTabColor = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ public partial class TimePickerHandler : ViewHandler<ITimePicker, SkiaTimePicker
|
||||
[nameof(ITimePicker.Format)] = MapFormat,
|
||||
[nameof(ITimePicker.TextColor)] = MapTextColor,
|
||||
[nameof(ITimePicker.CharacterSpacing)] = MapCharacterSpacing,
|
||||
[nameof(ITextStyle.Font)] = MapFont,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
};
|
||||
|
||||
@@ -47,6 +48,16 @@ public partial class TimePickerHandler : ViewHandler<ITimePicker, SkiaTimePicker
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.TimeSelected += OnTimeSelected;
|
||||
|
||||
// Apply dark theme colors if needed
|
||||
if (Application.Current?.UserAppTheme == AppTheme.Dark)
|
||||
{
|
||||
platformView.ClockBackgroundColor = Color.FromRgb(30, 30, 30);
|
||||
platformView.ClockFaceColor = Color.FromRgb(45, 45, 45);
|
||||
platformView.TextColor = Color.FromRgb(224, 224, 224);
|
||||
platformView.BorderColor = Color.FromRgb(97, 97, 97);
|
||||
platformView.BackgroundColor = Color.FromRgb(45, 45, 45);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaTimePicker platformView)
|
||||
@@ -55,11 +66,11 @@ public partial class TimePickerHandler : ViewHandler<ITimePicker, SkiaTimePicker
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnTimeSelected(object? sender, EventArgs e)
|
||||
private void OnTimeSelected(object? sender, TimeChangedEventArgs e)
|
||||
{
|
||||
if (VirtualView is null || PlatformView is null) return;
|
||||
|
||||
VirtualView.Time = PlatformView.Time;
|
||||
VirtualView.Time = e.NewTime;
|
||||
}
|
||||
|
||||
public static void MapTime(TimePickerHandler handler, ITimePicker timePicker)
|
||||
@@ -79,13 +90,32 @@ public partial class TimePickerHandler : ViewHandler<ITimePicker, SkiaTimePicker
|
||||
if (handler.PlatformView is null) return;
|
||||
if (timePicker.TextColor is not null)
|
||||
{
|
||||
handler.PlatformView.TextColor = timePicker.TextColor.ToSKColor();
|
||||
handler.PlatformView.TextColor = timePicker.TextColor;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapCharacterSpacing(TimePickerHandler handler, ITimePicker timePicker)
|
||||
{
|
||||
// Character spacing would require custom text rendering
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.CharacterSpacing = timePicker.CharacterSpacing;
|
||||
}
|
||||
|
||||
public static void MapFont(TimePickerHandler handler, ITimePicker timePicker)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
var font = timePicker.Font;
|
||||
if (font.Size > 0)
|
||||
handler.PlatformView.FontSize = font.Size;
|
||||
|
||||
if (!string.IsNullOrEmpty(font.Family))
|
||||
handler.PlatformView.FontFamily = font.Family;
|
||||
|
||||
// Map FontAttributes from the Font weight/slant
|
||||
var attrs = FontAttributes.None;
|
||||
if (font.Weight >= FontWeight.Bold)
|
||||
attrs |= FontAttributes.Bold;
|
||||
handler.PlatformView.FontAttributes = attrs;
|
||||
}
|
||||
|
||||
public static void MapBackground(TimePickerHandler handler, ITimePicker timePicker)
|
||||
@@ -94,7 +124,7 @@ public partial class TimePickerHandler : ViewHandler<ITimePicker, SkiaTimePicker
|
||||
|
||||
if (timePicker.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
207
Handlers/WebViewHandler.Linux.cs
Normal file
207
Handlers/WebViewHandler.Linux.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
|
||||
namespace Microsoft.Maui.Platform;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for WebView control using WebKitGTK.
|
||||
/// </summary>
|
||||
public partial class WebViewHandler : ViewHandler<IWebView, LinuxWebView>
|
||||
{
|
||||
/// <summary>
|
||||
/// Property mapper for WebView properties.
|
||||
/// </summary>
|
||||
public static IPropertyMapper<IWebView, WebViewHandler> Mapper = new PropertyMapper<IWebView, WebViewHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(IWebView.Source)] = MapSource,
|
||||
[nameof(IWebView.UserAgent)] = MapUserAgent,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Command mapper for WebView commands.
|
||||
/// </summary>
|
||||
public static CommandMapper<IWebView, WebViewHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
{
|
||||
[nameof(IWebView.GoBack)] = MapGoBack,
|
||||
[nameof(IWebView.GoForward)] = MapGoForward,
|
||||
[nameof(IWebView.Reload)] = MapReload,
|
||||
[nameof(IWebView.Eval)] = MapEval,
|
||||
[nameof(IWebView.EvaluateJavaScriptAsync)] = MapEvaluateJavaScriptAsync,
|
||||
};
|
||||
|
||||
public WebViewHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public WebViewHandler(IPropertyMapper? mapper)
|
||||
: base(mapper ?? Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public WebViewHandler(IPropertyMapper? mapper, CommandMapper? commandMapper)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override LinuxWebView CreatePlatformView()
|
||||
{
|
||||
Console.WriteLine("[WebViewHandler] Creating LinuxWebView");
|
||||
return new LinuxWebView();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(LinuxWebView platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
|
||||
platformView.Navigating += OnNavigating;
|
||||
platformView.Navigated += OnNavigated;
|
||||
|
||||
// Map initial properties
|
||||
if (VirtualView != null)
|
||||
{
|
||||
MapSource(this, VirtualView);
|
||||
MapUserAgent(this, VirtualView);
|
||||
}
|
||||
|
||||
Console.WriteLine("[WebViewHandler] Handler connected");
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(LinuxWebView platformView)
|
||||
{
|
||||
platformView.Navigating -= OnNavigating;
|
||||
platformView.Navigated -= OnNavigated;
|
||||
|
||||
base.DisconnectHandler(platformView);
|
||||
Console.WriteLine("[WebViewHandler] Handler disconnected");
|
||||
}
|
||||
|
||||
private void OnNavigating(object? sender, WebViewNavigatingEventArgs e)
|
||||
{
|
||||
if (VirtualView == null)
|
||||
return;
|
||||
|
||||
// Notify the virtual view about navigation starting
|
||||
VirtualView.Navigating(WebNavigationEvent.NewPage, e.Url);
|
||||
}
|
||||
|
||||
private void OnNavigated(object? sender, WebViewNavigatedEventArgs e)
|
||||
{
|
||||
if (VirtualView == null)
|
||||
return;
|
||||
|
||||
// Notify the virtual view about navigation completed
|
||||
var result = e.Success ? WebNavigationResult.Success : WebNavigationResult.Failure;
|
||||
VirtualView.Navigated(WebNavigationEvent.NewPage, e.Url, result);
|
||||
}
|
||||
|
||||
#region Property Mappers
|
||||
|
||||
public static void MapSource(WebViewHandler handler, IWebView webView)
|
||||
{
|
||||
var source = webView.Source;
|
||||
if (source == null)
|
||||
return;
|
||||
|
||||
Console.WriteLine($"[WebViewHandler] MapSource: {source.GetType().Name}");
|
||||
|
||||
if (source is IUrlWebViewSource urlSource && !string.IsNullOrEmpty(urlSource.Url))
|
||||
{
|
||||
handler.PlatformView?.LoadUrl(urlSource.Url);
|
||||
}
|
||||
else if (source is IHtmlWebViewSource htmlSource && !string.IsNullOrEmpty(htmlSource.Html))
|
||||
{
|
||||
handler.PlatformView?.LoadHtml(htmlSource.Html, htmlSource.BaseUrl);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapUserAgent(WebViewHandler handler, IWebView webView)
|
||||
{
|
||||
if (handler.PlatformView != null && !string.IsNullOrEmpty(webView.UserAgent))
|
||||
{
|
||||
handler.PlatformView.UserAgent = webView.UserAgent;
|
||||
Console.WriteLine($"[WebViewHandler] MapUserAgent: {webView.UserAgent}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Command Mappers
|
||||
|
||||
public static void MapGoBack(WebViewHandler handler, IWebView webView, object? args)
|
||||
{
|
||||
if (handler.PlatformView?.CanGoBack == true)
|
||||
{
|
||||
handler.PlatformView.GoBack();
|
||||
Console.WriteLine("[WebViewHandler] GoBack");
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapGoForward(WebViewHandler handler, IWebView webView, object? args)
|
||||
{
|
||||
if (handler.PlatformView?.CanGoForward == true)
|
||||
{
|
||||
handler.PlatformView.GoForward();
|
||||
Console.WriteLine("[WebViewHandler] GoForward");
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapReload(WebViewHandler handler, IWebView webView, object? args)
|
||||
{
|
||||
handler.PlatformView?.Reload();
|
||||
Console.WriteLine("[WebViewHandler] Reload");
|
||||
}
|
||||
|
||||
public static void MapEval(WebViewHandler handler, IWebView webView, object? args)
|
||||
{
|
||||
if (args is string script)
|
||||
{
|
||||
handler.PlatformView?.Eval(script);
|
||||
Console.WriteLine($"[WebViewHandler] Eval: {script.Substring(0, Math.Min(50, script.Length))}...");
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapEvaluateJavaScriptAsync(WebViewHandler handler, IWebView webView, object? args)
|
||||
{
|
||||
if (args is EvaluateJavaScriptAsyncRequest request)
|
||||
{
|
||||
var result = handler.PlatformView?.EvaluateJavaScriptAsync(request.Script);
|
||||
if (result != null)
|
||||
{
|
||||
result.ContinueWith(t =>
|
||||
{
|
||||
request.SetResult(t.Result);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
request.SetResult(null);
|
||||
}
|
||||
Console.WriteLine($"[WebViewHandler] EvaluateJavaScriptAsync: {request.Script.Substring(0, Math.Min(50, request.Script.Length))}...");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request object for async JavaScript evaluation.
|
||||
/// </summary>
|
||||
public class EvaluateJavaScriptAsyncRequest
|
||||
{
|
||||
public string Script { get; }
|
||||
private readonly TaskCompletionSource<string?> _tcs = new();
|
||||
|
||||
public EvaluateJavaScriptAsyncRequest(string script)
|
||||
{
|
||||
Script = script;
|
||||
}
|
||||
|
||||
public Task<string?> Task => _tcs.Task;
|
||||
|
||||
public void SetResult(string? result)
|
||||
{
|
||||
_tcs.TrySetResult(result);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ public partial class WebViewHandler : ViewHandler<IWebView, SkiaWebView>
|
||||
public static IPropertyMapper<IWebView, WebViewHandler> Mapper = new PropertyMapper<IWebView, WebViewHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
[nameof(IWebView.Source)] = MapSource,
|
||||
[nameof(IWebView.UserAgent)] = MapUserAgent,
|
||||
};
|
||||
|
||||
public static CommandMapper<IWebView, WebViewHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -22,6 +23,8 @@ public partial class WebViewHandler : ViewHandler<IWebView, SkiaWebView>
|
||||
[nameof(IWebView.GoBack)] = MapGoBack,
|
||||
[nameof(IWebView.GoForward)] = MapGoForward,
|
||||
[nameof(IWebView.Reload)] = MapReload,
|
||||
[nameof(IWebView.Eval)] = MapEval,
|
||||
[nameof(IWebView.EvaluateJavaScriptAsync)] = MapEvaluateJavaScriptAsync,
|
||||
};
|
||||
|
||||
public WebViewHandler() : base(Mapper, CommandMapper)
|
||||
@@ -54,29 +57,63 @@ public partial class WebViewHandler : ViewHandler<IWebView, SkiaWebView>
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnNavigating(object? sender, WebNavigatingEventArgs e)
|
||||
private void OnNavigating(object? sender, Microsoft.Maui.Platform.WebNavigatingEventArgs e)
|
||||
{
|
||||
// Forward to virtual view if needed
|
||||
IWebView virtualView = VirtualView;
|
||||
IWebViewController? controller = virtualView as IWebViewController;
|
||||
if (controller != null)
|
||||
{
|
||||
var args = new Microsoft.Maui.Controls.WebNavigatingEventArgs(
|
||||
WebNavigationEvent.NewPage,
|
||||
null,
|
||||
e.Url);
|
||||
controller.SendNavigating(args);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNavigated(object? sender, WebNavigatedEventArgs e)
|
||||
private void OnNavigated(object? sender, Microsoft.Maui.Platform.WebNavigatedEventArgs e)
|
||||
{
|
||||
// Forward to virtual view if needed
|
||||
IWebView virtualView = VirtualView;
|
||||
IWebViewController? controller = virtualView as IWebViewController;
|
||||
if (controller != null)
|
||||
{
|
||||
WebNavigationResult result = e.Success ? WebNavigationResult.Success : WebNavigationResult.Failure;
|
||||
var args = new Microsoft.Maui.Controls.WebNavigatedEventArgs(
|
||||
WebNavigationEvent.NewPage,
|
||||
null,
|
||||
e.Url,
|
||||
result);
|
||||
controller.SendNavigated(args);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapSource(WebViewHandler handler, IWebView webView)
|
||||
{
|
||||
if (handler.PlatformView == null) return;
|
||||
Console.WriteLine("[WebViewHandler] MapSource called");
|
||||
if (handler.PlatformView == null)
|
||||
{
|
||||
Console.WriteLine("[WebViewHandler] PlatformView is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
var source = webView.Source;
|
||||
Console.WriteLine($"[WebViewHandler] Source type: {source?.GetType().Name ?? "null"}");
|
||||
|
||||
if (source is UrlWebViewSource urlSource)
|
||||
{
|
||||
Console.WriteLine($"[WebViewHandler] Loading URL: {urlSource.Url}");
|
||||
handler.PlatformView.Source = urlSource.Url ?? "";
|
||||
}
|
||||
else if (source is HtmlWebViewSource htmlSource)
|
||||
{
|
||||
Console.WriteLine($"[WebViewHandler] Loading HTML ({htmlSource.Html?.Length ?? 0} chars)");
|
||||
Console.WriteLine($"[WebViewHandler] HTML preview: {htmlSource.Html?.Substring(0, Math.Min(100, htmlSource.Html?.Length ?? 0))}...");
|
||||
handler.PlatformView.Html = htmlSource.Html ?? "";
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("[WebViewHandler] Unknown source type or null");
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapGoBack(WebViewHandler handler, IWebView webView, object? args)
|
||||
@@ -93,4 +130,66 @@ public partial class WebViewHandler : ViewHandler<IWebView, SkiaWebView>
|
||||
{
|
||||
handler.PlatformView?.Reload();
|
||||
}
|
||||
|
||||
public static void MapUserAgent(WebViewHandler handler, IWebView webView)
|
||||
{
|
||||
if (handler.PlatformView != null && !string.IsNullOrEmpty(webView.UserAgent))
|
||||
{
|
||||
handler.PlatformView.UserAgent = webView.UserAgent;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapEval(WebViewHandler handler, IWebView webView, object? args)
|
||||
{
|
||||
if (args is string script)
|
||||
{
|
||||
handler.PlatformView?.Eval(script);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapEvaluateJavaScriptAsync(WebViewHandler handler, IWebView webView, object? args)
|
||||
{
|
||||
// Handle EvaluateJavaScriptAsyncRequest from Microsoft.Maui.Platform namespace
|
||||
if (args is EvaluateJavaScriptAsyncRequest request)
|
||||
{
|
||||
var result = handler.PlatformView?.EvaluateJavaScriptAsync(request.Script);
|
||||
if (result != null)
|
||||
{
|
||||
result.ContinueWith(t =>
|
||||
{
|
||||
request.SetResult(t.Result);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
request.SetResult(null);
|
||||
}
|
||||
}
|
||||
else if (args is string script)
|
||||
{
|
||||
// Direct script string
|
||||
handler.PlatformView?.EvaluateJavaScriptAsync(script);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request object for async JavaScript evaluation (matches Microsoft.Maui.Platform.EvaluateJavaScriptAsyncRequest).
|
||||
/// </summary>
|
||||
public class EvaluateJavaScriptAsyncRequest
|
||||
{
|
||||
public string Script { get; }
|
||||
private readonly System.Threading.Tasks.TaskCompletionSource<string?> _tcs = new();
|
||||
|
||||
public EvaluateJavaScriptAsyncRequest(string script)
|
||||
{
|
||||
Script = script;
|
||||
}
|
||||
|
||||
public System.Threading.Tasks.Task<string?> Task => _tcs.Task;
|
||||
|
||||
public void SetResult(string? result)
|
||||
{
|
||||
_tcs.TrySetResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,13 +81,20 @@ public partial class WindowHandler : ElementHandler<IWindow, SkiaWindow>
|
||||
|
||||
public static void MapContent(WindowHandler handler, IWindow window)
|
||||
{
|
||||
Console.Error.WriteLine($"[WindowHandler] MapContent - PlatformView={handler.PlatformView != null}");
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
var content = window.Content;
|
||||
Console.Error.WriteLine($"[WindowHandler] MapContent - content type={content?.GetType().Name}, handler={content?.Handler?.GetType().Name}");
|
||||
if (content?.Handler?.PlatformView is SkiaView skiaContent)
|
||||
{
|
||||
Console.Error.WriteLine($"[WindowHandler] MapContent - setting SkiaView content: {skiaContent.GetType().Name}");
|
||||
handler.PlatformView.Content = skiaContent;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine($"[WindowHandler] MapContent - content has no SkiaView! Handler={content?.Handler}, PlatformView={content?.Handler?.PlatformView}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapX(WindowHandler handler, IWindow window)
|
||||
@@ -177,8 +184,8 @@ public class SkiaWindow
|
||||
// Draw main content
|
||||
if (_content != null)
|
||||
{
|
||||
_content.Measure(new SKSize(_width, _height));
|
||||
_content.Arrange(new SKRect(0, 0, _width, _height));
|
||||
_content.Measure(new Size(_width, _height));
|
||||
_content.Arrange(new Rect(0, 0, _width, _height));
|
||||
_content.Draw(canvas);
|
||||
}
|
||||
|
||||
|
||||
52
Hosting/GtkMauiContext.cs
Normal file
52
Hosting/GtkMauiContext.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Maui.Animations;
|
||||
using Microsoft.Maui.Dispatching;
|
||||
using Microsoft.Maui.Platform.Linux.Dispatching;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Hosting;
|
||||
|
||||
public class GtkMauiContext : IMauiContext
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IMauiHandlersFactory _handlers;
|
||||
private IAnimationManager? _animationManager;
|
||||
private IDispatcher? _dispatcher;
|
||||
|
||||
public IServiceProvider Services => _services;
|
||||
|
||||
public IMauiHandlersFactory Handlers => _handlers;
|
||||
|
||||
public IAnimationManager AnimationManager
|
||||
{
|
||||
get
|
||||
{
|
||||
_animationManager ??= _services.GetService<IAnimationManager>()
|
||||
?? new LinuxAnimationManager(new LinuxTicker());
|
||||
return _animationManager;
|
||||
}
|
||||
}
|
||||
|
||||
public IDispatcher Dispatcher
|
||||
{
|
||||
get
|
||||
{
|
||||
_dispatcher ??= _services.GetService<IDispatcher>()
|
||||
?? new LinuxDispatcher();
|
||||
return _dispatcher;
|
||||
}
|
||||
}
|
||||
|
||||
public GtkMauiContext(IServiceProvider services)
|
||||
{
|
||||
_services = services ?? throw new ArgumentNullException(nameof(services));
|
||||
_handlers = services.GetRequiredService<IMauiHandlersFactory>();
|
||||
|
||||
if (LinuxApplication.Current == null)
|
||||
{
|
||||
new LinuxApplication();
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Hosting/HandlerMappingExtensions.cs
Normal file
17
Hosting/HandlerMappingExtensions.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Hosting;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Hosting;
|
||||
|
||||
public static class HandlerMappingExtensions
|
||||
{
|
||||
public static IMauiHandlersCollection AddHandler<TView, THandler>(this IMauiHandlersCollection handlers)
|
||||
where TView : class
|
||||
where THandler : class
|
||||
{
|
||||
handlers.AddHandler(typeof(TView), typeof(THandler));
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
56
Hosting/LinuxAnimationManager.cs
Normal file
56
Hosting/LinuxAnimationManager.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Animations;
|
||||
using Animation = Microsoft.Maui.Animations.Animation;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Hosting;
|
||||
|
||||
internal class LinuxAnimationManager : IAnimationManager
|
||||
{
|
||||
private readonly List<Animation> _animations = new();
|
||||
private readonly ITicker _ticker;
|
||||
|
||||
public double SpeedModifier { get; set; } = 1.0;
|
||||
|
||||
public bool AutoStartTicker { get; set; } = true;
|
||||
|
||||
public ITicker Ticker => _ticker;
|
||||
|
||||
public LinuxAnimationManager(ITicker ticker)
|
||||
{
|
||||
_ticker = ticker;
|
||||
_ticker.Fire = OnTickerFire;
|
||||
}
|
||||
|
||||
public void Add(Animation animation)
|
||||
{
|
||||
_animations.Add(animation);
|
||||
if (AutoStartTicker && !_ticker.IsRunning)
|
||||
{
|
||||
_ticker.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(Animation animation)
|
||||
{
|
||||
_animations.Remove(animation);
|
||||
if (_animations.Count == 0 && _ticker.IsRunning)
|
||||
{
|
||||
_ticker.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTickerFire()
|
||||
{
|
||||
var animationsArray = _animations.ToArray();
|
||||
foreach (var animation in animationsArray)
|
||||
{
|
||||
animation.Tick(0.016 * SpeedModifier);
|
||||
if (animation.HasFinished)
|
||||
{
|
||||
Remove(animation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,37 +7,41 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Maui.ApplicationModel;
|
||||
using Microsoft.Maui.ApplicationModel.Communication;
|
||||
using Microsoft.Maui.ApplicationModel.DataTransfer;
|
||||
using Microsoft.Maui.Hosting;
|
||||
using Microsoft.Maui.Platform.Linux.Services;
|
||||
using Microsoft.Maui.Platform.Linux.Converters;
|
||||
using Microsoft.Maui.Storage;
|
||||
using Microsoft.Maui.Platform.Linux.Handlers;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Devices;
|
||||
using Microsoft.Maui.Dispatching;
|
||||
using Microsoft.Maui.Hosting;
|
||||
using Microsoft.Maui.Networking;
|
||||
using Microsoft.Maui.Platform.Linux.Converters;
|
||||
using Microsoft.Maui.Platform.Linux.Dispatching;
|
||||
using Microsoft.Maui.Platform.Linux.Handlers;
|
||||
using Microsoft.Maui.Platform.Linux.Services;
|
||||
using Microsoft.Maui.Storage;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Hosting;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for configuring MAUI applications for Linux.
|
||||
/// </summary>
|
||||
public static class LinuxMauiAppBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures the MAUI application to run on Linux.
|
||||
/// </summary>
|
||||
public static MauiAppBuilder UseLinux(this MauiAppBuilder builder)
|
||||
{
|
||||
return builder.UseLinux(configure: null);
|
||||
return builder.UseLinux(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the MAUI application to run on Linux with options.
|
||||
/// </summary>
|
||||
public static MauiAppBuilder UseLinux(this MauiAppBuilder builder, Action<LinuxApplicationOptions>? configure)
|
||||
{
|
||||
var options = new LinuxApplicationOptions();
|
||||
configure?.Invoke(options);
|
||||
|
||||
// Register dispatcher provider
|
||||
builder.Services.TryAddSingleton<IDispatcherProvider>(LinuxDispatcherProvider.Instance);
|
||||
|
||||
// Register device services
|
||||
builder.Services.TryAddSingleton<IDeviceInfo>(DeviceInfoService.Instance);
|
||||
builder.Services.TryAddSingleton<IDeviceDisplay>(DeviceDisplayService.Instance);
|
||||
builder.Services.TryAddSingleton<IAppInfo>(AppInfoService.Instance);
|
||||
builder.Services.TryAddSingleton<IConnectivity>(ConnectivityService.Instance);
|
||||
|
||||
// Register platform services
|
||||
builder.Services.TryAddSingleton<ILauncher, LauncherService>();
|
||||
builder.Services.TryAddSingleton<IPreferences, PreferencesService>();
|
||||
@@ -50,6 +54,29 @@ public static class LinuxMauiAppBuilderExtensions
|
||||
builder.Services.TryAddSingleton<IBrowser, BrowserService>();
|
||||
builder.Services.TryAddSingleton<IEmail, EmailService>();
|
||||
|
||||
// Register theming and accessibility services
|
||||
builder.Services.TryAddSingleton<SystemThemeService>();
|
||||
builder.Services.TryAddSingleton<HighContrastService>();
|
||||
|
||||
// Register accessibility service
|
||||
builder.Services.TryAddSingleton<IAccessibilityService>(_ => AccessibilityServiceFactory.Instance);
|
||||
|
||||
// Register input method service
|
||||
builder.Services.TryAddSingleton<IInputMethodService>(_ => InputMethodServiceFactory.Instance);
|
||||
|
||||
// Register font fallback manager
|
||||
builder.Services.TryAddSingleton(_ => FontFallbackManager.Instance);
|
||||
|
||||
// Register additional Linux-specific services
|
||||
builder.Services.TryAddSingleton<FolderPickerService>();
|
||||
builder.Services.TryAddSingleton<NotificationService>();
|
||||
builder.Services.TryAddSingleton<SystemTrayService>();
|
||||
builder.Services.TryAddSingleton(_ => MonitorService.Instance);
|
||||
builder.Services.TryAddSingleton<DragDropService>();
|
||||
|
||||
// Register GTK host service
|
||||
builder.Services.TryAddSingleton(_ => GtkHostService.Instance);
|
||||
|
||||
// Register type converters for XAML support
|
||||
RegisterTypeConverters();
|
||||
|
||||
@@ -77,11 +104,12 @@ public static class LinuxMauiAppBuilderExtensions
|
||||
handlers.AddHandler<VerticalStackLayout, StackLayoutHandler>();
|
||||
handlers.AddHandler<HorizontalStackLayout, StackLayoutHandler>();
|
||||
handlers.AddHandler<AbsoluteLayout, LayoutHandler>();
|
||||
handlers.AddHandler<FlexLayout, LayoutHandler>();
|
||||
handlers.AddHandler<FlexLayout, FlexLayoutHandler>();
|
||||
handlers.AddHandler<ScrollView, ScrollViewHandler>();
|
||||
handlers.AddHandler<Frame, FrameHandler>();
|
||||
handlers.AddHandler<Border, BorderHandler>();
|
||||
handlers.AddHandler<ContentView, BorderHandler>();
|
||||
handlers.AddHandler<RefreshView, RefreshViewHandler>();
|
||||
|
||||
// Picker controls
|
||||
handlers.AddHandler<Picker, PickerHandler>();
|
||||
@@ -98,9 +126,15 @@ public static class LinuxMauiAppBuilderExtensions
|
||||
handlers.AddHandler<ImageButton, ImageButtonHandler>();
|
||||
handlers.AddHandler<GraphicsView, GraphicsViewHandler>();
|
||||
|
||||
// Web - use GtkWebViewHandler
|
||||
handlers.AddHandler<WebView, GtkWebViewHandler>();
|
||||
|
||||
// Collection Views
|
||||
handlers.AddHandler<CollectionView, CollectionViewHandler>();
|
||||
handlers.AddHandler<ListView, CollectionViewHandler>();
|
||||
handlers.AddHandler<CarouselView, CarouselViewHandler>();
|
||||
handlers.AddHandler<IndicatorView, IndicatorViewHandler>();
|
||||
handlers.AddHandler<SwipeView, SwipeViewHandler>();
|
||||
|
||||
// Pages & Navigation
|
||||
handlers.AddHandler<Page, PageHandler>();
|
||||
@@ -121,33 +155,11 @@ public static class LinuxMauiAppBuilderExtensions
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers custom type converters for Linux platform.
|
||||
/// </summary>
|
||||
private static void RegisterTypeConverters()
|
||||
{
|
||||
// Register SkiaSharp type converters for XAML styling support
|
||||
TypeDescriptor.AddAttributes(typeof(SKColor), new TypeConverterAttribute(typeof(SKColorTypeConverter)));
|
||||
TypeDescriptor.AddAttributes(typeof(SKRect), new TypeConverterAttribute(typeof(SKRectTypeConverter)));
|
||||
TypeDescriptor.AddAttributes(typeof(SKSize), new TypeConverterAttribute(typeof(SKSizeTypeConverter)));
|
||||
TypeDescriptor.AddAttributes(typeof(SKPoint), new TypeConverterAttribute(typeof(SKPointTypeConverter)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler registration extensions.
|
||||
/// </summary>
|
||||
public static class HandlerMappingExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a handler for the specified view type.
|
||||
/// </summary>
|
||||
public static IMauiHandlersCollection AddHandler<TView, THandler>(
|
||||
this IMauiHandlersCollection handlers)
|
||||
where TView : class
|
||||
where THandler : class
|
||||
{
|
||||
handlers.AddHandler(typeof(TView), typeof(THandler));
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,10 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Maui.Animations;
|
||||
using Microsoft.Maui.Dispatching;
|
||||
using Microsoft.Maui.Platform;
|
||||
using SkiaSharp;
|
||||
using Microsoft.Maui.Platform.Linux.Dispatching;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Hosting;
|
||||
|
||||
/// <summary>
|
||||
/// Linux-specific implementation of IMauiContext.
|
||||
/// Provides the infrastructure for creating handlers and accessing platform services.
|
||||
/// </summary>
|
||||
public class LinuxMauiContext : IMauiContext
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
@@ -21,27 +16,12 @@ public class LinuxMauiContext : IMauiContext
|
||||
private IAnimationManager? _animationManager;
|
||||
private IDispatcher? _dispatcher;
|
||||
|
||||
public LinuxMauiContext(IServiceProvider services, LinuxApplication linuxApp)
|
||||
{
|
||||
_services = services ?? throw new ArgumentNullException(nameof(services));
|
||||
_linuxApp = linuxApp ?? throw new ArgumentNullException(nameof(linuxApp));
|
||||
_handlers = services.GetRequiredService<IMauiHandlersFactory>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IServiceProvider Services => _services;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMauiHandlersFactory Handlers => _handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Linux application instance.
|
||||
/// </summary>
|
||||
public LinuxApplication LinuxApp => _linuxApp;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the animation manager.
|
||||
/// </summary>
|
||||
public IAnimationManager AnimationManager
|
||||
{
|
||||
get
|
||||
@@ -52,9 +32,6 @@ public class LinuxMauiContext : IMauiContext
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dispatcher for UI thread operations.
|
||||
/// </summary>
|
||||
public IDispatcher Dispatcher
|
||||
{
|
||||
get
|
||||
@@ -64,236 +41,11 @@ public class LinuxMauiContext : IMauiContext
|
||||
return _dispatcher;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scoped MAUI context for a specific window or view hierarchy.
|
||||
/// </summary>
|
||||
public class ScopedLinuxMauiContext : IMauiContext
|
||||
public LinuxMauiContext(IServiceProvider services, LinuxApplication linuxApp)
|
||||
{
|
||||
private readonly LinuxMauiContext _parent;
|
||||
|
||||
public ScopedLinuxMauiContext(LinuxMauiContext parent)
|
||||
{
|
||||
_parent = parent ?? throw new ArgumentNullException(nameof(parent));
|
||||
}
|
||||
|
||||
public IServiceProvider Services => _parent.Services;
|
||||
public IMauiHandlersFactory Handlers => _parent.Handlers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linux dispatcher for UI thread operations.
|
||||
/// </summary>
|
||||
internal class LinuxDispatcher : IDispatcher
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly Queue<Action> _queue = new();
|
||||
private bool _isDispatching;
|
||||
|
||||
public bool IsDispatchRequired => false; // Linux uses single-threaded event loop
|
||||
|
||||
public IDispatcherTimer CreateTimer()
|
||||
{
|
||||
return new LinuxDispatcherTimer();
|
||||
}
|
||||
|
||||
public bool Dispatch(Action action)
|
||||
{
|
||||
if (action == null)
|
||||
return false;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_queue.Enqueue(action);
|
||||
}
|
||||
|
||||
ProcessQueue();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool DispatchDelayed(TimeSpan delay, Action action)
|
||||
{
|
||||
if (action == null)
|
||||
return false;
|
||||
|
||||
Task.Delay(delay).ContinueWith(_ => Dispatch(action));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ProcessQueue()
|
||||
{
|
||||
if (_isDispatching)
|
||||
return;
|
||||
|
||||
_isDispatching = true;
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Action? action;
|
||||
lock (_lock)
|
||||
{
|
||||
if (_queue.Count == 0)
|
||||
break;
|
||||
action = _queue.Dequeue();
|
||||
}
|
||||
action?.Invoke();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isDispatching = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linux dispatcher timer implementation.
|
||||
/// </summary>
|
||||
internal class LinuxDispatcherTimer : IDispatcherTimer
|
||||
{
|
||||
private Timer? _timer;
|
||||
private TimeSpan _interval = TimeSpan.FromMilliseconds(16); // ~60fps default
|
||||
private bool _isRunning;
|
||||
private bool _isRepeating = true;
|
||||
|
||||
public TimeSpan Interval
|
||||
{
|
||||
get => _interval;
|
||||
set => _interval = value;
|
||||
}
|
||||
|
||||
public bool IsRunning => _isRunning;
|
||||
|
||||
public bool IsRepeating
|
||||
{
|
||||
get => _isRepeating;
|
||||
set => _isRepeating = value;
|
||||
}
|
||||
|
||||
public event EventHandler? Tick;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (_isRunning)
|
||||
return;
|
||||
|
||||
_isRunning = true;
|
||||
_timer = new Timer(OnTimerCallback, null, _interval, _isRepeating ? _interval : Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_isRunning = false;
|
||||
_timer?.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
|
||||
private void OnTimerCallback(object? state)
|
||||
{
|
||||
Tick?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
if (!_isRepeating)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linux animation manager.
|
||||
/// </summary>
|
||||
internal class LinuxAnimationManager : IAnimationManager
|
||||
{
|
||||
private readonly List<Microsoft.Maui.Animations.Animation> _animations = new();
|
||||
private readonly ITicker _ticker;
|
||||
|
||||
public LinuxAnimationManager(ITicker ticker)
|
||||
{
|
||||
_ticker = ticker;
|
||||
_ticker.Fire = OnTickerFire;
|
||||
}
|
||||
|
||||
public double SpeedModifier { get; set; } = 1.0;
|
||||
public bool AutoStartTicker { get; set; } = true;
|
||||
|
||||
public ITicker Ticker => _ticker;
|
||||
|
||||
public void Add(Microsoft.Maui.Animations.Animation animation)
|
||||
{
|
||||
_animations.Add(animation);
|
||||
|
||||
if (AutoStartTicker && !_ticker.IsRunning)
|
||||
{
|
||||
_ticker.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(Microsoft.Maui.Animations.Animation animation)
|
||||
{
|
||||
_animations.Remove(animation);
|
||||
|
||||
if (_animations.Count == 0 && _ticker.IsRunning)
|
||||
{
|
||||
_ticker.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTickerFire()
|
||||
{
|
||||
var animations = _animations.ToArray();
|
||||
foreach (var animation in animations)
|
||||
{
|
||||
animation.Tick(16.0 / 1000.0 * SpeedModifier); // ~60fps
|
||||
if (animation.HasFinished)
|
||||
{
|
||||
Remove(animation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linux ticker for animation timing.
|
||||
/// </summary>
|
||||
internal class LinuxTicker : ITicker
|
||||
{
|
||||
private Timer? _timer;
|
||||
private bool _isRunning;
|
||||
private int _maxFps = 60;
|
||||
|
||||
public bool IsRunning => _isRunning;
|
||||
|
||||
public bool SystemEnabled => true;
|
||||
|
||||
public int MaxFps
|
||||
{
|
||||
get => _maxFps;
|
||||
set => _maxFps = Math.Max(1, Math.Min(120, value));
|
||||
}
|
||||
|
||||
public Action? Fire { get; set; }
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (_isRunning)
|
||||
return;
|
||||
|
||||
_isRunning = true;
|
||||
var interval = TimeSpan.FromMilliseconds(1000.0 / _maxFps);
|
||||
_timer = new Timer(OnTimerCallback, null, TimeSpan.Zero, interval);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_isRunning = false;
|
||||
_timer?.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
|
||||
private void OnTimerCallback(object? state)
|
||||
{
|
||||
Fire?.Invoke();
|
||||
_services = services ?? throw new ArgumentNullException(nameof(services));
|
||||
_linuxApp = linuxApp ?? throw new ArgumentNullException(nameof(linuxApp));
|
||||
_handlers = services.GetRequiredService<IMauiHandlersFactory>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Maui.Hosting;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Platform;
|
||||
using Microsoft.Maui.Controls.Hosting;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Hosting;
|
||||
using Microsoft.Maui.Platform.Linux.Services;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Hosting;
|
||||
@@ -44,6 +46,10 @@ public static class LinuxProgramHost
|
||||
?? new LinuxApplicationOptions();
|
||||
ParseCommandLineOptions(args, options);
|
||||
|
||||
// Initialize GTK for WebView support
|
||||
GtkHostService.Instance.Initialize(options.Title ?? "MAUI Application", options.Width, options.Height);
|
||||
Console.WriteLine("[LinuxProgramHost] GTK initialized for WebView support");
|
||||
|
||||
// Create Linux application
|
||||
using var linuxApp = new LinuxApplication();
|
||||
linuxApp.Initialize(options);
|
||||
@@ -186,33 +192,33 @@ public static class LinuxProgramHost
|
||||
{
|
||||
Orientation = StackOrientation.Vertical,
|
||||
Spacing = 15,
|
||||
BackgroundColor = new SKColor(0xF5, 0xF5, 0xF5)
|
||||
BackgroundColor = Color.FromRgb(0xF5, 0xF5, 0xF5)
|
||||
};
|
||||
root.Padding = new SKRect(20, 20, 20, 20);
|
||||
root.Padding = new Thickness(20, 20, 20, 20);
|
||||
|
||||
// ========== TITLE ==========
|
||||
root.AddChild(new SkiaLabel
|
||||
{
|
||||
Text = "OpenMaui Linux Control Demo",
|
||||
FontSize = 28,
|
||||
TextColor = new SKColor(0x1A, 0x23, 0x7E),
|
||||
IsBold = true
|
||||
TextColor = Color.FromRgb(0x1A, 0x23, 0x7E),
|
||||
FontAttributes = FontAttributes.Bold
|
||||
});
|
||||
root.AddChild(new SkiaLabel
|
||||
{
|
||||
Text = "All controls rendered using SkiaSharp on X11",
|
||||
FontSize = 14,
|
||||
TextColor = SKColors.Gray
|
||||
TextColor = Colors.Gray
|
||||
});
|
||||
|
||||
// ========== LABELS SECTION ==========
|
||||
root.AddChild(CreateSeparator());
|
||||
root.AddChild(CreateSectionHeader("Labels"));
|
||||
var labelSection = new SkiaStackLayout { Orientation = StackOrientation.Vertical, Spacing = 5 };
|
||||
labelSection.AddChild(new SkiaLabel { Text = "Normal Label", FontSize = 16, TextColor = SKColors.Black });
|
||||
labelSection.AddChild(new SkiaLabel { Text = "Bold Label", FontSize = 16, TextColor = SKColors.Black, IsBold = true });
|
||||
labelSection.AddChild(new SkiaLabel { Text = "Italic Label", FontSize = 16, TextColor = SKColors.Gray, IsItalic = true });
|
||||
labelSection.AddChild(new SkiaLabel { Text = "Colored Label (Pink)", FontSize = 16, TextColor = new SKColor(0xE9, 0x1E, 0x63) });
|
||||
labelSection.AddChild(new SkiaLabel { Text = "Normal Label", FontSize = 16, TextColor = Colors.Black });
|
||||
labelSection.AddChild(new SkiaLabel { Text = "Bold Label", FontSize = 16, TextColor = Colors.Black, FontAttributes = FontAttributes.Bold });
|
||||
labelSection.AddChild(new SkiaLabel { Text = "Italic Label", FontSize = 16, TextColor = Colors.Gray, FontAttributes = FontAttributes.Italic });
|
||||
labelSection.AddChild(new SkiaLabel { Text = "Colored Label (Pink)", FontSize = 16, TextColor = Color.FromRgb(0xE9, 0x1E, 0x63) });
|
||||
root.AddChild(labelSection);
|
||||
|
||||
// ========== BUTTONS SECTION ==========
|
||||
@@ -221,20 +227,20 @@ public static class LinuxProgramHost
|
||||
var buttonSection = new SkiaStackLayout { Orientation = StackOrientation.Horizontal, Spacing = 10 };
|
||||
|
||||
var btnPrimary = new SkiaButton { Text = "Primary", FontSize = 14 };
|
||||
btnPrimary.BackgroundColor = new SKColor(0x21, 0x96, 0xF3);
|
||||
btnPrimary.TextColor = SKColors.White;
|
||||
btnPrimary.BackgroundColor = Color.FromRgb(0x21, 0x96, 0xF3);
|
||||
btnPrimary.TextColor = Colors.White;
|
||||
var clickCount = 0;
|
||||
btnPrimary.Clicked += (s, e) => { clickCount++; btnPrimary.Text = $"Clicked {clickCount}x"; };
|
||||
buttonSection.AddChild(btnPrimary);
|
||||
|
||||
var btnSuccess = new SkiaButton { Text = "Success", FontSize = 14 };
|
||||
btnSuccess.BackgroundColor = new SKColor(0x4C, 0xAF, 0x50);
|
||||
btnSuccess.TextColor = SKColors.White;
|
||||
btnSuccess.BackgroundColor = Color.FromRgb(0x4C, 0xAF, 0x50);
|
||||
btnSuccess.TextColor = Colors.White;
|
||||
buttonSection.AddChild(btnSuccess);
|
||||
|
||||
var btnDanger = new SkiaButton { Text = "Danger", FontSize = 14 };
|
||||
btnDanger.BackgroundColor = new SKColor(0xF4, 0x43, 0x36);
|
||||
btnDanger.TextColor = SKColors.White;
|
||||
btnDanger.BackgroundColor = Color.FromRgb(0xF4, 0x43, 0x36);
|
||||
btnDanger.TextColor = Colors.White;
|
||||
buttonSection.AddChild(btnDanger);
|
||||
|
||||
root.AddChild(buttonSection);
|
||||
@@ -249,7 +255,7 @@ public static class LinuxProgramHost
|
||||
root.AddChild(CreateSeparator());
|
||||
root.AddChild(CreateSectionHeader("SearchBar"));
|
||||
var searchBar = new SkiaSearchBar { Placeholder = "Search for items..." };
|
||||
var searchResultLabel = new SkiaLabel { Text = "", FontSize = 12, TextColor = SKColors.Gray };
|
||||
var searchResultLabel = new SkiaLabel { Text = "", FontSize = 12, TextColor = Colors.Gray };
|
||||
searchBar.TextChanged += (s, e) => searchResultLabel.Text = $"Searching: {e.NewTextValue}";
|
||||
searchBar.SearchButtonPressed += (s, e) => searchResultLabel.Text = $"Search submitted: {searchBar.Text}";
|
||||
root.AddChild(searchBar);
|
||||
@@ -262,7 +268,7 @@ public static class LinuxProgramHost
|
||||
{
|
||||
Placeholder = "Enter multiple lines of text...",
|
||||
FontSize = 14,
|
||||
BackgroundColor = SKColors.White
|
||||
BackgroundColor = Colors.White
|
||||
};
|
||||
root.AddChild(editor);
|
||||
|
||||
@@ -324,7 +330,7 @@ public static class LinuxProgramHost
|
||||
root.AddChild(CreateSectionHeader("ProgressBar"));
|
||||
var progress = new SkiaProgressBar { Progress = 0.7f };
|
||||
root.AddChild(progress);
|
||||
root.AddChild(new SkiaLabel { Text = "70% Complete", FontSize = 12, TextColor = SKColors.Gray });
|
||||
root.AddChild(new SkiaLabel { Text = "70% Complete", FontSize = 12, TextColor = Colors.Gray });
|
||||
|
||||
// ========== ACTIVITYINDICATOR SECTION ==========
|
||||
root.AddChild(CreateSeparator());
|
||||
@@ -332,7 +338,7 @@ public static class LinuxProgramHost
|
||||
var activitySection = new SkiaStackLayout { Orientation = StackOrientation.Horizontal, Spacing = 10 };
|
||||
var activity = new SkiaActivityIndicator { IsRunning = true };
|
||||
activitySection.AddChild(activity);
|
||||
activitySection.AddChild(new SkiaLabel { Text = "Loading...", FontSize = 14, TextColor = SKColors.Gray });
|
||||
activitySection.AddChild(new SkiaLabel { Text = "Loading...", FontSize = 14, TextColor = Colors.Gray });
|
||||
root.AddChild(activitySection);
|
||||
|
||||
// ========== PICKER SECTION ==========
|
||||
@@ -340,7 +346,7 @@ public static class LinuxProgramHost
|
||||
root.AddChild(CreateSectionHeader("Picker (Dropdown)"));
|
||||
var picker = new SkiaPicker { Title = "Select an item" };
|
||||
picker.SetItems(new[] { "Apple", "Banana", "Cherry", "Date", "Elderberry", "Fig", "Grape" });
|
||||
var pickerLabel = new SkiaLabel { Text = "Selected: (none)", FontSize = 12, TextColor = SKColors.Gray };
|
||||
var pickerLabel = new SkiaLabel { Text = "Selected: (none)", FontSize = 12, TextColor = Colors.Gray };
|
||||
picker.SelectedIndexChanged += (s, e) => pickerLabel.Text = $"Selected: {picker.SelectedItem}";
|
||||
root.AddChild(picker);
|
||||
root.AddChild(pickerLabel);
|
||||
@@ -349,7 +355,7 @@ public static class LinuxProgramHost
|
||||
root.AddChild(CreateSeparator());
|
||||
root.AddChild(CreateSectionHeader("DatePicker"));
|
||||
var datePicker = new SkiaDatePicker { Date = DateTime.Today };
|
||||
var dateLabel = new SkiaLabel { Text = $"Date: {DateTime.Today:d}", FontSize = 12, TextColor = SKColors.Gray };
|
||||
var dateLabel = new SkiaLabel { Text = $"Date: {DateTime.Today:d}", FontSize = 12, TextColor = Colors.Gray };
|
||||
datePicker.DateSelected += (s, e) => dateLabel.Text = $"Date: {datePicker.Date:d}";
|
||||
root.AddChild(datePicker);
|
||||
root.AddChild(dateLabel);
|
||||
@@ -358,7 +364,7 @@ public static class LinuxProgramHost
|
||||
root.AddChild(CreateSeparator());
|
||||
root.AddChild(CreateSectionHeader("TimePicker"));
|
||||
var timePicker = new SkiaTimePicker();
|
||||
var timeLabel = new SkiaLabel { Text = $"Time: {DateTime.Now:t}", FontSize = 12, TextColor = SKColors.Gray };
|
||||
var timeLabel = new SkiaLabel { Text = $"Time: {DateTime.Now:t}", FontSize = 12, TextColor = Colors.Gray };
|
||||
timePicker.TimeSelected += (s, e) => timeLabel.Text = $"Time: {DateTime.Today.Add(timePicker.Time):t}";
|
||||
root.AddChild(timePicker);
|
||||
root.AddChild(timeLabel);
|
||||
@@ -370,18 +376,18 @@ public static class LinuxProgramHost
|
||||
{
|
||||
CornerRadius = 8,
|
||||
StrokeThickness = 2,
|
||||
Stroke = new SKColor(0x21, 0x96, 0xF3),
|
||||
BackgroundColor = new SKColor(0xE3, 0xF2, 0xFD)
|
||||
Stroke = Color.FromRgb(0x21, 0x96, 0xF3),
|
||||
BackgroundColor = Color.FromRgb(0xE3, 0xF2, 0xFD)
|
||||
};
|
||||
border.SetPadding(15);
|
||||
border.AddChild(new SkiaLabel { Text = "Content inside a styled Border", FontSize = 14, TextColor = new SKColor(0x1A, 0x23, 0x7E) });
|
||||
border.AddChild(new SkiaLabel { Text = "Content inside a styled Border", FontSize = 14, TextColor = Color.FromRgb(0x1A, 0x23, 0x7E) });
|
||||
root.AddChild(border);
|
||||
|
||||
// ========== FRAME SECTION ==========
|
||||
root.AddChild(CreateSeparator());
|
||||
root.AddChild(CreateSectionHeader("Frame (with shadow)"));
|
||||
var frame = new SkiaFrame();
|
||||
frame.BackgroundColor = SKColors.White;
|
||||
frame.BackgroundColor = Colors.White;
|
||||
frame.AddChild(new SkiaLabel { Text = "Content inside a Frame with shadow effect", FontSize = 14 });
|
||||
root.AddChild(frame);
|
||||
|
||||
@@ -395,7 +401,7 @@ public static class LinuxProgramHost
|
||||
Footer = "End of list"
|
||||
};
|
||||
collectionView.ItemsSource =(new object[] { "Apple", "Banana", "Cherry", "Date", "Elderberry", "Fig", "Grape", "Honeydew" });
|
||||
var collectionLabel = new SkiaLabel { Text = "Selected: (none)", FontSize = 12, TextColor = SKColors.Gray };
|
||||
var collectionLabel = new SkiaLabel { Text = "Selected: (none)", FontSize = 12, TextColor = Colors.Gray };
|
||||
collectionView.SelectionChanged += (s, e) =>
|
||||
{
|
||||
var selected = e.CurrentSelection.FirstOrDefault();
|
||||
@@ -413,18 +419,15 @@ public static class LinuxProgramHost
|
||||
var imgBtn = new SkiaImageButton
|
||||
{
|
||||
CornerRadius = 8,
|
||||
StrokeColor = new SKColor(0x21, 0x96, 0xF3),
|
||||
StrokeColor = Color.FromRgb(0x21, 0x96, 0xF3),
|
||||
StrokeThickness = 1,
|
||||
BackgroundColor = new SKColor(0xE3, 0xF2, 0xFD),
|
||||
PaddingLeft = 10,
|
||||
PaddingRight = 10,
|
||||
PaddingTop = 10,
|
||||
PaddingBottom = 10
|
||||
ImageBackgroundColor = Color.FromRgb(0xE3, 0xF2, 0xFD),
|
||||
Padding = new Thickness(10)
|
||||
};
|
||||
// Generate a simple star icon bitmap
|
||||
var iconBitmap = CreateStarIcon(32, new SKColor(0x21, 0x96, 0xF3));
|
||||
imgBtn.Bitmap = iconBitmap;
|
||||
var imgBtnLabel = new SkiaLabel { Text = "Click the star!", FontSize = 12, TextColor = SKColors.Gray };
|
||||
var imgBtnLabel = new SkiaLabel { Text = "Click the star!", FontSize = 12, TextColor = Colors.Gray };
|
||||
imgBtn.Clicked += (s, e) => imgBtnLabel.Text = "Star clicked!";
|
||||
imageButtonSection.AddChild(imgBtn);
|
||||
imageButtonSection.AddChild(imgBtnLabel);
|
||||
@@ -440,7 +443,7 @@ public static class LinuxProgramHost
|
||||
var sampleBitmap = CreateSampleImage(80, 60);
|
||||
img.Bitmap = sampleBitmap;
|
||||
imageSection.AddChild(img);
|
||||
imageSection.AddChild(new SkiaLabel { Text = "Sample generated image", FontSize = 12, TextColor = SKColors.Gray });
|
||||
imageSection.AddChild(new SkiaLabel { Text = "Sample generated image", FontSize = 12, TextColor = Colors.Gray });
|
||||
root.AddChild(imageSection);
|
||||
|
||||
// ========== FOOTER ==========
|
||||
@@ -449,14 +452,14 @@ public static class LinuxProgramHost
|
||||
{
|
||||
Text = "All 25+ controls are interactive - try them all!",
|
||||
FontSize = 16,
|
||||
TextColor = new SKColor(0x4C, 0xAF, 0x50),
|
||||
IsBold = true
|
||||
TextColor = Color.FromRgb(0x4C, 0xAF, 0x50),
|
||||
FontAttributes = FontAttributes.Bold
|
||||
});
|
||||
root.AddChild(new SkiaLabel
|
||||
{
|
||||
Text = "Scroll down to see more controls",
|
||||
FontSize = 12,
|
||||
TextColor = SKColors.Gray
|
||||
TextColor = Colors.Gray
|
||||
});
|
||||
|
||||
scroll.Content = root;
|
||||
@@ -469,14 +472,14 @@ public static class LinuxProgramHost
|
||||
{
|
||||
Text = text,
|
||||
FontSize = 18,
|
||||
TextColor = new SKColor(0x37, 0x47, 0x4F),
|
||||
IsBold = true
|
||||
TextColor = Color.FromRgb(0x37, 0x47, 0x4F),
|
||||
FontAttributes = FontAttributes.Bold
|
||||
};
|
||||
}
|
||||
|
||||
private static SkiaView CreateSeparator()
|
||||
{
|
||||
var sep = new SkiaLabel { Text = "", BackgroundColor = new SKColor(0xE0, 0xE0, 0xE0), RequestedHeight = 1 };
|
||||
var sep = new SkiaLabel { Text = "", BackgroundColor = Color.FromRgb(0xE0, 0xE0, 0xE0), RequestedHeight = 1 };
|
||||
return sep;
|
||||
}
|
||||
|
||||
|
||||
47
Hosting/LinuxTicker.cs
Normal file
47
Hosting/LinuxTicker.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Animations;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Hosting;
|
||||
|
||||
internal class LinuxTicker : ITicker
|
||||
{
|
||||
private Timer? _timer;
|
||||
private bool _isRunning;
|
||||
private int _maxFps = 60;
|
||||
|
||||
public bool IsRunning => _isRunning;
|
||||
|
||||
public bool SystemEnabled => true;
|
||||
|
||||
public int MaxFps
|
||||
{
|
||||
get => _maxFps;
|
||||
set => _maxFps = Math.Max(1, Math.Min(120, value));
|
||||
}
|
||||
|
||||
public Action? Fire { get; set; }
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
_isRunning = true;
|
||||
var period = TimeSpan.FromMilliseconds(1000.0 / _maxFps);
|
||||
_timer = new Timer(OnTimerCallback, null, TimeSpan.Zero, period);
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_isRunning = false;
|
||||
_timer?.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
|
||||
private void OnTimerCallback(object? state)
|
||||
{
|
||||
Fire?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Reflection;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Platform;
|
||||
using SkiaSharp;
|
||||
|
||||
@@ -88,29 +90,19 @@ public class LinuxViewRenderer
|
||||
|
||||
try
|
||||
{
|
||||
// Render the page content
|
||||
SkiaView? pageContent = null;
|
||||
if (page is ContentPage contentPage && contentPage.Content != null)
|
||||
{
|
||||
pageContent = CurrentRenderer.RenderView(contentPage.Content);
|
||||
}
|
||||
// Render the page through the proper handler system
|
||||
// This ensures all properties (including BackgroundColor via AppThemeBinding) are mapped
|
||||
var skiaPage = CurrentRenderer.RenderPage(page);
|
||||
|
||||
if (pageContent == null)
|
||||
if (skiaPage == null)
|
||||
{
|
||||
Console.WriteLine($"[PushPage] Failed to render page content");
|
||||
Console.WriteLine($"[PushPage] Failed to render page through handler");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Wrap in ScrollView if needed
|
||||
if (pageContent is not SkiaScrollView)
|
||||
{
|
||||
var scrollView = new SkiaScrollView { Content = pageContent };
|
||||
pageContent = scrollView;
|
||||
}
|
||||
|
||||
// Push onto SkiaShell's navigation stack
|
||||
CurrentSkiaShell.PushAsync(pageContent, page.Title ?? "Detail");
|
||||
Console.WriteLine($"[PushPage] Successfully pushed page");
|
||||
CurrentSkiaShell.PushAsync(skiaPage, page.Title ?? "Detail");
|
||||
Console.WriteLine($"[PushPage] Successfully pushed page via handler system");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -162,18 +154,11 @@ public class LinuxViewRenderer
|
||||
page.Handler?.DisconnectHandler();
|
||||
var handler = page.ToHandler(_mauiContext);
|
||||
|
||||
// The handler's property mappers (e.g., ContentPageHandler.MapContent)
|
||||
// already set up the content and child handlers - no need to re-render here.
|
||||
// Re-rendering would disconnect the existing handler hierarchy.
|
||||
if (handler.PlatformView is SkiaView skiaPage)
|
||||
{
|
||||
// For ContentPage, render the content
|
||||
if (page is ContentPage contentPage && contentPage.Content != null)
|
||||
{
|
||||
var contentView = RenderView(contentPage.Content);
|
||||
if (skiaPage is SkiaPage sp && contentView != null)
|
||||
{
|
||||
sp.Content = contentView;
|
||||
}
|
||||
}
|
||||
|
||||
return skiaPage;
|
||||
}
|
||||
|
||||
@@ -198,9 +183,41 @@ public class LinuxViewRenderer
|
||||
FlyoutBehavior.Locked => ShellFlyoutBehavior.Locked,
|
||||
FlyoutBehavior.Disabled => ShellFlyoutBehavior.Disabled,
|
||||
_ => ShellFlyoutBehavior.Flyout
|
||||
}
|
||||
},
|
||||
MauiShell = shell
|
||||
};
|
||||
|
||||
// Apply shell colors based on theme
|
||||
ApplyShellColors(skiaShell, shell);
|
||||
|
||||
// Render flyout header if present
|
||||
if (shell.FlyoutHeader is View headerView)
|
||||
{
|
||||
var skiaHeader = RenderView(headerView);
|
||||
if (skiaHeader != null)
|
||||
{
|
||||
skiaShell.FlyoutHeaderView = skiaHeader;
|
||||
skiaShell.FlyoutHeaderHeight = (float)(headerView.HeightRequest > 0 ? headerView.HeightRequest : 140.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Render flyout footer if present, otherwise use version text
|
||||
if (shell.FlyoutFooter is View footerView)
|
||||
{
|
||||
var skiaFooter = RenderView(footerView);
|
||||
if (skiaFooter != null)
|
||||
{
|
||||
skiaShell.FlyoutFooterView = skiaFooter;
|
||||
skiaShell.FlyoutFooterHeight = (float)(footerView.HeightRequest > 0 ? footerView.HeightRequest : 40.0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: use assembly version as footer text
|
||||
var version = Assembly.GetEntryAssembly()?.GetName().Version;
|
||||
skiaShell.FlyoutFooterText = $"Version {version?.Major ?? 1}.{version?.Minor ?? 0}.{version?.Build ?? 0}";
|
||||
}
|
||||
|
||||
// Process shell items into sections
|
||||
foreach (var item in shell.Items)
|
||||
{
|
||||
@@ -210,6 +227,10 @@ public class LinuxViewRenderer
|
||||
// Store reference to SkiaShell for navigation
|
||||
CurrentSkiaShell = skiaShell;
|
||||
|
||||
// Set up content renderer and color refresher delegates
|
||||
skiaShell.ContentRenderer = CreateShellContentPage;
|
||||
skiaShell.ColorRefresher = ApplyShellColors;
|
||||
|
||||
// Subscribe to MAUI Shell navigation events to update SkiaShell
|
||||
shell.Navigated += OnShellNavigated;
|
||||
shell.Navigating += (s, e) => Console.WriteLine($"[Navigation] Navigating: {e.Target}");
|
||||
@@ -223,6 +244,51 @@ public class LinuxViewRenderer
|
||||
return skiaShell;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies shell colors based on the current theme (dark/light mode).
|
||||
/// </summary>
|
||||
private static void ApplyShellColors(SkiaShell skiaShell, Shell shell)
|
||||
{
|
||||
bool isDark = Application.Current?.UserAppTheme == AppTheme.Dark;
|
||||
Console.WriteLine($"[ApplyShellColors] Theme is: {(isDark ? "Dark" : "Light")}");
|
||||
|
||||
// Flyout background color
|
||||
if (shell.FlyoutBackgroundColor != null && shell.FlyoutBackgroundColor != Colors.Transparent)
|
||||
{
|
||||
skiaShell.FlyoutBackgroundColor = shell.FlyoutBackgroundColor;
|
||||
Console.WriteLine($"[ApplyShellColors] FlyoutBackgroundColor from MAUI: {skiaShell.FlyoutBackgroundColor}");
|
||||
}
|
||||
else
|
||||
{
|
||||
skiaShell.FlyoutBackgroundColor = isDark
|
||||
? Color.FromRgb(30, 30, 30)
|
||||
: Color.FromRgb(255, 255, 255);
|
||||
Console.WriteLine($"[ApplyShellColors] Using default FlyoutBackgroundColor: {skiaShell.FlyoutBackgroundColor}");
|
||||
}
|
||||
|
||||
// Flyout text color
|
||||
skiaShell.FlyoutTextColor = isDark
|
||||
? Color.FromRgb(224, 224, 224)
|
||||
: Color.FromRgb(33, 33, 33);
|
||||
Console.WriteLine($"[ApplyShellColors] FlyoutTextColor: {skiaShell.FlyoutTextColor}");
|
||||
|
||||
// Content background color
|
||||
skiaShell.ContentBackgroundColor = isDark
|
||||
? Color.FromRgb(18, 18, 18)
|
||||
: Color.FromRgb(250, 250, 250);
|
||||
Console.WriteLine($"[ApplyShellColors] ContentBackgroundColor: {skiaShell.ContentBackgroundColor}");
|
||||
|
||||
// NavBar background color
|
||||
if (shell.BackgroundColor != null && shell.BackgroundColor != Colors.Transparent)
|
||||
{
|
||||
skiaShell.NavBarBackgroundColor = shell.BackgroundColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
skiaShell.NavBarBackgroundColor = Color.FromRgb(33, 150, 243); // Material blue
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles MAUI Shell navigation events and updates SkiaShell accordingly.
|
||||
/// </summary>
|
||||
@@ -290,7 +356,8 @@ public class LinuxViewRenderer
|
||||
var shellContent = new ShellContent
|
||||
{
|
||||
Title = content.Title ?? shellSection.Title ?? flyoutItem.Title ?? "",
|
||||
Route = content.Route ?? ""
|
||||
Route = content.Route ?? "",
|
||||
MauiShellContent = content
|
||||
};
|
||||
|
||||
// Create the page content
|
||||
@@ -328,7 +395,8 @@ public class LinuxViewRenderer
|
||||
var shellContent = new ShellContent
|
||||
{
|
||||
Title = content.Title ?? tab.Title ?? "",
|
||||
Route = content.Route ?? ""
|
||||
Route = content.Route ?? "",
|
||||
MauiShellContent = content
|
||||
};
|
||||
|
||||
var pageContent = CreateShellContentPage(content);
|
||||
@@ -359,7 +427,8 @@ public class LinuxViewRenderer
|
||||
var shellContent = new ShellContent
|
||||
{
|
||||
Title = content.Title ?? "",
|
||||
Route = content.Route ?? ""
|
||||
Route = content.Route ?? "",
|
||||
MauiShellContent = content
|
||||
};
|
||||
|
||||
var pageContent = CreateShellContentPage(content);
|
||||
@@ -402,17 +471,33 @@ public class LinuxViewRenderer
|
||||
var contentView = RenderView(cp.Content);
|
||||
if (contentView != null)
|
||||
{
|
||||
if (contentView is SkiaScrollView)
|
||||
// Get page background color if set
|
||||
Color? bgColor = null;
|
||||
if (cp.BackgroundColor != null && cp.BackgroundColor != Colors.Transparent)
|
||||
{
|
||||
return contentView;
|
||||
bgColor = cp.BackgroundColor;
|
||||
Console.WriteLine($"[CreateShellContentPage] Page BackgroundColor: {bgColor}");
|
||||
}
|
||||
|
||||
if (contentView is SkiaScrollView scrollView)
|
||||
{
|
||||
if (bgColor != null)
|
||||
{
|
||||
scrollView.BackgroundColor = bgColor;
|
||||
}
|
||||
return scrollView;
|
||||
}
|
||||
else
|
||||
{
|
||||
var scrollView = new SkiaScrollView
|
||||
var newScrollView = new SkiaScrollView
|
||||
{
|
||||
Content = contentView
|
||||
};
|
||||
return scrollView;
|
||||
if (bgColor != null)
|
||||
{
|
||||
newScrollView.BackgroundColor = bgColor;
|
||||
}
|
||||
return newScrollView;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -470,28 +555,9 @@ public class LinuxViewRenderer
|
||||
return new SkiaLabel
|
||||
{
|
||||
Text = $"[{view.GetType().Name}]",
|
||||
TextColor = SKColors.Gray,
|
||||
TextColor = Colors.Gray,
|
||||
FontSize = 12
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for MAUI handler creation.
|
||||
/// </summary>
|
||||
public static class MauiHandlerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a handler for the view and returns it.
|
||||
/// </summary>
|
||||
public static IElementHandler ToHandler(this IElement element, IMauiContext mauiContext)
|
||||
{
|
||||
var handler = mauiContext.Handlers.GetHandler(element.GetType());
|
||||
if (handler != null)
|
||||
{
|
||||
handler.SetMauiContext(mauiContext);
|
||||
handler.SetVirtualView(element);
|
||||
}
|
||||
return handler!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// Copyright (c) 2025 MarketAlly LLC
|
||||
|
||||
using Microsoft.Maui;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Controls.Hosting;
|
||||
using Microsoft.Maui.Hosting;
|
||||
using Microsoft.Maui.Platform.Linux;
|
||||
using Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
namespace OpenMaui.Platform.Linux.Hosting;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for configuring OpenMaui Linux platform in a MAUI application.
|
||||
/// This enables full XAML support by registering Linux-specific handlers.
|
||||
/// </summary>
|
||||
public static class MauiAppBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures the application to use OpenMaui Linux platform with full XAML support.
|
||||
/// </summary>
|
||||
/// <param name="builder">The MAUI app builder.</param>
|
||||
/// <returns>The configured MAUI app builder.</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var builder = MauiApp.CreateBuilder();
|
||||
/// builder
|
||||
/// .UseMauiApp<App>()
|
||||
/// .UseOpenMauiLinux(); // Enable Linux support with XAML
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static MauiAppBuilder UseOpenMauiLinux(this MauiAppBuilder builder)
|
||||
{
|
||||
builder.ConfigureMauiHandlers(handlers =>
|
||||
{
|
||||
// Register all Linux platform handlers
|
||||
// These map MAUI virtual views to our Skia platform views
|
||||
|
||||
// Basic Controls
|
||||
handlers.AddHandler<Button, ButtonHandler>();
|
||||
handlers.AddHandler<Label, LabelHandler>();
|
||||
handlers.AddHandler<Entry, EntryHandler>();
|
||||
handlers.AddHandler<Editor, EditorHandler>();
|
||||
handlers.AddHandler<CheckBox, CheckBoxHandler>();
|
||||
handlers.AddHandler<Switch, SwitchHandler>();
|
||||
handlers.AddHandler<RadioButton, RadioButtonHandler>();
|
||||
|
||||
// Selection Controls
|
||||
handlers.AddHandler<Slider, SliderHandler>();
|
||||
handlers.AddHandler<Stepper, StepperHandler>();
|
||||
handlers.AddHandler<Picker, PickerHandler>();
|
||||
handlers.AddHandler<DatePicker, DatePickerHandler>();
|
||||
handlers.AddHandler<TimePicker, TimePickerHandler>();
|
||||
|
||||
// Display Controls
|
||||
handlers.AddHandler<Image, ImageHandler>();
|
||||
handlers.AddHandler<ImageButton, ImageButtonHandler>();
|
||||
handlers.AddHandler<ActivityIndicator, ActivityIndicatorHandler>();
|
||||
handlers.AddHandler<ProgressBar, ProgressBarHandler>();
|
||||
|
||||
// Layout Controls
|
||||
handlers.AddHandler<Border, BorderHandler>();
|
||||
|
||||
// Collection Controls
|
||||
handlers.AddHandler<CollectionView, CollectionViewHandler>();
|
||||
|
||||
// Navigation Controls
|
||||
handlers.AddHandler<NavigationPage, NavigationPageHandler>();
|
||||
handlers.AddHandler<TabbedPage, TabbedPageHandler>();
|
||||
handlers.AddHandler<FlyoutPage, FlyoutPageHandler>();
|
||||
handlers.AddHandler<Shell, ShellHandler>();
|
||||
|
||||
// Page Controls
|
||||
handlers.AddHandler<Page, PageHandler>();
|
||||
handlers.AddHandler<ContentPage, PageHandler>();
|
||||
|
||||
// Graphics
|
||||
handlers.AddHandler<GraphicsView, GraphicsViewHandler>();
|
||||
|
||||
// Search
|
||||
handlers.AddHandler<SearchBar, SearchBarHandler>();
|
||||
|
||||
// Web
|
||||
handlers.AddHandler<WebView, WebViewHandler>();
|
||||
|
||||
// Window
|
||||
handlers.AddHandler<Window, WindowHandler>();
|
||||
});
|
||||
|
||||
// Register Linux-specific services
|
||||
builder.Services.AddSingleton<ILinuxPlatformServices, LinuxPlatformServices>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the application to use OpenMaui Linux with custom handler configuration.
|
||||
/// </summary>
|
||||
/// <param name="builder">The MAUI app builder.</param>
|
||||
/// <param name="configureHandlers">Action to configure additional handlers.</param>
|
||||
/// <returns>The configured MAUI app builder.</returns>
|
||||
public static MauiAppBuilder UseOpenMauiLinux(
|
||||
this MauiAppBuilder builder,
|
||||
Action<IMauiHandlersCollection>? configureHandlers)
|
||||
{
|
||||
builder.UseOpenMauiLinux();
|
||||
|
||||
if (configureHandlers != null)
|
||||
{
|
||||
builder.ConfigureMauiHandlers(configureHandlers);
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for Linux platform services.
|
||||
/// </summary>
|
||||
public interface ILinuxPlatformServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the display server type (X11 or Wayland).
|
||||
/// </summary>
|
||||
DisplayServerType DisplayServer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current DPI scale factor.
|
||||
/// </summary>
|
||||
float ScaleFactor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether high contrast mode is enabled.
|
||||
/// </summary>
|
||||
bool IsHighContrastEnabled { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display server types supported by OpenMaui.
|
||||
/// </summary>
|
||||
public enum DisplayServerType
|
||||
{
|
||||
/// <summary>X11 display server.</summary>
|
||||
X11,
|
||||
/// <summary>Wayland display server.</summary>
|
||||
Wayland,
|
||||
/// <summary>Auto-detected display server.</summary>
|
||||
Auto
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of Linux platform services.
|
||||
/// </summary>
|
||||
internal class LinuxPlatformServices : ILinuxPlatformServices
|
||||
{
|
||||
public DisplayServerType DisplayServer => DetectDisplayServer();
|
||||
public float ScaleFactor => DetectScaleFactor();
|
||||
public bool IsHighContrastEnabled => DetectHighContrast();
|
||||
|
||||
private static DisplayServerType DetectDisplayServer()
|
||||
{
|
||||
var waylandDisplay = Environment.GetEnvironmentVariable("WAYLAND_DISPLAY");
|
||||
if (!string.IsNullOrEmpty(waylandDisplay))
|
||||
return DisplayServerType.Wayland;
|
||||
|
||||
var display = Environment.GetEnvironmentVariable("DISPLAY");
|
||||
if (!string.IsNullOrEmpty(display))
|
||||
return DisplayServerType.X11;
|
||||
|
||||
return DisplayServerType.Auto;
|
||||
}
|
||||
|
||||
private static float DetectScaleFactor()
|
||||
{
|
||||
// Try GDK_SCALE first
|
||||
var gdkScale = Environment.GetEnvironmentVariable("GDK_SCALE");
|
||||
if (float.TryParse(gdkScale, out var scale))
|
||||
return scale;
|
||||
|
||||
// Default to 1.0
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
private static bool DetectHighContrast()
|
||||
{
|
||||
var highContrast = Environment.GetEnvironmentVariable("GTK_THEME");
|
||||
return highContrast?.Contains("HighContrast", StringComparison.OrdinalIgnoreCase) ?? false;
|
||||
}
|
||||
}
|
||||
123
Hosting/MauiHandlerExtensions.cs
Normal file
123
Hosting/MauiHandlerExtensions.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Hosting;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for creating MAUI handlers on Linux.
|
||||
/// Maps MAUI types to Linux-specific handlers with fallback to MAUI defaults.
|
||||
/// </summary>
|
||||
public static class MauiHandlerExtensions
|
||||
{
|
||||
private static readonly Dictionary<Type, Func<IElementHandler>> LinuxHandlerMap = new Dictionary<Type, Func<IElementHandler>>
|
||||
{
|
||||
[typeof(Button)] = () => new TextButtonHandler(),
|
||||
[typeof(Label)] = () => new LabelHandler(),
|
||||
[typeof(Entry)] = () => new EntryHandler(),
|
||||
[typeof(Editor)] = () => new EditorHandler(),
|
||||
[typeof(CheckBox)] = () => new CheckBoxHandler(),
|
||||
[typeof(Switch)] = () => new SwitchHandler(),
|
||||
[typeof(Slider)] = () => new SliderHandler(),
|
||||
[typeof(Stepper)] = () => new StepperHandler(),
|
||||
[typeof(ProgressBar)] = () => new ProgressBarHandler(),
|
||||
[typeof(ActivityIndicator)] = () => new ActivityIndicatorHandler(),
|
||||
[typeof(Picker)] = () => new PickerHandler(),
|
||||
[typeof(DatePicker)] = () => new DatePickerHandler(),
|
||||
[typeof(TimePicker)] = () => new TimePickerHandler(),
|
||||
[typeof(SearchBar)] = () => new SearchBarHandler(),
|
||||
[typeof(RadioButton)] = () => new RadioButtonHandler(),
|
||||
[typeof(WebView)] = () => new GtkWebViewHandler(),
|
||||
[typeof(Image)] = () => new ImageHandler(),
|
||||
[typeof(ImageButton)] = () => new ImageButtonHandler(),
|
||||
[typeof(BoxView)] = () => new BoxViewHandler(),
|
||||
[typeof(Frame)] = () => new FrameHandler(),
|
||||
[typeof(Border)] = () => new BorderHandler(),
|
||||
[typeof(ContentView)] = () => new BorderHandler(),
|
||||
[typeof(ScrollView)] = () => new ScrollViewHandler(),
|
||||
[typeof(Grid)] = () => new GridHandler(),
|
||||
[typeof(StackLayout)] = () => new StackLayoutHandler(),
|
||||
[typeof(VerticalStackLayout)] = () => new StackLayoutHandler(),
|
||||
[typeof(HorizontalStackLayout)] = () => new StackLayoutHandler(),
|
||||
[typeof(AbsoluteLayout)] = () => new LayoutHandler(),
|
||||
[typeof(FlexLayout)] = () => new LayoutHandler(),
|
||||
[typeof(CollectionView)] = () => new CollectionViewHandler(),
|
||||
[typeof(ListView)] = () => new CollectionViewHandler(),
|
||||
[typeof(Page)] = () => new PageHandler(),
|
||||
[typeof(ContentPage)] = () => new ContentPageHandler(),
|
||||
[typeof(NavigationPage)] = () => new NavigationPageHandler(),
|
||||
[typeof(Shell)] = () => new ShellHandler(),
|
||||
[typeof(FlyoutPage)] = () => new FlyoutPageHandler(),
|
||||
[typeof(TabbedPage)] = () => new TabbedPageHandler(),
|
||||
[typeof(Application)] = () => new ApplicationHandler(),
|
||||
[typeof(Microsoft.Maui.Controls.Window)] = () => new WindowHandler(),
|
||||
[typeof(GraphicsView)] = () => new GraphicsViewHandler()
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Creates an element handler for the given element.
|
||||
/// </summary>
|
||||
public static IElementHandler ToHandler(this IElement element, IMauiContext mauiContext)
|
||||
{
|
||||
return CreateHandler(element, mauiContext)!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a view handler for the given view.
|
||||
/// </summary>
|
||||
public static IViewHandler? ToViewHandler(this IView view, IMauiContext mauiContext)
|
||||
{
|
||||
var handler = CreateHandler((IElement)view, mauiContext);
|
||||
return handler as IViewHandler;
|
||||
}
|
||||
|
||||
private static IElementHandler? CreateHandler(IElement element, IMauiContext mauiContext)
|
||||
{
|
||||
Type type = element.GetType();
|
||||
IElementHandler? handler = null;
|
||||
|
||||
// First, try exact type match
|
||||
if (LinuxHandlerMap.TryGetValue(type, out Func<IElementHandler>? factory))
|
||||
{
|
||||
handler = factory();
|
||||
Console.WriteLine($"[ToHandler] Using Linux handler for {type.Name}: {handler.GetType().Name}");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try to find a base type match
|
||||
Type? bestMatch = null;
|
||||
Func<IElementHandler>? bestFactory = null;
|
||||
|
||||
foreach (var kvp in LinuxHandlerMap)
|
||||
{
|
||||
if (kvp.Key.IsAssignableFrom(type) && (bestMatch == null || bestMatch.IsAssignableFrom(kvp.Key)))
|
||||
{
|
||||
bestMatch = kvp.Key;
|
||||
bestFactory = kvp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestFactory != null)
|
||||
{
|
||||
handler = bestFactory();
|
||||
Console.WriteLine($"[ToHandler] Using Linux handler (via base {bestMatch!.Name}) for {type.Name}: {handler.GetType().Name}");
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to MAUI's default handler
|
||||
if (handler == null)
|
||||
{
|
||||
handler = mauiContext.Handlers.GetHandler(type);
|
||||
Console.WriteLine($"[ToHandler] Using MAUI handler for {type.Name}: {handler?.GetType().Name ?? "null"}");
|
||||
}
|
||||
|
||||
if (handler != null)
|
||||
{
|
||||
handler.SetMauiContext(mauiContext);
|
||||
handler.SetVirtualView(element);
|
||||
}
|
||||
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
18
Hosting/ScopedLinuxMauiContext.cs
Normal file
18
Hosting/ScopedLinuxMauiContext.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Hosting;
|
||||
|
||||
public class ScopedLinuxMauiContext : IMauiContext
|
||||
{
|
||||
private readonly LinuxMauiContext _parent;
|
||||
|
||||
public IServiceProvider Services => _parent.Services;
|
||||
|
||||
public IMauiHandlersFactory Handlers => _parent.Handlers;
|
||||
|
||||
public ScopedLinuxMauiContext(LinuxMauiContext parent)
|
||||
{
|
||||
_parent = parent ?? throw new ArgumentNullException(nameof(parent));
|
||||
}
|
||||
}
|
||||
25
Interop/ClientMessageData.cs
Normal file
25
Interop/ClientMessageData.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct ClientMessageData
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public long L0;
|
||||
|
||||
[FieldOffset(8)]
|
||||
public long L1;
|
||||
|
||||
[FieldOffset(16)]
|
||||
public long L2;
|
||||
|
||||
[FieldOffset(24)]
|
||||
public long L3;
|
||||
|
||||
[FieldOffset(32)]
|
||||
public long L4;
|
||||
}
|
||||
345
Interop/WebKitGtk.cs
Normal file
345
Interop/WebKitGtk.cs
Normal file
@@ -0,0 +1,345 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
/// <summary>
|
||||
/// P/Invoke bindings for WebKitGTK library.
|
||||
/// WebKitGTK provides a full-featured web browser engine for Linux.
|
||||
/// </summary>
|
||||
public static class WebKitGtk
|
||||
{
|
||||
private const string WebKit2Lib = "libwebkit2gtk-4.1.so.0";
|
||||
private const string GtkLib = "libgtk-3.so.0";
|
||||
private const string GObjectLib = "libgobject-2.0.so.0";
|
||||
private const string GLibLib = "libglib-2.0.so.0";
|
||||
|
||||
#region GTK Initialization
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern bool gtk_init_check(ref int argc, ref IntPtr argv);
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void gtk_main();
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void gtk_main_quit();
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern bool gtk_events_pending();
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void gtk_main_iteration();
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern bool gtk_main_iteration_do(bool blocking);
|
||||
|
||||
#endregion
|
||||
|
||||
#region GTK Window
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr gtk_window_new(int type);
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void gtk_window_set_default_size(IntPtr window, int width, int height);
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void gtk_window_set_decorated(IntPtr window, bool decorated);
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void gtk_window_move(IntPtr window, int x, int y);
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void gtk_window_resize(IntPtr window, int width, int height);
|
||||
|
||||
#endregion
|
||||
|
||||
#region GTK Widget
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void gtk_widget_show_all(IntPtr widget);
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void gtk_widget_show(IntPtr widget);
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void gtk_widget_hide(IntPtr widget);
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void gtk_widget_destroy(IntPtr widget);
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void gtk_widget_set_size_request(IntPtr widget, int width, int height);
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void gtk_widget_realize(IntPtr widget);
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr gtk_widget_get_window(IntPtr widget);
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void gtk_widget_set_can_focus(IntPtr widget, bool canFocus);
|
||||
|
||||
#endregion
|
||||
|
||||
#region GTK Container
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void gtk_container_add(IntPtr container, IntPtr widget);
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void gtk_container_remove(IntPtr container, IntPtr widget);
|
||||
|
||||
#endregion
|
||||
|
||||
#region GTK Plug (for embedding in X11 windows)
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr gtk_plug_new(ulong socketId);
|
||||
|
||||
[DllImport(GtkLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern ulong gtk_plug_get_id(IntPtr plug);
|
||||
|
||||
#endregion
|
||||
|
||||
#region WebKitWebView
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr webkit_web_view_new();
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr webkit_web_view_new_with_context(IntPtr context);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void webkit_web_view_load_uri(IntPtr webView, [MarshalAs(UnmanagedType.LPUTF8Str)] string uri);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void webkit_web_view_load_html(IntPtr webView,
|
||||
[MarshalAs(UnmanagedType.LPUTF8Str)] string content,
|
||||
[MarshalAs(UnmanagedType.LPUTF8Str)] string? baseUri);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void webkit_web_view_reload(IntPtr webView);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void webkit_web_view_stop_loading(IntPtr webView);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void webkit_web_view_go_back(IntPtr webView);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void webkit_web_view_go_forward(IntPtr webView);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern bool webkit_web_view_can_go_back(IntPtr webView);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern bool webkit_web_view_can_go_forward(IntPtr webView);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr webkit_web_view_get_uri(IntPtr webView);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr webkit_web_view_get_title(IntPtr webView);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern double webkit_web_view_get_estimated_load_progress(IntPtr webView);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern bool webkit_web_view_is_loading(IntPtr webView);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void webkit_web_view_run_javascript(IntPtr webView,
|
||||
[MarshalAs(UnmanagedType.LPUTF8Str)] string script,
|
||||
IntPtr cancellable,
|
||||
IntPtr callback,
|
||||
IntPtr userData);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr webkit_web_view_run_javascript_finish(IntPtr webView,
|
||||
IntPtr result,
|
||||
out IntPtr error);
|
||||
|
||||
#endregion
|
||||
|
||||
#region WebKitSettings
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr webkit_web_view_get_settings(IntPtr webView);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void webkit_settings_set_enable_javascript(IntPtr settings, bool enabled);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void webkit_settings_set_user_agent(IntPtr settings,
|
||||
[MarshalAs(UnmanagedType.LPUTF8Str)] string userAgent);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr webkit_settings_get_user_agent(IntPtr settings);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void webkit_settings_set_enable_developer_extras(IntPtr settings, bool enabled);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void webkit_settings_set_javascript_can_access_clipboard(IntPtr settings, bool enabled);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void webkit_settings_set_enable_webgl(IntPtr settings, bool enabled);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void webkit_settings_set_allow_file_access_from_file_urls(IntPtr settings, bool enabled);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void webkit_settings_set_allow_universal_access_from_file_urls(IntPtr settings, bool enabled);
|
||||
|
||||
#endregion
|
||||
|
||||
#region WebKitWebContext
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr webkit_web_context_get_default();
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr webkit_web_context_new();
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr webkit_web_context_get_cookie_manager(IntPtr context);
|
||||
|
||||
#endregion
|
||||
|
||||
#region WebKitCookieManager
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void webkit_cookie_manager_set_accept_policy(IntPtr cookieManager, int policy);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void webkit_cookie_manager_set_persistent_storage(IntPtr cookieManager,
|
||||
[MarshalAs(UnmanagedType.LPUTF8Str)] string filename,
|
||||
int storage);
|
||||
|
||||
// Cookie accept policies
|
||||
public const int WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS = 0;
|
||||
public const int WEBKIT_COOKIE_POLICY_ACCEPT_NEVER = 1;
|
||||
public const int WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY = 2;
|
||||
|
||||
// Cookie persistent storage types
|
||||
public const int WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT = 0;
|
||||
public const int WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE = 1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region WebKitNavigationAction
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr webkit_navigation_action_get_request(IntPtr action);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int webkit_navigation_action_get_navigation_type(IntPtr action);
|
||||
|
||||
#endregion
|
||||
|
||||
#region WebKitURIRequest
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr webkit_uri_request_get_uri(IntPtr request);
|
||||
|
||||
#endregion
|
||||
|
||||
#region WebKitPolicyDecision
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void webkit_policy_decision_use(IntPtr decision);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void webkit_policy_decision_ignore(IntPtr decision);
|
||||
|
||||
[DllImport(WebKit2Lib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void webkit_policy_decision_download(IntPtr decision);
|
||||
|
||||
#endregion
|
||||
|
||||
#region GObject Signal Connection
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void GCallback();
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void LoadChangedCallback(IntPtr webView, int loadEvent, IntPtr userData);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate bool DecidePolicyCallback(IntPtr webView, IntPtr decision, int decisionType, IntPtr userData);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void LoadFailedCallback(IntPtr webView, int loadEvent, IntPtr failingUri, IntPtr error, IntPtr userData);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void NotifyCallback(IntPtr webView, IntPtr paramSpec, IntPtr userData);
|
||||
|
||||
[DllImport(GObjectLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern ulong g_signal_connect_data(IntPtr instance,
|
||||
[MarshalAs(UnmanagedType.LPUTF8Str)] string detailedSignal,
|
||||
Delegate handler,
|
||||
IntPtr data,
|
||||
IntPtr destroyData,
|
||||
int connectFlags);
|
||||
|
||||
[DllImport(GObjectLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void g_signal_handler_disconnect(IntPtr instance, ulong handlerId);
|
||||
|
||||
[DllImport(GObjectLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void g_object_unref(IntPtr obj);
|
||||
|
||||
#endregion
|
||||
|
||||
#region GLib Memory
|
||||
|
||||
[DllImport(GLibLib, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void g_free(IntPtr mem);
|
||||
|
||||
#endregion
|
||||
|
||||
#region WebKit Load Events
|
||||
|
||||
public const int WEBKIT_LOAD_STARTED = 0;
|
||||
public const int WEBKIT_LOAD_REDIRECTED = 1;
|
||||
public const int WEBKIT_LOAD_COMMITTED = 2;
|
||||
public const int WEBKIT_LOAD_FINISHED = 3;
|
||||
|
||||
#endregion
|
||||
|
||||
#region WebKit Policy Decision Types
|
||||
|
||||
public const int WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION = 0;
|
||||
public const int WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION = 1;
|
||||
public const int WEBKIT_POLICY_DECISION_TYPE_RESPONSE = 2;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Converts a native UTF-8 string pointer to a managed string.
|
||||
/// </summary>
|
||||
public static string? PtrToStringUtf8(IntPtr ptr)
|
||||
{
|
||||
if (ptr == IntPtr.Zero)
|
||||
return null;
|
||||
return Marshal.PtrToStringUTF8(ptr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes pending GTK events without blocking.
|
||||
/// </summary>
|
||||
public static void ProcessGtkEvents()
|
||||
{
|
||||
while (gtk_events_pending())
|
||||
{
|
||||
gtk_main_iteration_do(false);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
239
Interop/X11.cs
Normal file
239
Interop/X11.cs
Normal file
@@ -0,0 +1,239 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
internal static partial class X11
|
||||
{
|
||||
private const string LibX11 = "libX11.so.6";
|
||||
|
||||
public const int ZPixmap = 2;
|
||||
|
||||
// Event types
|
||||
public const int ClientMessage = 33;
|
||||
|
||||
// Event masks for XSendEvent
|
||||
public const long SubstructureRedirectMask = 1L << 20;
|
||||
public const long SubstructureNotifyMask = 1L << 19;
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XOpenDisplay(IntPtr displayName);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XCloseDisplay(IntPtr display);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XDefaultScreen(IntPtr display);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XRootWindow(IntPtr display, int screenNumber);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XDisplayWidth(IntPtr display, int screenNumber);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XDisplayHeight(IntPtr display, int screenNumber);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XDefaultDepth(IntPtr display, int screenNumber);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XDefaultVisual(IntPtr display, int screenNumber);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XDefaultColormap(IntPtr display, int screenNumber);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XFlush(IntPtr display);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XSync(IntPtr display, [MarshalAs(UnmanagedType.Bool)] bool discard);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XCreateSimpleWindow(
|
||||
IntPtr display, IntPtr parent,
|
||||
int x, int y, uint width, uint height,
|
||||
uint borderWidth, ulong border, ulong background);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XCreateWindow(
|
||||
IntPtr display, IntPtr parent,
|
||||
int x, int y, uint width, uint height, uint borderWidth,
|
||||
int depth, uint windowClass, IntPtr visual,
|
||||
ulong valueMask, ref XSetWindowAttributes attributes);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XDestroyWindow(IntPtr display, IntPtr window);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XMapWindow(IntPtr display, IntPtr window);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XUnmapWindow(IntPtr display, IntPtr window);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XMoveWindow(IntPtr display, IntPtr window, int x, int y);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XResizeWindow(IntPtr display, IntPtr window, uint width, uint height);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XIconifyWindow(IntPtr display, IntPtr window, int screen);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XMoveResizeWindow(IntPtr display, IntPtr window, int x, int y, uint width, uint height);
|
||||
|
||||
[LibraryImport(LibX11, StringMarshalling = StringMarshalling.Utf8)]
|
||||
public static partial int XStoreName(IntPtr display, IntPtr window, string windowName);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XSetClassHint(IntPtr display, IntPtr window, ref XClassHint classHint);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XRaiseWindow(IntPtr display, IntPtr window);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XLowerWindow(IntPtr display, IntPtr window);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XSelectInput(IntPtr display, IntPtr window, long eventMask);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XNextEvent(IntPtr display, out XEvent eventReturn);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XPeekEvent(IntPtr display, out XEvent eventReturn);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XPending(IntPtr display);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool XCheckTypedWindowEvent(IntPtr display, IntPtr window, int eventType, out XEvent eventReturn);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XSendEvent(
|
||||
IntPtr display, IntPtr window,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool propagate,
|
||||
long eventMask, ref XEvent eventSend);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial ulong XKeycodeToKeysym(IntPtr display, int keycode, int index);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XLookupString(
|
||||
ref XKeyEvent keyEvent, IntPtr bufferReturn, int bytesBuffer,
|
||||
out ulong keysymReturn, IntPtr statusInOut);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XGrabKeyboard(
|
||||
IntPtr display, IntPtr grabWindow,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool ownerEvents,
|
||||
int pointerMode, int keyboardMode, ulong time);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XUngrabKeyboard(IntPtr display, ulong time);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XGrabPointer(
|
||||
IntPtr display, IntPtr grabWindow,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool ownerEvents,
|
||||
uint eventMask, int pointerMode, int keyboardMode,
|
||||
IntPtr confineTo, IntPtr cursor, ulong time);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XUngrabPointer(IntPtr display, ulong time);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool XQueryPointer(
|
||||
IntPtr display, IntPtr window,
|
||||
out IntPtr rootReturn, out IntPtr childReturn,
|
||||
out int rootX, out int rootY,
|
||||
out int winX, out int winY,
|
||||
out uint maskReturn);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XWarpPointer(
|
||||
IntPtr display, IntPtr srcWindow, IntPtr destWindow,
|
||||
int srcX, int srcY, uint srcWidth, uint srcHeight,
|
||||
int destX, int destY);
|
||||
|
||||
[LibraryImport(LibX11, StringMarshalling = StringMarshalling.Utf8)]
|
||||
public static partial IntPtr XInternAtom(IntPtr display, string atomName, [MarshalAs(UnmanagedType.Bool)] bool onlyIfExists);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XChangeProperty(
|
||||
IntPtr display, IntPtr window, IntPtr property, IntPtr type,
|
||||
int format, int mode, IntPtr data, int nelements);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XGetWindowProperty(
|
||||
IntPtr display, IntPtr window, IntPtr property,
|
||||
long longOffset, long longLength,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool delete, IntPtr reqType,
|
||||
out IntPtr actualTypeReturn, out int actualFormatReturn,
|
||||
out IntPtr nitemsReturn, out IntPtr bytesAfterReturn,
|
||||
out IntPtr propReturn);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XDeleteProperty(IntPtr display, IntPtr window, IntPtr property);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XSetSelectionOwner(IntPtr display, IntPtr selection, IntPtr owner, ulong time);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XGetSelectionOwner(IntPtr display, IntPtr selection);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XConvertSelection(
|
||||
IntPtr display, IntPtr selection, IntPtr target,
|
||||
IntPtr property, IntPtr requestor, ulong time);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XFree(IntPtr data);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XCreateGC(IntPtr display, IntPtr drawable, ulong valueMask, IntPtr values);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XFreeGC(IntPtr display, IntPtr gc);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XCopyArea(
|
||||
IntPtr display, IntPtr src, IntPtr dest, IntPtr gc,
|
||||
int srcX, int srcY, uint width, uint height, int destX, int destY);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XCreateFontCursor(IntPtr display, uint shape);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XFreeCursor(IntPtr display, IntPtr cursor);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XDefineCursor(IntPtr display, IntPtr window, IntPtr cursor);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XUndefineCursor(IntPtr display, IntPtr window);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XConnectionNumber(IntPtr display);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XCreateImage(
|
||||
IntPtr display, IntPtr visual, uint depth, int format, int offset,
|
||||
IntPtr data, uint width, uint height, int bitmapPad, int bytesPerLine);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XPutImage(
|
||||
IntPtr display, IntPtr drawable, IntPtr gc, IntPtr image,
|
||||
int srcX, int srcY, int destX, int destY, uint width, uint height);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XDestroyImage(IntPtr image);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XDefaultGC(IntPtr display, int screen);
|
||||
}
|
||||
@@ -1,482 +0,0 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
/// <summary>
|
||||
/// P/Invoke declarations for X11 library functions.
|
||||
/// </summary>
|
||||
internal static partial class X11
|
||||
{
|
||||
private const string LibX11 = "libX11.so.6";
|
||||
private const string LibXext = "libXext.so.6";
|
||||
|
||||
#region Display and Screen
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XOpenDisplay(IntPtr displayName);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XCloseDisplay(IntPtr display);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XDefaultScreen(IntPtr display);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XRootWindow(IntPtr display, int screenNumber);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XDisplayWidth(IntPtr display, int screenNumber);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XDisplayHeight(IntPtr display, int screenNumber);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XDefaultDepth(IntPtr display, int screenNumber);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XDefaultVisual(IntPtr display, int screenNumber);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XDefaultColormap(IntPtr display, int screenNumber);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XFlush(IntPtr display);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XSync(IntPtr display, [MarshalAs(UnmanagedType.Bool)] bool discard);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Window Creation and Management
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XCreateSimpleWindow(
|
||||
IntPtr display,
|
||||
IntPtr parent,
|
||||
int x, int y,
|
||||
uint width, uint height,
|
||||
uint borderWidth,
|
||||
ulong border,
|
||||
ulong background);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XCreateWindow(
|
||||
IntPtr display,
|
||||
IntPtr parent,
|
||||
int x, int y,
|
||||
uint width, uint height,
|
||||
uint borderWidth,
|
||||
int depth,
|
||||
uint windowClass,
|
||||
IntPtr visual,
|
||||
ulong valueMask,
|
||||
ref XSetWindowAttributes attributes);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XDestroyWindow(IntPtr display, IntPtr window);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XMapWindow(IntPtr display, IntPtr window);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XUnmapWindow(IntPtr display, IntPtr window);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XMoveWindow(IntPtr display, IntPtr window, int x, int y);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XResizeWindow(IntPtr display, IntPtr window, uint width, uint height);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XMoveResizeWindow(IntPtr display, IntPtr window, int x, int y, uint width, uint height);
|
||||
|
||||
[LibraryImport(LibX11, StringMarshalling = StringMarshalling.Utf8)]
|
||||
public static partial int XStoreName(IntPtr display, IntPtr window, string windowName);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XRaiseWindow(IntPtr display, IntPtr window);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XLowerWindow(IntPtr display, IntPtr window);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handling
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XSelectInput(IntPtr display, IntPtr window, long eventMask);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XNextEvent(IntPtr display, out XEvent eventReturn);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XPeekEvent(IntPtr display, out XEvent eventReturn);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XPending(IntPtr display);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool XCheckTypedWindowEvent(IntPtr display, IntPtr window, int eventType, out XEvent eventReturn);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XSendEvent(IntPtr display, IntPtr window, [MarshalAs(UnmanagedType.Bool)] bool propagate, long eventMask, ref XEvent eventSend);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Keyboard
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial ulong XKeycodeToKeysym(IntPtr display, int keycode, int index);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XLookupString(ref XKeyEvent keyEvent, IntPtr bufferReturn, int bytesBuffer, out ulong keysymReturn, IntPtr statusInOut);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XGrabKeyboard(IntPtr display, IntPtr grabWindow, [MarshalAs(UnmanagedType.Bool)] bool ownerEvents, int pointerMode, int keyboardMode, ulong time);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XUngrabKeyboard(IntPtr display, ulong time);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Mouse/Pointer
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XGrabPointer(IntPtr display, IntPtr grabWindow, [MarshalAs(UnmanagedType.Bool)] bool ownerEvents, uint eventMask, int pointerMode, int keyboardMode, IntPtr confineTo, IntPtr cursor, ulong time);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XUngrabPointer(IntPtr display, ulong time);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool XQueryPointer(IntPtr display, IntPtr window, out IntPtr rootReturn, out IntPtr childReturn, out int rootX, out int rootY, out int winX, out int winY, out uint maskReturn);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XWarpPointer(IntPtr display, IntPtr srcWindow, IntPtr destWindow, int srcX, int srcY, uint srcWidth, uint srcHeight, int destX, int destY);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Atoms and Properties
|
||||
|
||||
[LibraryImport(LibX11, StringMarshalling = StringMarshalling.Utf8)]
|
||||
public static partial IntPtr XInternAtom(IntPtr display, string atomName, [MarshalAs(UnmanagedType.Bool)] bool onlyIfExists);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XChangeProperty(IntPtr display, IntPtr window, IntPtr property, IntPtr type, int format, int mode, IntPtr data, int nelements);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XGetWindowProperty(IntPtr display, IntPtr window, IntPtr property, long longOffset, long longLength, [MarshalAs(UnmanagedType.Bool)] bool delete, IntPtr reqType, out IntPtr actualTypeReturn, out int actualFormatReturn, out IntPtr nitemsReturn, out IntPtr bytesAfterReturn, out IntPtr propReturn);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XDeleteProperty(IntPtr display, IntPtr window, IntPtr property);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Clipboard/Selection
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XSetSelectionOwner(IntPtr display, IntPtr selection, IntPtr owner, ulong time);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XGetSelectionOwner(IntPtr display, IntPtr selection);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XConvertSelection(IntPtr display, IntPtr selection, IntPtr target, IntPtr property, IntPtr requestor, ulong time);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Memory
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XFree(IntPtr data);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Graphics Context
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XCreateGC(IntPtr display, IntPtr drawable, ulong valueMask, IntPtr values);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XFreeGC(IntPtr display, IntPtr gc);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XCopyArea(IntPtr display, IntPtr src, IntPtr dest, IntPtr gc, int srcX, int srcY, uint width, uint height, int destX, int destY);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cursor
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XCreateFontCursor(IntPtr display, uint shape);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XFreeCursor(IntPtr display, IntPtr cursor);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XDefineCursor(IntPtr display, IntPtr window, IntPtr cursor);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XUndefineCursor(IntPtr display, IntPtr window);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Connection
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XConnectionNumber(IntPtr display);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Image Functions
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XCreateImage(IntPtr display, IntPtr visual, uint depth, int format,
|
||||
int offset, IntPtr data, uint width, uint height, int bitmapPad, int bytesPerLine);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, IntPtr image,
|
||||
int srcX, int srcY, int destX, int destY, uint width, uint height);
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial int XDestroyImage(IntPtr image);
|
||||
|
||||
|
||||
[LibraryImport(LibX11)]
|
||||
public static partial IntPtr XDefaultGC(IntPtr display, int screen);
|
||||
|
||||
public const int ZPixmap = 2;
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
#region X11 Structures
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct XSetWindowAttributes
|
||||
{
|
||||
public IntPtr BackgroundPixmap;
|
||||
public ulong BackgroundPixel;
|
||||
public IntPtr BorderPixmap;
|
||||
public ulong BorderPixel;
|
||||
public int BitGravity;
|
||||
public int WinGravity;
|
||||
public int BackingStore;
|
||||
public ulong BackingPlanes;
|
||||
public ulong BackingPixel;
|
||||
public int SaveUnder;
|
||||
public long EventMask;
|
||||
public long DoNotPropagateMask;
|
||||
public int OverrideRedirect;
|
||||
public IntPtr Colormap;
|
||||
public IntPtr Cursor;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 192)]
|
||||
public struct XEvent
|
||||
{
|
||||
[FieldOffset(0)] public int Type;
|
||||
[FieldOffset(0)] public XKeyEvent KeyEvent;
|
||||
[FieldOffset(0)] public XButtonEvent ButtonEvent;
|
||||
[FieldOffset(0)] public XMotionEvent MotionEvent;
|
||||
[FieldOffset(0)] public XConfigureEvent ConfigureEvent;
|
||||
[FieldOffset(0)] public XExposeEvent ExposeEvent;
|
||||
[FieldOffset(0)] public XClientMessageEvent ClientMessageEvent;
|
||||
[FieldOffset(0)] public XCrossingEvent CrossingEvent;
|
||||
[FieldOffset(0)] public XFocusChangeEvent FocusChangeEvent;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct XKeyEvent
|
||||
{
|
||||
public int Type;
|
||||
public ulong Serial;
|
||||
public int SendEvent;
|
||||
public IntPtr Display;
|
||||
public IntPtr Window;
|
||||
public IntPtr Root;
|
||||
public IntPtr Subwindow;
|
||||
public ulong Time;
|
||||
public int X, Y;
|
||||
public int XRoot, YRoot;
|
||||
public uint State;
|
||||
public uint Keycode;
|
||||
public int SameScreen;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct XButtonEvent
|
||||
{
|
||||
public int Type;
|
||||
public ulong Serial;
|
||||
public int SendEvent;
|
||||
public IntPtr Display;
|
||||
public IntPtr Window;
|
||||
public IntPtr Root;
|
||||
public IntPtr Subwindow;
|
||||
public ulong Time;
|
||||
public int X, Y;
|
||||
public int XRoot, YRoot;
|
||||
public uint State;
|
||||
public uint Button;
|
||||
public int SameScreen;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct XMotionEvent
|
||||
{
|
||||
public int Type;
|
||||
public ulong Serial;
|
||||
public int SendEvent;
|
||||
public IntPtr Display;
|
||||
public IntPtr Window;
|
||||
public IntPtr Root;
|
||||
public IntPtr Subwindow;
|
||||
public ulong Time;
|
||||
public int X, Y;
|
||||
public int XRoot, YRoot;
|
||||
public uint State;
|
||||
public byte IsHint;
|
||||
public int SameScreen;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct XConfigureEvent
|
||||
{
|
||||
public int Type;
|
||||
public ulong Serial;
|
||||
public int SendEvent;
|
||||
public IntPtr Display;
|
||||
public IntPtr Event;
|
||||
public IntPtr Window;
|
||||
public int X, Y;
|
||||
public int Width, Height;
|
||||
public int BorderWidth;
|
||||
public IntPtr Above;
|
||||
public int OverrideRedirect;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct XExposeEvent
|
||||
{
|
||||
public int Type;
|
||||
public ulong Serial;
|
||||
public int SendEvent;
|
||||
public IntPtr Display;
|
||||
public IntPtr Window;
|
||||
public int X, Y;
|
||||
public int Width, Height;
|
||||
public int Count;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct XClientMessageEvent
|
||||
{
|
||||
public int Type;
|
||||
public ulong Serial;
|
||||
public int SendEvent;
|
||||
public IntPtr Display;
|
||||
public IntPtr Window;
|
||||
public IntPtr MessageType;
|
||||
public int Format;
|
||||
public ClientMessageData Data;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct ClientMessageData
|
||||
{
|
||||
[FieldOffset(0)] public long L0;
|
||||
[FieldOffset(8)] public long L1;
|
||||
[FieldOffset(16)] public long L2;
|
||||
[FieldOffset(24)] public long L3;
|
||||
[FieldOffset(32)] public long L4;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct XCrossingEvent
|
||||
{
|
||||
public int Type;
|
||||
public ulong Serial;
|
||||
public int SendEvent;
|
||||
public IntPtr Display;
|
||||
public IntPtr Window;
|
||||
public IntPtr Root;
|
||||
public IntPtr Subwindow;
|
||||
public ulong Time;
|
||||
public int X, Y;
|
||||
public int XRoot, YRoot;
|
||||
public int Mode;
|
||||
public int Detail;
|
||||
public int SameScreen;
|
||||
public int Focus;
|
||||
public uint State;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct XFocusChangeEvent
|
||||
{
|
||||
public int Type;
|
||||
public ulong Serial;
|
||||
public int SendEvent;
|
||||
public IntPtr Display;
|
||||
public IntPtr Window;
|
||||
public int Mode;
|
||||
public int Detail;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region X11 Constants
|
||||
|
||||
public static class XEventType
|
||||
{
|
||||
public const int KeyPress = 2;
|
||||
public const int KeyRelease = 3;
|
||||
public const int ButtonPress = 4;
|
||||
public const int ButtonRelease = 5;
|
||||
public const int MotionNotify = 6;
|
||||
public const int EnterNotify = 7;
|
||||
public const int LeaveNotify = 8;
|
||||
public const int FocusIn = 9;
|
||||
public const int FocusOut = 10;
|
||||
public const int Expose = 12;
|
||||
public const int ConfigureNotify = 22;
|
||||
public const int ClientMessage = 33;
|
||||
}
|
||||
|
||||
public static class XEventMask
|
||||
{
|
||||
public const long KeyPressMask = 1L << 0;
|
||||
public const long KeyReleaseMask = 1L << 1;
|
||||
public const long ButtonPressMask = 1L << 2;
|
||||
public const long ButtonReleaseMask = 1L << 3;
|
||||
public const long EnterWindowMask = 1L << 4;
|
||||
public const long LeaveWindowMask = 1L << 5;
|
||||
public const long PointerMotionMask = 1L << 6;
|
||||
public const long ExposureMask = 1L << 15;
|
||||
public const long StructureNotifyMask = 1L << 17;
|
||||
public const long FocusChangeMask = 1L << 21;
|
||||
}
|
||||
|
||||
public static class XWindowClass
|
||||
{
|
||||
public const uint InputOutput = 1;
|
||||
public const uint InputOnly = 2;
|
||||
}
|
||||
|
||||
public static class XCursorShape
|
||||
{
|
||||
public const uint XC_left_ptr = 68;
|
||||
public const uint XC_hand2 = 60;
|
||||
public const uint XC_xterm = 152;
|
||||
public const uint XC_watch = 150;
|
||||
public const uint XC_crosshair = 34;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
23
Interop/XButtonEvent.cs
Normal file
23
Interop/XButtonEvent.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
public struct XButtonEvent
|
||||
{
|
||||
public int Type;
|
||||
public ulong Serial;
|
||||
public int SendEvent;
|
||||
public IntPtr Display;
|
||||
public IntPtr Window;
|
||||
public IntPtr Root;
|
||||
public IntPtr Subwindow;
|
||||
public ulong Time;
|
||||
public int X;
|
||||
public int Y;
|
||||
public int XRoot;
|
||||
public int YRoot;
|
||||
public uint State;
|
||||
public uint Button;
|
||||
public int SameScreen;
|
||||
}
|
||||
13
Interop/XClassHint.cs
Normal file
13
Interop/XClassHint.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct XClassHint
|
||||
{
|
||||
public IntPtr res_name;
|
||||
public IntPtr res_class;
|
||||
}
|
||||
16
Interop/XClientMessageEvent.cs
Normal file
16
Interop/XClientMessageEvent.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
public struct XClientMessageEvent
|
||||
{
|
||||
public int Type;
|
||||
public ulong Serial;
|
||||
public int SendEvent;
|
||||
public IntPtr Display;
|
||||
public IntPtr Window;
|
||||
public IntPtr MessageType;
|
||||
public int Format;
|
||||
public ClientMessageData Data;
|
||||
}
|
||||
21
Interop/XConfigureEvent.cs
Normal file
21
Interop/XConfigureEvent.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
public struct XConfigureEvent
|
||||
{
|
||||
public int Type;
|
||||
public ulong Serial;
|
||||
public int SendEvent;
|
||||
public IntPtr Display;
|
||||
public IntPtr Event;
|
||||
public IntPtr Window;
|
||||
public int X;
|
||||
public int Y;
|
||||
public int Width;
|
||||
public int Height;
|
||||
public int BorderWidth;
|
||||
public IntPtr Above;
|
||||
public int OverrideRedirect;
|
||||
}
|
||||
25
Interop/XCrossingEvent.cs
Normal file
25
Interop/XCrossingEvent.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
public struct XCrossingEvent
|
||||
{
|
||||
public int Type;
|
||||
public ulong Serial;
|
||||
public int SendEvent;
|
||||
public IntPtr Display;
|
||||
public IntPtr Window;
|
||||
public IntPtr Root;
|
||||
public IntPtr Subwindow;
|
||||
public ulong Time;
|
||||
public int X;
|
||||
public int Y;
|
||||
public int XRoot;
|
||||
public int YRoot;
|
||||
public int Mode;
|
||||
public int Detail;
|
||||
public int SameScreen;
|
||||
public int Focus;
|
||||
public uint State;
|
||||
}
|
||||
13
Interop/XCursorShape.cs
Normal file
13
Interop/XCursorShape.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
public static class XCursorShape
|
||||
{
|
||||
public const uint XC_left_ptr = 68;
|
||||
public const uint XC_hand2 = 60;
|
||||
public const uint XC_xterm = 152;
|
||||
public const uint XC_watch = 150;
|
||||
public const uint XC_crosshair = 34;
|
||||
}
|
||||
37
Interop/XEvent.cs
Normal file
37
Interop/XEvent.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 192)]
|
||||
public struct XEvent
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public int Type;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public XKeyEvent KeyEvent;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public XButtonEvent ButtonEvent;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public XMotionEvent MotionEvent;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public XConfigureEvent ConfigureEvent;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public XExposeEvent ExposeEvent;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public XClientMessageEvent ClientMessageEvent;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public XCrossingEvent CrossingEvent;
|
||||
|
||||
[FieldOffset(0)]
|
||||
public XFocusChangeEvent FocusChangeEvent;
|
||||
}
|
||||
18
Interop/XEventMask.cs
Normal file
18
Interop/XEventMask.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
public static class XEventMask
|
||||
{
|
||||
public const long KeyPressMask = 1L;
|
||||
public const long KeyReleaseMask = 2L;
|
||||
public const long ButtonPressMask = 4L;
|
||||
public const long ButtonReleaseMask = 8L;
|
||||
public const long EnterWindowMask = 16L;
|
||||
public const long LeaveWindowMask = 32L;
|
||||
public const long PointerMotionMask = 64L;
|
||||
public const long ExposureMask = 32768L;
|
||||
public const long StructureNotifyMask = 131072L;
|
||||
public const long FocusChangeMask = 2097152L;
|
||||
}
|
||||
20
Interop/XEventType.cs
Normal file
20
Interop/XEventType.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
public static class XEventType
|
||||
{
|
||||
public const int KeyPress = 2;
|
||||
public const int KeyRelease = 3;
|
||||
public const int ButtonPress = 4;
|
||||
public const int ButtonRelease = 5;
|
||||
public const int MotionNotify = 6;
|
||||
public const int EnterNotify = 7;
|
||||
public const int LeaveNotify = 8;
|
||||
public const int FocusIn = 9;
|
||||
public const int FocusOut = 10;
|
||||
public const int Expose = 12;
|
||||
public const int ConfigureNotify = 22;
|
||||
public const int ClientMessage = 33;
|
||||
}
|
||||
18
Interop/XExposeEvent.cs
Normal file
18
Interop/XExposeEvent.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
public struct XExposeEvent
|
||||
{
|
||||
public int Type;
|
||||
public ulong Serial;
|
||||
public int SendEvent;
|
||||
public IntPtr Display;
|
||||
public IntPtr Window;
|
||||
public int X;
|
||||
public int Y;
|
||||
public int Width;
|
||||
public int Height;
|
||||
public int Count;
|
||||
}
|
||||
15
Interop/XFocusChangeEvent.cs
Normal file
15
Interop/XFocusChangeEvent.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
public struct XFocusChangeEvent
|
||||
{
|
||||
public int Type;
|
||||
public ulong Serial;
|
||||
public int SendEvent;
|
||||
public IntPtr Display;
|
||||
public IntPtr Window;
|
||||
public int Mode;
|
||||
public int Detail;
|
||||
}
|
||||
23
Interop/XKeyEvent.cs
Normal file
23
Interop/XKeyEvent.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
public struct XKeyEvent
|
||||
{
|
||||
public int Type;
|
||||
public ulong Serial;
|
||||
public int SendEvent;
|
||||
public IntPtr Display;
|
||||
public IntPtr Window;
|
||||
public IntPtr Root;
|
||||
public IntPtr Subwindow;
|
||||
public ulong Time;
|
||||
public int X;
|
||||
public int Y;
|
||||
public int XRoot;
|
||||
public int YRoot;
|
||||
public uint State;
|
||||
public uint Keycode;
|
||||
public int SameScreen;
|
||||
}
|
||||
23
Interop/XMotionEvent.cs
Normal file
23
Interop/XMotionEvent.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
public struct XMotionEvent
|
||||
{
|
||||
public int Type;
|
||||
public ulong Serial;
|
||||
public int SendEvent;
|
||||
public IntPtr Display;
|
||||
public IntPtr Window;
|
||||
public IntPtr Root;
|
||||
public IntPtr Subwindow;
|
||||
public ulong Time;
|
||||
public int X;
|
||||
public int Y;
|
||||
public int XRoot;
|
||||
public int YRoot;
|
||||
public uint State;
|
||||
public byte IsHint;
|
||||
public int SameScreen;
|
||||
}
|
||||
139
Interop/XRandR.cs
Normal file
139
Interop/XRandR.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
/// <summary>
|
||||
/// XRandR (X Resize and Rotate) extension interop for multi-monitor support.
|
||||
/// </summary>
|
||||
internal static partial class XRandR
|
||||
{
|
||||
private const string LibXrandr = "libXrandr.so.2";
|
||||
|
||||
// RROutput and RRCrtc are XIDs (unsigned long)
|
||||
// RRMode is also an XID
|
||||
|
||||
[LibraryImport(LibXrandr)]
|
||||
public static partial IntPtr XRRGetScreenResources(IntPtr display, IntPtr window);
|
||||
|
||||
[LibraryImport(LibXrandr)]
|
||||
public static partial IntPtr XRRGetScreenResourcesCurrent(IntPtr display, IntPtr window);
|
||||
|
||||
[LibraryImport(LibXrandr)]
|
||||
public static partial void XRRFreeScreenResources(IntPtr resources);
|
||||
|
||||
[LibraryImport(LibXrandr)]
|
||||
public static partial IntPtr XRRGetOutputInfo(IntPtr display, IntPtr resources, ulong output);
|
||||
|
||||
[LibraryImport(LibXrandr)]
|
||||
public static partial void XRRFreeOutputInfo(IntPtr outputInfo);
|
||||
|
||||
[LibraryImport(LibXrandr)]
|
||||
public static partial IntPtr XRRGetCrtcInfo(IntPtr display, IntPtr resources, ulong crtc);
|
||||
|
||||
[LibraryImport(LibXrandr)]
|
||||
public static partial void XRRFreeCrtcInfo(IntPtr crtcInfo);
|
||||
|
||||
[LibraryImport(LibXrandr)]
|
||||
public static partial int XRRQueryExtension(IntPtr display, out int eventBase, out int errorBase);
|
||||
|
||||
[LibraryImport(LibXrandr)]
|
||||
public static partial int XRRQueryVersion(IntPtr display, out int major, out int minor);
|
||||
|
||||
[LibraryImport(LibXrandr)]
|
||||
public static partial void XRRSelectInput(IntPtr display, IntPtr window, int mask);
|
||||
|
||||
// RRNotify mask values
|
||||
public const int RRScreenChangeNotifyMask = 1 << 0;
|
||||
public const int RRCrtcChangeNotifyMask = 1 << 1;
|
||||
public const int RROutputChangeNotifyMask = 1 << 2;
|
||||
public const int RROutputPropertyNotifyMask = 1 << 3;
|
||||
|
||||
// Connection status
|
||||
public const int RR_Connected = 0;
|
||||
public const int RR_Disconnected = 1;
|
||||
public const int RR_UnknownConnection = 2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// XRRScreenResources structure layout.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct XRRScreenResources
|
||||
{
|
||||
public ulong Timestamp;
|
||||
public ulong ConfigTimestamp;
|
||||
public int NCrtc;
|
||||
public IntPtr Crtcs; // RRCrtc* (array of ulongs)
|
||||
public int NOutput;
|
||||
public IntPtr Outputs; // RROutput* (array of ulongs)
|
||||
public int NMode;
|
||||
public IntPtr Modes; // XRRModeInfo*
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// XRROutputInfo structure layout.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct XRROutputInfo
|
||||
{
|
||||
public ulong Timestamp;
|
||||
public ulong Crtc; // RRCrtc - current CRTC (0 if not connected)
|
||||
public IntPtr Name; // char*
|
||||
public int NameLen;
|
||||
public ulong MmWidth; // Physical width in mm
|
||||
public ulong MmHeight; // Physical height in mm
|
||||
public ushort Connection; // RRConnection status
|
||||
public ushort SubpixelOrder;
|
||||
public int NCrtc;
|
||||
public IntPtr Crtcs; // RRCrtc* - possible CRTCs
|
||||
public int NClone;
|
||||
public IntPtr Clones; // RROutput*
|
||||
public int NMode;
|
||||
public int NPreferred;
|
||||
public IntPtr Modes; // RRMode*
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// XRRCrtcInfo structure layout.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct XRRCrtcInfo
|
||||
{
|
||||
public ulong Timestamp;
|
||||
public int X;
|
||||
public int Y;
|
||||
public uint Width;
|
||||
public uint Height;
|
||||
public ulong Mode; // RRMode - current mode
|
||||
public ushort Rotation;
|
||||
public int NOutput;
|
||||
public IntPtr Outputs; // RROutput*
|
||||
public ushort Rotations; // Possible rotations
|
||||
public int NPossible;
|
||||
public IntPtr Possible; // RROutput*
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// XRRModeInfo structure layout.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct XRRModeInfo
|
||||
{
|
||||
public ulong Id; // RRMode
|
||||
public uint Width;
|
||||
public uint Height;
|
||||
public ulong DotClock;
|
||||
public uint HSyncStart;
|
||||
public uint HSyncEnd;
|
||||
public uint HTotal;
|
||||
public uint HSkew;
|
||||
public uint VSyncStart;
|
||||
public uint VSyncEnd;
|
||||
public uint VTotal;
|
||||
public IntPtr Name; // char*
|
||||
public uint NameLength;
|
||||
public ulong ModeFlags;
|
||||
}
|
||||
23
Interop/XSetWindowAttributes.cs
Normal file
23
Interop/XSetWindowAttributes.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
public struct XSetWindowAttributes
|
||||
{
|
||||
public IntPtr BackgroundPixmap;
|
||||
public ulong BackgroundPixel;
|
||||
public IntPtr BorderPixmap;
|
||||
public ulong BorderPixel;
|
||||
public int BitGravity;
|
||||
public int WinGravity;
|
||||
public int BackingStore;
|
||||
public ulong BackingPlanes;
|
||||
public ulong BackingPixel;
|
||||
public int SaveUnder;
|
||||
public long EventMask;
|
||||
public long DoNotPropagateMask;
|
||||
public int OverrideRedirect;
|
||||
public IntPtr Colormap;
|
||||
public IntPtr Cursor;
|
||||
}
|
||||
10
Interop/XWindowClass.cs
Normal file
10
Interop/XWindowClass.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Interop;
|
||||
|
||||
public static class XWindowClass
|
||||
{
|
||||
public const uint InputOutput = 1;
|
||||
public const uint InputOnly = 2;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user