Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a4e35cd39 | |||
| 33914bf572 | |||
| 1f096c38dc |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -47,4 +47,3 @@ coverage*.xml
|
|||||||
|
|
||||||
# Publish output
|
# Publish output
|
||||||
publish/
|
publish/
|
||||||
mauiplan.md
|
|
||||||
|
|||||||
@@ -1,182 +0,0 @@
|
|||||||
// 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
297
CLAUDE.md
@@ -1,297 +0,0 @@
|
|||||||
# 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
|
|
||||||
@@ -1,39 +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.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +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.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,3 +235,25 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,75 +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.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,3 +137,192 @@ public class SKRectTypeConverter : TypeConverter
|
|||||||
return SKRect.Empty;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,82 +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.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +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.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
// 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
53
Easing.cs
@@ -1,53 +0,0 @@
|
|||||||
// 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);
|
|
||||||
}
|
|
||||||
63
Handlers/ActivityIndicatorHandler.Linux.cs
Normal file
63
Handlers/ActivityIndicatorHandler.Linux.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// 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.Handlers;
|
||||||
using Microsoft.Maui.Graphics;
|
using Microsoft.Maui.Graphics;
|
||||||
using Microsoft.Maui.Platform;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||||
|
|
||||||
@@ -18,7 +18,6 @@ public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator,
|
|||||||
[nameof(IActivityIndicator.IsRunning)] = MapIsRunning,
|
[nameof(IActivityIndicator.IsRunning)] = MapIsRunning,
|
||||||
[nameof(IActivityIndicator.Color)] = MapColor,
|
[nameof(IActivityIndicator.Color)] = MapColor,
|
||||||
[nameof(IView.Background)] = MapBackground,
|
[nameof(IView.Background)] = MapBackground,
|
||||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static CommandMapper<IActivityIndicator, ActivityIndicatorHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
public static CommandMapper<IActivityIndicator, ActivityIndicatorHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||||
@@ -39,19 +38,6 @@ public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator,
|
|||||||
return new SkiaActivityIndicator();
|
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)
|
public static void MapIsRunning(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
@@ -63,7 +49,7 @@ public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator,
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
if (activityIndicator.Color is not null)
|
if (activityIndicator.Color is not null)
|
||||||
handler.PlatformView.Color = activityIndicator.Color;
|
handler.PlatformView.Color = activityIndicator.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapBackground(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
|
public static void MapBackground(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
|
||||||
@@ -72,14 +58,7 @@ public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator,
|
|||||||
|
|
||||||
if (activityIndicator.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (activityIndicator.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapIsEnabled(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
|
|
||||||
{
|
|
||||||
if (handler.PlatformView is null) return;
|
|
||||||
handler.PlatformView.IsEnabled = activityIndicator.IsEnabled;
|
|
||||||
handler.PlatformView.Invalidate();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using Microsoft.Maui.Handlers;
|
|||||||
using Microsoft.Maui.Graphics;
|
using Microsoft.Maui.Graphics;
|
||||||
using Microsoft.Maui.Controls;
|
using Microsoft.Maui.Controls;
|
||||||
using Microsoft.Maui.Platform;
|
using Microsoft.Maui.Platform;
|
||||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||||
@@ -21,17 +20,10 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
|||||||
[nameof(IBorderView.Content)] = MapContent,
|
[nameof(IBorderView.Content)] = MapContent,
|
||||||
[nameof(IBorderStroke.Stroke)] = MapStroke,
|
[nameof(IBorderStroke.Stroke)] = MapStroke,
|
||||||
[nameof(IBorderStroke.StrokeThickness)] = MapStrokeThickness,
|
[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
|
["StrokeShape"] = MapStrokeShape, // StrokeShape is on Border, not IBorderStroke
|
||||||
[nameof(IView.Background)] = MapBackground,
|
[nameof(IView.Background)] = MapBackground,
|
||||||
["BackgroundColor"] = MapBackgroundColor,
|
["BackgroundColor"] = MapBackgroundColor,
|
||||||
[nameof(IPadding.Padding)] = MapPadding,
|
[nameof(IPadding.Padding)] = MapPadding,
|
||||||
["WidthRequest"] = MapWidthRequest,
|
|
||||||
["HeightRequest"] = MapHeightRequest,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static CommandMapper<IBorderView, BorderHandler> CommandMapper =
|
public static CommandMapper<IBorderView, BorderHandler> CommandMapper =
|
||||||
@@ -56,49 +48,13 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
|||||||
protected override void ConnectHandler(SkiaBorder platformView)
|
protected override void ConnectHandler(SkiaBorder platformView)
|
||||||
{
|
{
|
||||||
base.ConnectHandler(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)
|
protected override void DisconnectHandler(SkiaBorder platformView)
|
||||||
{
|
{
|
||||||
platformView.Tapped -= OnPlatformViewTapped;
|
|
||||||
platformView.MauiView = null;
|
|
||||||
base.DisconnectHandler(platformView);
|
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)
|
public static void MapContent(BorderHandler handler, IBorderView border)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null || handler.MauiContext is null) return;
|
if (handler.PlatformView is null || handler.MauiContext is null) return;
|
||||||
@@ -112,7 +68,7 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
|||||||
if (content.Handler == null)
|
if (content.Handler == null)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[BorderHandler] Creating handler for content: {content.GetType().Name}");
|
Console.WriteLine($"[BorderHandler] Creating handler for content: {content.GetType().Name}");
|
||||||
content.Handler = content.ToViewHandler(handler.MauiContext);
|
content.Handler = content.ToHandler(handler.MauiContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.Handler?.PlatformView is SkiaView skiaContent)
|
if (content.Handler?.PlatformView is SkiaView skiaContent)
|
||||||
@@ -129,14 +85,14 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
|||||||
|
|
||||||
if (border.Stroke is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (border.Stroke is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.Stroke = solidPaint.Color;
|
handler.PlatformView.Stroke = solidPaint.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapStrokeThickness(BorderHandler handler, IBorderView border)
|
public static void MapStrokeThickness(BorderHandler handler, IBorderView border)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
handler.PlatformView.StrokeThickness = border.StrokeThickness;
|
handler.PlatformView.StrokeThickness = (float)border.StrokeThickness;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapBackground(BorderHandler handler, IBorderView border)
|
public static void MapBackground(BorderHandler handler, IBorderView border)
|
||||||
@@ -145,7 +101,7 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
|||||||
|
|
||||||
if (border.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (border.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,27 +109,22 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
|||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
if (border is VisualElement ve)
|
if (border is VisualElement ve && ve.BackgroundColor != null)
|
||||||
{
|
{
|
||||||
var bgColor = ve.BackgroundColor;
|
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||||
Console.WriteLine($"[BorderHandler] MapBackgroundColor: {bgColor}");
|
|
||||||
if (bgColor != null)
|
|
||||||
{
|
|
||||||
handler.PlatformView.BackgroundColor = bgColor;
|
|
||||||
handler.PlatformView.Invalidate();
|
handler.PlatformView.Invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static void MapPadding(BorderHandler handler, IBorderView border)
|
public static void MapPadding(BorderHandler handler, IBorderView border)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
var padding = border.Padding;
|
var padding = border.Padding;
|
||||||
handler.PlatformView.PaddingLeft = padding.Left;
|
handler.PlatformView.PaddingLeft = (float)padding.Left;
|
||||||
handler.PlatformView.PaddingTop = padding.Top;
|
handler.PlatformView.PaddingTop = (float)padding.Top;
|
||||||
handler.PlatformView.PaddingRight = padding.Right;
|
handler.PlatformView.PaddingRight = (float)padding.Right;
|
||||||
handler.PlatformView.PaddingBottom = padding.Bottom;
|
handler.PlatformView.PaddingBottom = (float)padding.Bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapStrokeShape(BorderHandler handler, IBorderView border)
|
public static void MapStrokeShape(BorderHandler handler, IBorderView border)
|
||||||
@@ -184,109 +135,24 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
|||||||
if (border is not Border borderControl) return;
|
if (border is not Border borderControl) return;
|
||||||
|
|
||||||
var shape = borderControl.StrokeShape;
|
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)
|
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;
|
var cornerRadius = roundRect.CornerRadius;
|
||||||
handler.PlatformView.CornerRadius = cornerRadius.TopLeft;
|
handler.PlatformView.CornerRadius = (float)cornerRadius.TopLeft;
|
||||||
}
|
}
|
||||||
else if (shape is Microsoft.Maui.Controls.Shapes.Rectangle)
|
else if (shape is Microsoft.Maui.Controls.Shapes.Rectangle)
|
||||||
{
|
{
|
||||||
handler.PlatformView.CornerRadius = 0.0;
|
handler.PlatformView.CornerRadius = 0;
|
||||||
}
|
}
|
||||||
else if (shape is Microsoft.Maui.Controls.Shapes.Ellipse)
|
else if (shape is Microsoft.Maui.Controls.Shapes.Ellipse)
|
||||||
{
|
{
|
||||||
handler.PlatformView.CornerRadius = double.MaxValue;
|
// 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.Invalidate();
|
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,38 +30,28 @@ public partial class BoxViewHandler : ViewHandler<BoxView, SkiaBoxView>
|
|||||||
return new 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)
|
public static void MapColor(BoxViewHandler handler, BoxView boxView)
|
||||||
{
|
{
|
||||||
if (boxView.Color != null)
|
if (boxView.Color != null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.Color = boxView.Color;
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapCornerRadius(BoxViewHandler handler, BoxView boxView)
|
public static void MapCornerRadius(BoxViewHandler handler, BoxView boxView)
|
||||||
{
|
{
|
||||||
handler.PlatformView.CornerRadius = boxView.CornerRadius;
|
handler.PlatformView.CornerRadius = (float)boxView.CornerRadius.TopLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapBackground(BoxViewHandler handler, BoxView boxView)
|
public static void MapBackground(BoxViewHandler handler, BoxView boxView)
|
||||||
{
|
{
|
||||||
if (boxView.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
if (boxView.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidBrush.Color;
|
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||||
handler.PlatformView.Invalidate();
|
handler.PlatformView.Invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,7 +60,7 @@ public partial class BoxViewHandler : ViewHandler<BoxView, SkiaBoxView>
|
|||||||
{
|
{
|
||||||
if (boxView.BackgroundColor != null)
|
if (boxView.BackgroundColor != null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = boxView.BackgroundColor;
|
handler.PlatformView.BackgroundColor = boxView.BackgroundColor.ToSKColor();
|
||||||
handler.PlatformView.Invalidate();
|
handler.PlatformView.Invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
179
Handlers/ButtonHandler.Linux.cs
Normal file
179
Handlers/ButtonHandler.Linux.cs
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
// 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,7 +1,6 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
using Microsoft.Maui.Controls;
|
|
||||||
using Microsoft.Maui.Handlers;
|
using Microsoft.Maui.Handlers;
|
||||||
using Microsoft.Maui.Graphics;
|
using Microsoft.Maui.Graphics;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
@@ -60,20 +59,6 @@ public partial class ButtonHandler : ViewHandler<IButton, SkiaButton>
|
|||||||
MapBackground(this, VirtualView);
|
MapBackground(this, VirtualView);
|
||||||
MapPadding(this, VirtualView);
|
MapPadding(this, VirtualView);
|
||||||
MapIsEnabled(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}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,13 +80,13 @@ public partial class ButtonHandler : ViewHandler<IButton, SkiaButton>
|
|||||||
|
|
||||||
var strokeColor = button.StrokeColor;
|
var strokeColor = button.StrokeColor;
|
||||||
if (strokeColor is not null)
|
if (strokeColor is not null)
|
||||||
handler.PlatformView.BorderColor = strokeColor;
|
handler.PlatformView.BorderColor = strokeColor.ToSKColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapStrokeThickness(ButtonHandler handler, IButton button)
|
public static void MapStrokeThickness(ButtonHandler handler, IButton button)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
handler.PlatformView.BorderWidth = button.StrokeThickness;
|
handler.PlatformView.BorderWidth = (float)button.StrokeThickness;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapCornerRadius(ButtonHandler handler, IButton button)
|
public static void MapCornerRadius(ButtonHandler handler, IButton button)
|
||||||
@@ -116,8 +101,8 @@ public partial class ButtonHandler : ViewHandler<IButton, SkiaButton>
|
|||||||
|
|
||||||
if (button.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (button.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
// Set BackgroundColor (MAUI Color type)
|
// Set ButtonBackgroundColor (used for rendering) not base BackgroundColor
|
||||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
handler.PlatformView.ButtonBackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,16 +111,17 @@ public partial class ButtonHandler : ViewHandler<IButton, SkiaButton>
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
var padding = button.Padding;
|
var padding = button.Padding;
|
||||||
handler.PlatformView.Padding = new Thickness(
|
handler.PlatformView.Padding = new SKRect(
|
||||||
padding.Left,
|
(float)padding.Left,
|
||||||
padding.Top,
|
(float)padding.Top,
|
||||||
padding.Right,
|
(float)padding.Right,
|
||||||
padding.Bottom);
|
(float)padding.Bottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapIsEnabled(ButtonHandler handler, IButton button)
|
public static void MapIsEnabled(ButtonHandler handler, IButton button)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
Console.WriteLine($"[ButtonHandler] MapIsEnabled - Text='{handler.PlatformView.Text}', IsEnabled={button.IsEnabled}");
|
||||||
handler.PlatformView.IsEnabled = button.IsEnabled;
|
handler.PlatformView.IsEnabled = button.IsEnabled;
|
||||||
handler.PlatformView.Invalidate();
|
handler.PlatformView.Invalidate();
|
||||||
}
|
}
|
||||||
@@ -162,7 +148,6 @@ public partial class TextButtonHandler : ButtonHandler
|
|||||||
|
|
||||||
protected override void ConnectHandler(SkiaButton platformView)
|
protected override void ConnectHandler(SkiaButton platformView)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[TextButtonHandler] ConnectHandler START");
|
|
||||||
base.ConnectHandler(platformView);
|
base.ConnectHandler(platformView);
|
||||||
|
|
||||||
// Manually map text properties on connect since MAUI may not trigger updates
|
// Manually map text properties on connect since MAUI may not trigger updates
|
||||||
@@ -174,17 +159,6 @@ public partial class TextButtonHandler : ButtonHandler
|
|||||||
MapFont(this, textButton);
|
MapFont(this, textButton);
|
||||||
MapCharacterSpacing(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)
|
public static void MapText(TextButtonHandler handler, ITextButton button)
|
||||||
@@ -198,7 +172,7 @@ public partial class TextButtonHandler : ButtonHandler
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
if (button.TextColor is not null)
|
if (button.TextColor is not null)
|
||||||
handler.PlatformView.TextColor = button.TextColor;
|
handler.PlatformView.TextColor = button.TextColor.ToSKColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapFont(TextButtonHandler handler, ITextButton button)
|
public static void MapFont(TextButtonHandler handler, ITextButton button)
|
||||||
@@ -207,23 +181,18 @@ public partial class TextButtonHandler : ButtonHandler
|
|||||||
|
|
||||||
var font = button.Font;
|
var font = button.Font;
|
||||||
if (font.Size > 0)
|
if (font.Size > 0)
|
||||||
handler.PlatformView.FontSize = font.Size;
|
handler.PlatformView.FontSize = (float)font.Size;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(font.Family))
|
if (!string.IsNullOrEmpty(font.Family))
|
||||||
handler.PlatformView.FontFamily = font.Family;
|
handler.PlatformView.FontFamily = font.Family;
|
||||||
|
|
||||||
// Convert Font weight/slant to FontAttributes
|
handler.PlatformView.IsBold = font.Weight >= FontWeight.Bold;
|
||||||
FontAttributes attrs = FontAttributes.None;
|
handler.PlatformView.IsItalic = font.Slant == FontSlant.Italic || font.Slant == FontSlant.Oblique;
|
||||||
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)
|
public static void MapCharacterSpacing(TextButtonHandler handler, ITextButton button)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
handler.PlatformView.CharacterSpacing = button.CharacterSpacing;
|
handler.PlatformView.CharacterSpacing = (float)button.CharacterSpacing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,255 +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 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
113
Handlers/CheckBoxHandler.Linux.cs
Normal file
113
Handlers/CheckBoxHandler.Linux.cs
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
// 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,7 +18,6 @@ public partial class CheckBoxHandler : ViewHandler<ICheckBox, SkiaCheckBox>
|
|||||||
[nameof(ICheckBox.IsChecked)] = MapIsChecked,
|
[nameof(ICheckBox.IsChecked)] = MapIsChecked,
|
||||||
[nameof(ICheckBox.Foreground)] = MapForeground,
|
[nameof(ICheckBox.Foreground)] = MapForeground,
|
||||||
[nameof(IView.Background)] = MapBackground,
|
[nameof(IView.Background)] = MapBackground,
|
||||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
|
||||||
[nameof(IView.VerticalLayoutAlignment)] = MapVerticalLayoutAlignment,
|
[nameof(IView.VerticalLayoutAlignment)] = MapVerticalLayoutAlignment,
|
||||||
[nameof(IView.HorizontalLayoutAlignment)] = MapHorizontalLayoutAlignment,
|
[nameof(IView.HorizontalLayoutAlignment)] = MapHorizontalLayoutAlignment,
|
||||||
};
|
};
|
||||||
@@ -73,7 +72,7 @@ public partial class CheckBoxHandler : ViewHandler<ICheckBox, SkiaCheckBox>
|
|||||||
|
|
||||||
if (checkBox.Foreground is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (checkBox.Foreground is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.CheckColor = solidPaint.Color;
|
handler.PlatformView.CheckColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,16 +82,10 @@ public partial class CheckBoxHandler : ViewHandler<ICheckBox, SkiaCheckBox>
|
|||||||
|
|
||||||
if (checkBox.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (checkBox.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.Color = solidPaint.Color;
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
public static void MapVerticalLayoutAlignment(CheckBoxHandler handler, ICheckBox checkBox)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using Microsoft.Maui.Handlers;
|
|||||||
using Microsoft.Maui.Graphics;
|
using Microsoft.Maui.Graphics;
|
||||||
using Microsoft.Maui.Controls;
|
using Microsoft.Maui.Controls;
|
||||||
using Microsoft.Maui.Platform;
|
using Microsoft.Maui.Platform;
|
||||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||||
@@ -124,49 +123,7 @@ public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCol
|
|||||||
|
|
||||||
private void OnItemTapped(object? sender, ItemsViewItemTappedEventArgs e)
|
private void OnItemTapped(object? sender, ItemsViewItemTappedEventArgs e)
|
||||||
{
|
{
|
||||||
if (VirtualView is null || _isUpdatingSelection) return;
|
// Item tap is handled through selection
|
||||||
|
|
||||||
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)
|
public static void MapItemsSource(CollectionViewHandler handler, CollectionView collectionView)
|
||||||
@@ -201,14 +158,11 @@ public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCol
|
|||||||
// Create handler for the view
|
// Create handler for the view
|
||||||
if (view.Handler == null && handler.MauiContext != null)
|
if (view.Handler == null && handler.MauiContext != null)
|
||||||
{
|
{
|
||||||
view.Handler = view.ToViewHandler(handler.MauiContext);
|
view.Handler = view.ToHandler(handler.MauiContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (view.Handler?.PlatformView is SkiaView skiaView)
|
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;
|
return skiaView;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,7 +174,7 @@ public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCol
|
|||||||
{
|
{
|
||||||
if (cellView.Handler == null && handler.MauiContext != null)
|
if (cellView.Handler == null && handler.MauiContext != null)
|
||||||
{
|
{
|
||||||
cellView.Handler = cellView.ToViewHandler(handler.MauiContext);
|
cellView.Handler = cellView.ToHandler(handler.MauiContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cellView.Handler?.PlatformView is SkiaView skiaView)
|
if (cellView.Handler?.PlatformView is SkiaView skiaView)
|
||||||
@@ -361,7 +315,7 @@ public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCol
|
|||||||
|
|
||||||
if (collectionView.Background is SolidColorBrush solidBrush)
|
if (collectionView.Background is SolidColorBrush solidBrush)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidBrush.Color;
|
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,7 +325,7 @@ public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCol
|
|||||||
|
|
||||||
if (collectionView.BackgroundColor is not null)
|
if (collectionView.BackgroundColor is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = collectionView.BackgroundColor;
|
handler.PlatformView.BackgroundColor = collectionView.BackgroundColor.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ public partial class DatePickerHandler : ViewHandler<IDatePicker, SkiaDatePicker
|
|||||||
[nameof(IDatePicker.Format)] = MapFormat,
|
[nameof(IDatePicker.Format)] = MapFormat,
|
||||||
[nameof(IDatePicker.TextColor)] = MapTextColor,
|
[nameof(IDatePicker.TextColor)] = MapTextColor,
|
||||||
[nameof(IDatePicker.CharacterSpacing)] = MapCharacterSpacing,
|
[nameof(IDatePicker.CharacterSpacing)] = MapCharacterSpacing,
|
||||||
[nameof(ITextStyle.Font)] = MapFont,
|
|
||||||
[nameof(IView.Background)] = MapBackground,
|
[nameof(IView.Background)] = MapBackground,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -50,17 +49,6 @@ public partial class DatePickerHandler : ViewHandler<IDatePicker, SkiaDatePicker
|
|||||||
{
|
{
|
||||||
base.ConnectHandler(platformView);
|
base.ConnectHandler(platformView);
|
||||||
platformView.DateSelected += OnDateSelected;
|
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)
|
protected override void DisconnectHandler(SkiaDatePicker platformView)
|
||||||
@@ -69,11 +57,11 @@ public partial class DatePickerHandler : ViewHandler<IDatePicker, SkiaDatePicker
|
|||||||
base.DisconnectHandler(platformView);
|
base.DisconnectHandler(platformView);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDateSelected(object? sender, DateChangedEventArgs e)
|
private void OnDateSelected(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (VirtualView is null || PlatformView is null) return;
|
if (VirtualView is null || PlatformView is null) return;
|
||||||
|
|
||||||
VirtualView.Date = e.NewDate;
|
VirtualView.Date = PlatformView.Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapDate(DatePickerHandler handler, IDatePicker datePicker)
|
public static void MapDate(DatePickerHandler handler, IDatePicker datePicker)
|
||||||
@@ -105,33 +93,13 @@ public partial class DatePickerHandler : ViewHandler<IDatePicker, SkiaDatePicker
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
if (datePicker.TextColor is not null)
|
if (datePicker.TextColor is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.TextColor = datePicker.TextColor;
|
handler.PlatformView.TextColor = datePicker.TextColor.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapCharacterSpacing(DatePickerHandler handler, IDatePicker datePicker)
|
public static void MapCharacterSpacing(DatePickerHandler handler, IDatePicker datePicker)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
// Character spacing would require custom text rendering
|
||||||
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)
|
public static void MapBackground(DatePickerHandler handler, IDatePicker datePicker)
|
||||||
@@ -140,7 +108,7 @@ public partial class DatePickerHandler : ViewHandler<IDatePicker, SkiaDatePicker
|
|||||||
|
|
||||||
if (datePicker.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (datePicker.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,9 @@ public partial class EditorHandler : ViewHandler<IEditor, SkiaEditor>
|
|||||||
[nameof(IEditor.Placeholder)] = MapPlaceholder,
|
[nameof(IEditor.Placeholder)] = MapPlaceholder,
|
||||||
[nameof(IEditor.PlaceholderColor)] = MapPlaceholderColor,
|
[nameof(IEditor.PlaceholderColor)] = MapPlaceholderColor,
|
||||||
[nameof(IEditor.TextColor)] = MapTextColor,
|
[nameof(IEditor.TextColor)] = MapTextColor,
|
||||||
[nameof(ITextStyle.Font)] = MapFont,
|
|
||||||
[nameof(IEditor.CharacterSpacing)] = MapCharacterSpacing,
|
[nameof(IEditor.CharacterSpacing)] = MapCharacterSpacing,
|
||||||
[nameof(IEditor.IsReadOnly)] = MapIsReadOnly,
|
[nameof(IEditor.IsReadOnly)] = MapIsReadOnly,
|
||||||
[nameof(IEditor.IsTextPredictionEnabled)] = MapIsTextPredictionEnabled,
|
[nameof(IEditor.IsTextPredictionEnabled)] = MapIsTextPredictionEnabled,
|
||||||
[nameof(IEditor.IsSpellCheckEnabled)] = MapIsSpellCheckEnabled,
|
|
||||||
[nameof(IEditor.MaxLength)] = MapMaxLength,
|
[nameof(IEditor.MaxLength)] = MapMaxLength,
|
||||||
[nameof(IEditor.CursorPosition)] = MapCursorPosition,
|
[nameof(IEditor.CursorPosition)] = MapCursorPosition,
|
||||||
[nameof(IEditor.SelectionLength)] = MapSelectionLength,
|
[nameof(IEditor.SelectionLength)] = MapSelectionLength,
|
||||||
@@ -99,7 +97,7 @@ public partial class EditorHandler : ViewHandler<IEditor, SkiaEditor>
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
if (editor.PlaceholderColor is not null)
|
if (editor.PlaceholderColor is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.PlaceholderColor = editor.PlaceholderColor;
|
handler.PlatformView.PlaceholderColor = editor.PlaceholderColor.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,34 +106,13 @@ public partial class EditorHandler : ViewHandler<IEditor, SkiaEditor>
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
if (editor.TextColor is not null)
|
if (editor.TextColor is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.TextColor = editor.TextColor;
|
handler.PlatformView.TextColor = editor.TextColor.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
public static void MapCharacterSpacing(EditorHandler handler, IEditor editor)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
// Character spacing would require custom text rendering
|
||||||
handler.PlatformView.CharacterSpacing = editor.CharacterSpacing;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapIsReadOnly(EditorHandler handler, IEditor editor)
|
public static void MapIsReadOnly(EditorHandler handler, IEditor editor)
|
||||||
@@ -146,14 +123,7 @@ public partial class EditorHandler : ViewHandler<IEditor, SkiaEditor>
|
|||||||
|
|
||||||
public static void MapIsTextPredictionEnabled(EditorHandler handler, IEditor editor)
|
public static void MapIsTextPredictionEnabled(EditorHandler handler, IEditor editor)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
// Text prediction not applicable to desktop
|
||||||
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)
|
public static void MapMaxLength(EditorHandler handler, IEditor editor)
|
||||||
@@ -170,39 +140,22 @@ public partial class EditorHandler : ViewHandler<IEditor, SkiaEditor>
|
|||||||
|
|
||||||
public static void MapSelectionLength(EditorHandler handler, IEditor editor)
|
public static void MapSelectionLength(EditorHandler handler, IEditor editor)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
// Selection would need to be added to SkiaEditor
|
||||||
handler.PlatformView.SelectionLength = editor.SelectionLength;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapKeyboard(EditorHandler handler, IEditor editor)
|
public static void MapKeyboard(EditorHandler handler, IEditor editor)
|
||||||
{
|
{
|
||||||
// Virtual keyboard type not applicable to desktop - stored for future use
|
// Virtual keyboard type not applicable to desktop
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapHorizontalTextAlignment(EditorHandler handler, IEditor editor)
|
public static void MapHorizontalTextAlignment(EditorHandler handler, IEditor editor)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
// Text alignment would require changes to SkiaEditor drawing
|
||||||
|
|
||||||
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)
|
public static void MapVerticalTextAlignment(EditorHandler handler, IEditor editor)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
// Text alignment would require changes to SkiaEditor drawing
|
||||||
|
|
||||||
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)
|
public static void MapBackground(EditorHandler handler, IEditor editor)
|
||||||
@@ -211,7 +164,7 @@ public partial class EditorHandler : ViewHandler<IEditor, SkiaEditor>
|
|||||||
|
|
||||||
if (editor.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (editor.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.EditorBackgroundColor = solidPaint.Color;
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,9 +172,9 @@ public partial class EditorHandler : ViewHandler<IEditor, SkiaEditor>
|
|||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
if (editor is Editor ve && ve.BackgroundColor != null)
|
if (editor is VisualElement ve && ve.BackgroundColor != null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.EditorBackgroundColor = ve.BackgroundColor;
|
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||||
handler.PlatformView.Invalidate();
|
handler.PlatformView.Invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
199
Handlers/EntryHandler.Linux.cs
Normal file
199
Handlers/EntryHandler.Linux.cs
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
// 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,7 +3,6 @@
|
|||||||
|
|
||||||
using Microsoft.Maui.Handlers;
|
using Microsoft.Maui.Handlers;
|
||||||
using Microsoft.Maui.Graphics;
|
using Microsoft.Maui.Graphics;
|
||||||
using Microsoft.Maui.Controls;
|
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||||
@@ -29,13 +28,9 @@ public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
|||||||
[nameof(IEntry.IsPassword)] = MapIsPassword,
|
[nameof(IEntry.IsPassword)] = MapIsPassword,
|
||||||
[nameof(IEntry.ReturnType)] = MapReturnType,
|
[nameof(IEntry.ReturnType)] = MapReturnType,
|
||||||
[nameof(IEntry.ClearButtonVisibility)] = MapClearButtonVisibility,
|
[nameof(IEntry.ClearButtonVisibility)] = MapClearButtonVisibility,
|
||||||
[nameof(IEntry.IsTextPredictionEnabled)] = MapIsTextPredictionEnabled,
|
|
||||||
[nameof(IEntry.IsSpellCheckEnabled)] = MapIsSpellCheckEnabled,
|
|
||||||
[nameof(ITextAlignment.HorizontalTextAlignment)] = MapHorizontalTextAlignment,
|
[nameof(ITextAlignment.HorizontalTextAlignment)] = MapHorizontalTextAlignment,
|
||||||
[nameof(ITextAlignment.VerticalTextAlignment)] = MapVerticalTextAlignment,
|
[nameof(ITextAlignment.VerticalTextAlignment)] = MapVerticalTextAlignment,
|
||||||
[nameof(IView.Background)] = MapBackground,
|
[nameof(IView.Background)] = MapBackground,
|
||||||
["BackgroundColor"] = MapBackgroundColor,
|
|
||||||
["SelectAllOnDoubleClick"] = MapSelectAllOnDoubleClick,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static CommandMapper<IEntry, EntryHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
public static CommandMapper<IEntry, EntryHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||||
@@ -101,7 +96,7 @@ public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
if (entry.TextColor is not null)
|
if (entry.TextColor is not null)
|
||||||
handler.PlatformView.TextColor = entry.TextColor;
|
handler.PlatformView.TextColor = entry.TextColor.ToSKColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapFont(EntryHandler handler, IEntry entry)
|
public static void MapFont(EntryHandler handler, IEntry entry)
|
||||||
@@ -110,24 +105,19 @@ public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
|||||||
|
|
||||||
var font = entry.Font;
|
var font = entry.Font;
|
||||||
if (font.Size > 0)
|
if (font.Size > 0)
|
||||||
handler.PlatformView.FontSize = font.Size;
|
handler.PlatformView.FontSize = (float)font.Size;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(font.Family))
|
if (!string.IsNullOrEmpty(font.Family))
|
||||||
handler.PlatformView.FontFamily = font.Family;
|
handler.PlatformView.FontFamily = font.Family;
|
||||||
|
|
||||||
// Convert Font weight/slant to FontAttributes
|
handler.PlatformView.IsBold = font.Weight >= FontWeight.Bold;
|
||||||
FontAttributes attrs = FontAttributes.None;
|
handler.PlatformView.IsItalic = font.Slant == FontSlant.Italic || font.Slant == FontSlant.Oblique;
|
||||||
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)
|
public static void MapCharacterSpacing(EntryHandler handler, IEntry entry)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
handler.PlatformView.CharacterSpacing = entry.CharacterSpacing;
|
handler.PlatformView.CharacterSpacing = (float)entry.CharacterSpacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapPlaceholder(EntryHandler handler, IEntry entry)
|
public static void MapPlaceholder(EntryHandler handler, IEntry entry)
|
||||||
@@ -141,7 +131,7 @@ public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
if (entry.PlaceholderColor is not null)
|
if (entry.PlaceholderColor is not null)
|
||||||
handler.PlatformView.PlaceholderColor = entry.PlaceholderColor;
|
handler.PlatformView.PlaceholderColor = entry.PlaceholderColor.ToSKColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapIsReadOnly(EntryHandler handler, IEntry entry)
|
public static void MapIsReadOnly(EntryHandler handler, IEntry entry)
|
||||||
@@ -187,28 +177,16 @@ public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
|||||||
handler.PlatformView.ShowClearButton = entry.ClearButtonVisibility == ClearButtonVisibility.WhileEditing;
|
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)
|
public static void MapHorizontalTextAlignment(EntryHandler handler, IEntry entry)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
handler.PlatformView.HorizontalTextAlignment = entry.HorizontalTextAlignment switch
|
handler.PlatformView.HorizontalTextAlignment = entry.HorizontalTextAlignment switch
|
||||||
{
|
{
|
||||||
Microsoft.Maui.TextAlignment.Start => TextAlignment.Start,
|
Microsoft.Maui.TextAlignment.Start => Platform.TextAlignment.Start,
|
||||||
Microsoft.Maui.TextAlignment.Center => TextAlignment.Center,
|
Microsoft.Maui.TextAlignment.Center => Platform.TextAlignment.Center,
|
||||||
Microsoft.Maui.TextAlignment.End => TextAlignment.End,
|
Microsoft.Maui.TextAlignment.End => Platform.TextAlignment.End,
|
||||||
_ => TextAlignment.Start
|
_ => Platform.TextAlignment.Start
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,10 +196,10 @@ public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
|||||||
|
|
||||||
handler.PlatformView.VerticalTextAlignment = entry.VerticalTextAlignment switch
|
handler.PlatformView.VerticalTextAlignment = entry.VerticalTextAlignment switch
|
||||||
{
|
{
|
||||||
Microsoft.Maui.TextAlignment.Start => TextAlignment.Start,
|
Microsoft.Maui.TextAlignment.Start => Platform.TextAlignment.Start,
|
||||||
Microsoft.Maui.TextAlignment.Center => TextAlignment.Center,
|
Microsoft.Maui.TextAlignment.Center => Platform.TextAlignment.Center,
|
||||||
Microsoft.Maui.TextAlignment.End => TextAlignment.End,
|
Microsoft.Maui.TextAlignment.End => Platform.TextAlignment.End,
|
||||||
_ => TextAlignment.Center
|
_ => Platform.TextAlignment.Center
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,29 +209,7 @@ public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
|||||||
|
|
||||||
if (entry.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (entry.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
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,10 +1,8 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
using Microsoft.Maui.Controls;
|
|
||||||
using Microsoft.Maui.Handlers;
|
using Microsoft.Maui.Handlers;
|
||||||
using Microsoft.Maui.Graphics;
|
using Microsoft.Maui.Graphics;
|
||||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||||
@@ -15,17 +13,12 @@ namespace Microsoft.Maui.Platform.Linux.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class FlyoutPageHandler : ViewHandler<IFlyoutView, SkiaFlyoutPage>
|
public partial class FlyoutPageHandler : ViewHandler<IFlyoutView, SkiaFlyoutPage>
|
||||||
{
|
{
|
||||||
private bool _isUpdatingPresented;
|
|
||||||
|
|
||||||
public static IPropertyMapper<IFlyoutView, FlyoutPageHandler> Mapper = new PropertyMapper<IFlyoutView, FlyoutPageHandler>(ViewHandler.ViewMapper)
|
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.IsPresented)] = MapIsPresented,
|
||||||
[nameof(IFlyoutView.FlyoutWidth)] = MapFlyoutWidth,
|
[nameof(IFlyoutView.FlyoutWidth)] = MapFlyoutWidth,
|
||||||
[nameof(IFlyoutView.IsGestureEnabled)] = MapIsGestureEnabled,
|
[nameof(IFlyoutView.IsGestureEnabled)] = MapIsGestureEnabled,
|
||||||
[nameof(IFlyoutView.FlyoutBehavior)] = MapFlyoutBehavior,
|
[nameof(IFlyoutView.FlyoutBehavior)] = MapFlyoutBehavior,
|
||||||
[nameof(IView.Background)] = MapBackground,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static CommandMapper<IFlyoutView, FlyoutPageHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
public static CommandMapper<IFlyoutView, FlyoutPageHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||||
@@ -62,83 +55,14 @@ public partial class FlyoutPageHandler : ViewHandler<IFlyoutView, SkiaFlyoutPage
|
|||||||
|
|
||||||
private void OnIsPresentedChanged(object? sender, EventArgs e)
|
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
|
// 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)
|
public static void MapIsPresented(FlyoutPageHandler handler, IFlyoutView flyoutView)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null || handler._isUpdatingPresented) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
handler._isUpdatingPresented = true;
|
|
||||||
handler.PlatformView.IsPresented = flyoutView.IsPresented;
|
handler.PlatformView.IsPresented = flyoutView.IsPresented;
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
handler._isUpdatingPresented = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void MapFlyoutWidth(FlyoutPageHandler handler, IFlyoutView flyoutView)
|
public static void MapFlyoutWidth(FlyoutPageHandler handler, IFlyoutView flyoutView)
|
||||||
{
|
{
|
||||||
@@ -164,14 +88,4 @@ public partial class FlyoutPageHandler : ViewHandler<IFlyoutView, SkiaFlyoutPage
|
|||||||
_ => FlyoutLayoutBehavior.Default
|
_ => 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,7 +3,6 @@
|
|||||||
|
|
||||||
using Microsoft.Maui.Controls;
|
using Microsoft.Maui.Controls;
|
||||||
using Microsoft.Maui.Handlers;
|
using Microsoft.Maui.Handlers;
|
||||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||||
@@ -38,36 +37,15 @@ public partial class FrameHandler : ViewHandler<Frame, SkiaFrame>
|
|||||||
return new 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)
|
public static void MapBorderColor(FrameHandler handler, Frame frame)
|
||||||
{
|
{
|
||||||
if (frame.BorderColor != null)
|
if (frame.BorderColor != null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.Stroke = frame.BorderColor;
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +63,11 @@ public partial class FrameHandler : ViewHandler<Frame, SkiaFrame>
|
|||||||
{
|
{
|
||||||
if (frame.BackgroundColor != null)
|
if (frame.BackgroundColor != null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = frame.BackgroundColor;
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +92,7 @@ public partial class FrameHandler : ViewHandler<Frame, SkiaFrame>
|
|||||||
// Create handler for content if it doesn't exist
|
// Create handler for content if it doesn't exist
|
||||||
if (content.Handler == null)
|
if (content.Handler == null)
|
||||||
{
|
{
|
||||||
content.Handler = content.ToViewHandler(handler.MauiContext);
|
content.Handler = content.ToHandler(handler.MauiContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.Handler?.PlatformView is SkiaView skiaContent)
|
if (content.Handler?.PlatformView is SkiaView skiaContent)
|
||||||
|
|||||||
@@ -1,845 +0,0 @@
|
|||||||
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)
|
if (graphicsView.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,265 +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.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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,546 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,71 +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.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,11 +24,6 @@ public partial class ImageButtonHandler : ViewHandler<IImageButton, SkiaImageBut
|
|||||||
[nameof(IButtonStroke.CornerRadius)] = MapCornerRadius,
|
[nameof(IButtonStroke.CornerRadius)] = MapCornerRadius,
|
||||||
[nameof(IPadding.Padding)] = MapPadding,
|
[nameof(IPadding.Padding)] = MapPadding,
|
||||||
[nameof(IView.Background)] = MapBackground,
|
[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)
|
public static CommandMapper<IImageButton, ImageButtonHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||||
@@ -123,13 +118,13 @@ public partial class ImageButtonHandler : ViewHandler<IImageButton, SkiaImageBut
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
if (imageButton.StrokeColor is not null)
|
if (imageButton.StrokeColor is not null)
|
||||||
handler.PlatformView.StrokeColor = imageButton.StrokeColor;
|
handler.PlatformView.StrokeColor = imageButton.StrokeColor.ToSKColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapStrokeThickness(ImageButtonHandler handler, IImageButton imageButton)
|
public static void MapStrokeThickness(ImageButtonHandler handler, IImageButton imageButton)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
handler.PlatformView.StrokeThickness = imageButton.StrokeThickness;
|
handler.PlatformView.StrokeThickness = (float)imageButton.StrokeThickness;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapCornerRadius(ImageButtonHandler handler, IImageButton imageButton)
|
public static void MapCornerRadius(ImageButtonHandler handler, IImageButton imageButton)
|
||||||
@@ -141,7 +136,12 @@ public partial class ImageButtonHandler : ViewHandler<IImageButton, SkiaImageBut
|
|||||||
public static void MapPadding(ImageButtonHandler handler, IImageButton imageButton)
|
public static void MapPadding(ImageButtonHandler handler, IImageButton imageButton)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
handler.PlatformView.Padding = imageButton.Padding;
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapBackground(ImageButtonHandler handler, IImageButton imageButton)
|
public static void MapBackground(ImageButtonHandler handler, IImageButton imageButton)
|
||||||
@@ -150,59 +150,7 @@ public partial class ImageButtonHandler : ViewHandler<IImageButton, SkiaImageBut
|
|||||||
|
|
||||||
if (imageButton.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (imageButton.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.ImageBackgroundColor = solidPaint.Color;
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,8 +1,6 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// 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.Handlers;
|
||||||
using Microsoft.Maui.Graphics;
|
using Microsoft.Maui.Graphics;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
@@ -22,10 +20,6 @@ public partial class ImageHandler : ViewHandler<IImage, SkiaImage>
|
|||||||
[nameof(IImage.IsOpaque)] = MapIsOpaque,
|
[nameof(IImage.IsOpaque)] = MapIsOpaque,
|
||||||
[nameof(IImageSourcePart.Source)] = MapSource,
|
[nameof(IImageSourcePart.Source)] = MapSource,
|
||||||
[nameof(IView.Background)] = MapBackground,
|
[nameof(IView.Background)] = MapBackground,
|
||||||
["Width"] = MapWidth,
|
|
||||||
["Height"] = MapHeight,
|
|
||||||
["HorizontalOptions"] = MapHorizontalOptions,
|
|
||||||
["VerticalOptions"] = MapVerticalOptions,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static CommandMapper<IImage, ImageHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
public static CommandMapper<IImage, ImageHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||||
@@ -94,19 +88,6 @@ public partial class ImageHandler : ViewHandler<IImage, SkiaImage>
|
|||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
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();
|
handler.SourceLoader.UpdateImageSourceAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,57 +97,7 @@ public partial class ImageHandler : ViewHandler<IImage, SkiaImage>
|
|||||||
|
|
||||||
if (image.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (image.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.ImageBackgroundColor = solidPaint.Color;
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,14 +162,6 @@ public partial class ImageHandler : ViewHandler<IImage, SkiaImage>
|
|||||||
await _handler.PlatformView!.LoadFromStreamAsync(stream);
|
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)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@@ -253,73 +176,5 @@ 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,157 +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 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)
|
if (itemsView.Background is SolidColorBrush solidBrush)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidBrush.Color;
|
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
174
Handlers/LabelHandler.Linux.cs
Normal file
174
Handlers/LabelHandler.Linux.cs
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
// 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,11 +1,8 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// 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.Handlers;
|
||||||
using Microsoft.Maui.Graphics;
|
using Microsoft.Maui.Graphics;
|
||||||
using Microsoft.Maui.Platform.Linux.Window;
|
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||||
@@ -32,7 +29,6 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
|||||||
[nameof(IView.Background)] = MapBackground,
|
[nameof(IView.Background)] = MapBackground,
|
||||||
[nameof(IView.VerticalLayoutAlignment)] = MapVerticalLayoutAlignment,
|
[nameof(IView.VerticalLayoutAlignment)] = MapVerticalLayoutAlignment,
|
||||||
[nameof(IView.HorizontalLayoutAlignment)] = MapHorizontalLayoutAlignment,
|
[nameof(IView.HorizontalLayoutAlignment)] = MapHorizontalLayoutAlignment,
|
||||||
["FormattedText"] = MapFormattedText,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static CommandMapper<ILabel, LabelHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
public static CommandMapper<ILabel, LabelHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||||
@@ -53,45 +49,6 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
|||||||
return new 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)
|
public static void MapText(LabelHandler handler, ILabel label)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
@@ -103,7 +60,7 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
if (label.TextColor is not null)
|
if (label.TextColor is not null)
|
||||||
handler.PlatformView.TextColor = label.TextColor;
|
handler.PlatformView.TextColor = label.TextColor.ToSKColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapFont(LabelHandler handler, ILabel label)
|
public static void MapFont(LabelHandler handler, ILabel label)
|
||||||
@@ -112,37 +69,32 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
|||||||
|
|
||||||
var font = label.Font;
|
var font = label.Font;
|
||||||
if (font.Size > 0)
|
if (font.Size > 0)
|
||||||
handler.PlatformView.FontSize = font.Size;
|
handler.PlatformView.FontSize = (float)font.Size;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(font.Family))
|
if (!string.IsNullOrEmpty(font.Family))
|
||||||
handler.PlatformView.FontFamily = font.Family;
|
handler.PlatformView.FontFamily = font.Family;
|
||||||
|
|
||||||
// Convert Font weight/slant to FontAttributes
|
handler.PlatformView.IsBold = font.Weight >= FontWeight.Bold;
|
||||||
FontAttributes attrs = FontAttributes.None;
|
handler.PlatformView.IsItalic = font.Slant == FontSlant.Italic || font.Slant == FontSlant.Oblique;
|
||||||
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)
|
public static void MapCharacterSpacing(LabelHandler handler, ILabel label)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
handler.PlatformView.CharacterSpacing = label.CharacterSpacing;
|
handler.PlatformView.CharacterSpacing = (float)label.CharacterSpacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapHorizontalTextAlignment(LabelHandler handler, ILabel label)
|
public static void MapHorizontalTextAlignment(LabelHandler handler, ILabel label)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
// Map MAUI TextAlignment to our TextAlignment
|
// Map MAUI TextAlignment to our internal TextAlignment
|
||||||
handler.PlatformView.HorizontalTextAlignment = label.HorizontalTextAlignment switch
|
handler.PlatformView.HorizontalTextAlignment = label.HorizontalTextAlignment switch
|
||||||
{
|
{
|
||||||
Microsoft.Maui.TextAlignment.Start => TextAlignment.Start,
|
Microsoft.Maui.TextAlignment.Start => Platform.TextAlignment.Start,
|
||||||
Microsoft.Maui.TextAlignment.Center => TextAlignment.Center,
|
Microsoft.Maui.TextAlignment.Center => Platform.TextAlignment.Center,
|
||||||
Microsoft.Maui.TextAlignment.End => TextAlignment.End,
|
Microsoft.Maui.TextAlignment.End => Platform.TextAlignment.End,
|
||||||
_ => TextAlignment.Start
|
_ => Platform.TextAlignment.Start
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,23 +104,25 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
|||||||
|
|
||||||
handler.PlatformView.VerticalTextAlignment = label.VerticalTextAlignment switch
|
handler.PlatformView.VerticalTextAlignment = label.VerticalTextAlignment switch
|
||||||
{
|
{
|
||||||
Microsoft.Maui.TextAlignment.Start => TextAlignment.Start,
|
Microsoft.Maui.TextAlignment.Start => Platform.TextAlignment.Start,
|
||||||
Microsoft.Maui.TextAlignment.Center => TextAlignment.Center,
|
Microsoft.Maui.TextAlignment.Center => Platform.TextAlignment.Center,
|
||||||
Microsoft.Maui.TextAlignment.End => TextAlignment.End,
|
Microsoft.Maui.TextAlignment.End => Platform.TextAlignment.End,
|
||||||
_ => TextAlignment.Center
|
_ => Platform.TextAlignment.Center
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapTextDecorations(LabelHandler handler, ILabel label)
|
public static void MapTextDecorations(LabelHandler handler, ILabel label)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
handler.PlatformView.TextDecorations = label.TextDecorations;
|
|
||||||
|
handler.PlatformView.IsUnderline = (label.TextDecorations & TextDecorations.Underline) != 0;
|
||||||
|
handler.PlatformView.IsStrikethrough = (label.TextDecorations & TextDecorations.Strikethrough) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapLineHeight(LabelHandler handler, ILabel label)
|
public static void MapLineHeight(LabelHandler handler, ILabel label)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
handler.PlatformView.LineHeight = label.LineHeight;
|
handler.PlatformView.LineHeight = (float)label.LineHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapLineBreakMode(LabelHandler handler, ILabel label)
|
public static void MapLineBreakMode(LabelHandler handler, ILabel label)
|
||||||
@@ -178,7 +132,16 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
|||||||
// LineBreakMode is on Label control, not ILabel interface
|
// LineBreakMode is on Label control, not ILabel interface
|
||||||
if (label is Microsoft.Maui.Controls.Label mauiLabel)
|
if (label is Microsoft.Maui.Controls.Label mauiLabel)
|
||||||
{
|
{
|
||||||
handler.PlatformView.LineBreakMode = mauiLabel.LineBreakMode;
|
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
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,11 +161,11 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
var padding = label.Padding;
|
var padding = label.Padding;
|
||||||
handler.PlatformView.Padding = new Thickness(
|
handler.PlatformView.Padding = new SKRect(
|
||||||
padding.Left,
|
(float)padding.Left,
|
||||||
padding.Top,
|
(float)padding.Top,
|
||||||
padding.Right,
|
(float)padding.Right,
|
||||||
padding.Bottom);
|
(float)padding.Bottom);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapBackground(LabelHandler handler, ILabel label)
|
public static void MapBackground(LabelHandler handler, ILabel label)
|
||||||
@@ -211,7 +174,7 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
|||||||
|
|
||||||
if (label.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (label.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,17 +205,4 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
|||||||
_ => LayoutOptions.Start
|
_ => 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,7 +2,6 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
using Microsoft.Maui.Handlers;
|
using Microsoft.Maui.Handlers;
|
||||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform;
|
namespace Microsoft.Maui.Platform;
|
||||||
@@ -64,7 +63,7 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
|||||||
// (e.g., in ItemTemplates for CollectionView)
|
// (e.g., in ItemTemplates for CollectionView)
|
||||||
if (VirtualView is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
if (VirtualView is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||||
{
|
{
|
||||||
platformView.BackgroundColor = ve.BackgroundColor;
|
platformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||||
platformView.Invalidate();
|
platformView.Invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +78,7 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
|||||||
// Create handler for child if it doesn't exist
|
// Create handler for child if it doesn't exist
|
||||||
if (child.Handler == null)
|
if (child.Handler == null)
|
||||||
{
|
{
|
||||||
child.Handler = child.ToViewHandler(MauiContext);
|
child.Handler = child.ToHandler(MauiContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (child.Handler?.PlatformView is SkiaView skiaChild)
|
if (child.Handler?.PlatformView is SkiaView skiaChild)
|
||||||
@@ -99,7 +98,7 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
|||||||
var background = layout.Background;
|
var background = layout.Background;
|
||||||
if (background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
if (background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidBrush.Color;
|
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
handler.PlatformView.Invalidate();
|
handler.PlatformView.Invalidate();
|
||||||
}
|
}
|
||||||
@@ -108,7 +107,7 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
|||||||
{
|
{
|
||||||
if (layout is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
if (layout is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor;
|
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||||
handler.PlatformView.Invalidate();
|
handler.PlatformView.Invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -300,7 +299,7 @@ public partial class GridHandler : LayoutHandler
|
|||||||
// Create handler for child if it doesn't exist
|
// Create handler for child if it doesn't exist
|
||||||
if (child.Handler == null)
|
if (child.Handler == null)
|
||||||
{
|
{
|
||||||
child.Handler = child.ToViewHandler(MauiContext);
|
child.Handler = child.ToHandler(MauiContext);
|
||||||
Console.WriteLine($"[GridHandler.ConnectHandler] Created handler for child[{i}]: {child.Handler?.GetType().Name ?? "failed"}");
|
Console.WriteLine($"[GridHandler.ConnectHandler] Created handler for child[{i}]: {child.Handler?.GetType().Name ?? "failed"}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using Microsoft.Maui.Handlers;
|
using Microsoft.Maui.Handlers;
|
||||||
using Microsoft.Maui.Graphics;
|
using Microsoft.Maui.Graphics;
|
||||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||||
@@ -54,7 +53,7 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
|||||||
// Explicitly map BackgroundColor since it may be set before handler creation
|
// Explicitly map BackgroundColor since it may be set before handler creation
|
||||||
if (VirtualView is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
if (VirtualView is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||||
{
|
{
|
||||||
platformView.BackgroundColor = ve.BackgroundColor;
|
platformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < VirtualView.Count; i++)
|
for (int i = 0; i < VirtualView.Count; i++)
|
||||||
@@ -65,7 +64,7 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
|||||||
// Create handler for child if it doesn't exist
|
// Create handler for child if it doesn't exist
|
||||||
if (child.Handler == null)
|
if (child.Handler == null)
|
||||||
{
|
{
|
||||||
child.Handler = child.ToViewHandler(MauiContext);
|
child.Handler = child.ToHandler(MauiContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add child's platform view to our layout
|
// Add child's platform view to our layout
|
||||||
@@ -88,7 +87,7 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
|||||||
|
|
||||||
if (layout.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (layout.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +142,12 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
|||||||
|
|
||||||
if (layout is IPadding paddable)
|
if (layout is IPadding paddable)
|
||||||
{
|
{
|
||||||
handler.PlatformView.Padding = paddable.Padding;
|
var padding = paddable.Padding;
|
||||||
|
handler.PlatformView.Padding = new SKRect(
|
||||||
|
(float)padding.Left,
|
||||||
|
(float)padding.Top,
|
||||||
|
(float)padding.Right,
|
||||||
|
(float)padding.Bottom);
|
||||||
handler.PlatformView.InvalidateMeasure();
|
handler.PlatformView.InvalidateMeasure();
|
||||||
handler.PlatformView.Invalidate();
|
handler.PlatformView.Invalidate();
|
||||||
}
|
}
|
||||||
@@ -250,14 +254,18 @@ public partial class GridHandler : LayoutHandler
|
|||||||
// Explicitly map BackgroundColor since it may be set before handler creation
|
// Explicitly map BackgroundColor since it may be set before handler creation
|
||||||
if (VirtualView is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
if (VirtualView is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||||
{
|
{
|
||||||
platformView.BackgroundColor = ve.BackgroundColor;
|
platformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicitly map Padding since it may be set before handler creation
|
// Explicitly map Padding since it may be set before handler creation
|
||||||
if (VirtualView is IPadding paddable)
|
if (VirtualView is IPadding paddable)
|
||||||
{
|
{
|
||||||
var padding = paddable.Padding;
|
var padding = paddable.Padding;
|
||||||
platformView.Padding = padding;
|
platformView.Padding = new SKRect(
|
||||||
|
(float)padding.Left,
|
||||||
|
(float)padding.Top,
|
||||||
|
(float)padding.Right,
|
||||||
|
(float)padding.Bottom);
|
||||||
Console.WriteLine($"[GridHandler] Applied Padding: L={padding.Left}, T={padding.Top}, R={padding.Right}, B={padding.Bottom}");
|
Console.WriteLine($"[GridHandler] Applied Padding: L={padding.Left}, T={padding.Top}, R={padding.Right}, B={padding.Bottom}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +284,7 @@ public partial class GridHandler : LayoutHandler
|
|||||||
// Create handler for child if it doesn't exist
|
// Create handler for child if it doesn't exist
|
||||||
if (child.Handler == null)
|
if (child.Handler == null)
|
||||||
{
|
{
|
||||||
child.Handler = child.ToViewHandler(MauiContext);
|
child.Handler = child.ToHandler(MauiContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get grid position from attached properties
|
// Get grid position from attached properties
|
||||||
|
|||||||
@@ -1,376 +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.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,14 +1,11 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
using System.IO;
|
|
||||||
using Microsoft.Maui.Handlers;
|
using Microsoft.Maui.Handlers;
|
||||||
using Microsoft.Maui.Graphics;
|
using Microsoft.Maui.Graphics;
|
||||||
using Microsoft.Maui.Controls;
|
using Microsoft.Maui.Controls;
|
||||||
using Microsoft.Maui.Platform;
|
using Microsoft.Maui.Platform;
|
||||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
using Svg.Skia;
|
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||||
@@ -103,7 +100,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
|||||||
if (page.Handler == null)
|
if (page.Handler == null)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[NavigationPageHandler] Creating handler for: {page.Title}");
|
Console.WriteLine($"[NavigationPageHandler] Creating handler for: {page.Title}");
|
||||||
page.Handler = page.ToViewHandler(MauiContext);
|
page.Handler = page.ToHandler(MauiContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine($"[NavigationPageHandler] Page handler type: {page.Handler?.GetType().Name}");
|
Console.WriteLine($"[NavigationPageHandler] Page handler type: {page.Handler?.GetType().Name}");
|
||||||
@@ -125,7 +122,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
|||||||
Console.WriteLine($"[NavigationPageHandler] Content is null, manually creating handler for: {contentPage.Content.GetType().Name}");
|
Console.WriteLine($"[NavigationPageHandler] Content is null, manually creating handler for: {contentPage.Content.GetType().Name}");
|
||||||
if (contentPage.Content.Handler == null)
|
if (contentPage.Content.Handler == null)
|
||||||
{
|
{
|
||||||
contentPage.Content.Handler = contentPage.Content.ToViewHandler(MauiContext);
|
contentPage.Content.Handler = contentPage.Content.ToHandler(MauiContext);
|
||||||
}
|
}
|
||||||
if (contentPage.Content.Handler?.PlatformView is SkiaView skiaContent)
|
if (contentPage.Content.Handler?.PlatformView is SkiaView skiaContent)
|
||||||
{
|
{
|
||||||
@@ -166,7 +163,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
|||||||
contentPage.ToolbarItems.Clear();
|
contentPage.ToolbarItems.Clear();
|
||||||
foreach (var item in page.ToolbarItems)
|
foreach (var item in page.ToolbarItems)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[NavigationPageHandler] Adding toolbar item: '{item.Text}', IconImageSource={item.IconImageSource}, Order={item.Order}");
|
Console.WriteLine($"[NavigationPageHandler] Adding toolbar item: '{item.Text}', Order={item.Order}");
|
||||||
// Default and Primary should both be treated as Primary (shown in toolbar)
|
// Default and Primary should both be treated as Primary (shown in toolbar)
|
||||||
// Only Secondary goes to overflow menu
|
// Only Secondary goes to overflow menu
|
||||||
var order = item.Order == ToolbarItemOrder.Secondary
|
var order = item.Order == ToolbarItemOrder.Secondary
|
||||||
@@ -190,17 +187,9 @@ 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
|
contentPage.ToolbarItems.Add(new SkiaToolbarItem
|
||||||
{
|
{
|
||||||
Text = item.Text ?? "",
|
Text = item.Text ?? "",
|
||||||
Icon = icon,
|
|
||||||
Order = order,
|
Order = order,
|
||||||
Command = clickCommand
|
Command = clickCommand
|
||||||
});
|
});
|
||||||
@@ -221,56 +210,6 @@ 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)
|
private void OnVirtualViewPushed(object? sender, Microsoft.Maui.Controls.NavigationEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -282,7 +221,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
|||||||
if (e.Page.Handler == null)
|
if (e.Page.Handler == null)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[NavigationPageHandler] Creating handler for page: {e.Page.GetType().Name}");
|
Console.WriteLine($"[NavigationPageHandler] Creating handler for page: {e.Page.GetType().Name}");
|
||||||
e.Page.Handler = e.Page.ToViewHandler(MauiContext);
|
e.Page.Handler = e.Page.ToHandler(MauiContext);
|
||||||
Console.WriteLine($"[NavigationPageHandler] Handler created: {e.Page.Handler?.GetType().Name}");
|
Console.WriteLine($"[NavigationPageHandler] Handler created: {e.Page.Handler?.GetType().Name}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,30 +231,12 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
|||||||
skiaPage.ShowNavigationBar = true;
|
skiaPage.ShowNavigationBar = true;
|
||||||
skiaPage.TitleBarColor = PlatformView.BarBackgroundColor;
|
skiaPage.TitleBarColor = PlatformView.BarBackgroundColor;
|
||||||
skiaPage.TitleTextColor = PlatformView.BarTextColor;
|
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");
|
Console.WriteLine($"[NavigationPageHandler] Mapping toolbar items");
|
||||||
MapToolbarItems(skiaPage, e.Page);
|
MapToolbarItems(skiaPage, e.Page);
|
||||||
Console.WriteLine($"[NavigationPageHandler] Pushing page to platform");
|
Console.WriteLine($"[NavigationPageHandler] Pushing page to platform");
|
||||||
PlatformView.Push(skiaPage, false);
|
PlatformView.Push(skiaPage, true);
|
||||||
Console.WriteLine($"[NavigationPageHandler] Push complete, thread={Environment.CurrentManagedThreadId}");
|
Console.WriteLine($"[NavigationPageHandler] Push complete");
|
||||||
}
|
}
|
||||||
Console.WriteLine("[NavigationPageHandler] OnVirtualViewPushed returning");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -329,13 +250,13 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
|||||||
{
|
{
|
||||||
Console.WriteLine($"[NavigationPageHandler] VirtualView Popped: {e.Page?.Title}");
|
Console.WriteLine($"[NavigationPageHandler] VirtualView Popped: {e.Page?.Title}");
|
||||||
// Pop on the platform side to sync with MAUI navigation
|
// Pop on the platform side to sync with MAUI navigation
|
||||||
PlatformView?.Pop();
|
PlatformView?.Pop(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnVirtualViewPoppedToRoot(object? sender, Microsoft.Maui.Controls.NavigationEventArgs e)
|
private void OnVirtualViewPoppedToRoot(object? sender, Microsoft.Maui.Controls.NavigationEventArgs e)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[NavigationPageHandler] VirtualView PoppedToRoot");
|
Console.WriteLine($"[NavigationPageHandler] VirtualView PoppedToRoot");
|
||||||
PlatformView?.PopToRoot();
|
PlatformView?.PopToRoot(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPushed(object? sender, NavigationEventArgs e)
|
private void OnPushed(object? sender, NavigationEventArgs e)
|
||||||
@@ -364,7 +285,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
|||||||
|
|
||||||
if (navigationPage.BarBackgroundColor is not null)
|
if (navigationPage.BarBackgroundColor is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BarBackgroundColor = navigationPage.BarBackgroundColor;
|
handler.PlatformView.BarBackgroundColor = navigationPage.BarBackgroundColor.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,7 +295,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
|||||||
|
|
||||||
if (navigationPage.BarBackground is SolidColorBrush solidBrush)
|
if (navigationPage.BarBackground is SolidColorBrush solidBrush)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BarBackgroundColor = solidBrush.Color;
|
handler.PlatformView.BarBackgroundColor = solidBrush.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,7 +305,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
|||||||
|
|
||||||
if (navigationPage.BarTextColor is not null)
|
if (navigationPage.BarTextColor is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BarTextColor = navigationPage.BarTextColor;
|
handler.PlatformView.BarTextColor = navigationPage.BarTextColor.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,7 +315,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
|||||||
|
|
||||||
if (navigationPage.Background is SolidColorBrush solidBrush)
|
if (navigationPage.Background is SolidColorBrush solidBrush)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidBrush.Color;
|
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,7 +334,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
|||||||
// Ensure handler exists
|
// Ensure handler exists
|
||||||
if (page.Handler == null)
|
if (page.Handler == null)
|
||||||
{
|
{
|
||||||
page.Handler = page.ToViewHandler(handler.MauiContext);
|
page.Handler = page.ToHandler(handler.MauiContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (page.Handler?.PlatformView is SkiaPage skiaPage)
|
if (page.Handler?.PlatformView is SkiaPage skiaPage)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using Microsoft.Maui.Handlers;
|
|||||||
using Microsoft.Maui.Graphics;
|
using Microsoft.Maui.Graphics;
|
||||||
using Microsoft.Maui.Controls;
|
using Microsoft.Maui.Controls;
|
||||||
using Microsoft.Maui.Platform;
|
using Microsoft.Maui.Platform;
|
||||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||||
@@ -20,11 +19,8 @@ public partial class PageHandler : ViewHandler<Page, SkiaPage>
|
|||||||
{
|
{
|
||||||
[nameof(Page.Title)] = MapTitle,
|
[nameof(Page.Title)] = MapTitle,
|
||||||
[nameof(Page.BackgroundImageSource)] = MapBackgroundImageSource,
|
[nameof(Page.BackgroundImageSource)] = MapBackgroundImageSource,
|
||||||
[nameof(Page.IconImageSource)] = MapIconImageSource,
|
|
||||||
[nameof(Page.Padding)] = MapPadding,
|
[nameof(Page.Padding)] = MapPadding,
|
||||||
[nameof(Page.IsBusy)] = MapIsBusy,
|
|
||||||
[nameof(IView.Background)] = MapBackground,
|
[nameof(IView.Background)] = MapBackground,
|
||||||
[nameof(VisualElement.BackgroundColor)] = MapBackgroundColor,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static CommandMapper<Page, PageHandler> CommandMapper =
|
public static CommandMapper<Page, PageHandler> CommandMapper =
|
||||||
@@ -49,10 +45,6 @@ public partial class PageHandler : ViewHandler<Page, SkiaPage>
|
|||||||
protected override void ConnectHandler(SkiaPage platformView)
|
protected override void ConnectHandler(SkiaPage platformView)
|
||||||
{
|
{
|
||||||
base.ConnectHandler(platformView);
|
base.ConnectHandler(platformView);
|
||||||
|
|
||||||
// Set MauiPage reference for theme refresh support
|
|
||||||
platformView.MauiPage = VirtualView;
|
|
||||||
|
|
||||||
platformView.Appearing += OnAppearing;
|
platformView.Appearing += OnAppearing;
|
||||||
platformView.Disappearing += OnDisappearing;
|
platformView.Disappearing += OnDisappearing;
|
||||||
}
|
}
|
||||||
@@ -61,7 +53,6 @@ public partial class PageHandler : ViewHandler<Page, SkiaPage>
|
|||||||
{
|
{
|
||||||
platformView.Appearing -= OnAppearing;
|
platformView.Appearing -= OnAppearing;
|
||||||
platformView.Disappearing -= OnDisappearing;
|
platformView.Disappearing -= OnDisappearing;
|
||||||
platformView.MauiPage = null;
|
|
||||||
base.DisconnectHandler(platformView);
|
base.DisconnectHandler(platformView);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,34 +97,9 @@ public partial class PageHandler : ViewHandler<Page, SkiaPage>
|
|||||||
|
|
||||||
if (page.Background is SolidColorBrush solidBrush)
|
if (page.Background is SolidColorBrush solidBrush)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidBrush.Color;
|
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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>
|
/// <summary>
|
||||||
@@ -145,7 +111,6 @@ public partial class ContentPageHandler : PageHandler
|
|||||||
new PropertyMapper<ContentPage, ContentPageHandler>(PageHandler.Mapper)
|
new PropertyMapper<ContentPage, ContentPageHandler>(PageHandler.Mapper)
|
||||||
{
|
{
|
||||||
[nameof(ContentPage.Content)] = MapContent,
|
[nameof(ContentPage.Content)] = MapContent,
|
||||||
[nameof(ContentPage.ToolbarItems)] = MapToolbarItems,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static new CommandMapper<ContentPage, ContentPageHandler> CommandMapper =
|
public static new CommandMapper<ContentPage, ContentPageHandler> CommandMapper =
|
||||||
@@ -167,17 +132,6 @@ public partial class ContentPageHandler : PageHandler
|
|||||||
return new SkiaContentPage();
|
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)
|
public static void MapContent(ContentPageHandler handler, ContentPage page)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null || handler.MauiContext is null) return;
|
if (handler.PlatformView is null || handler.MauiContext is null) return;
|
||||||
@@ -190,7 +144,7 @@ public partial class ContentPageHandler : PageHandler
|
|||||||
if (content.Handler == null)
|
if (content.Handler == null)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[ContentPageHandler] Creating handler for content: {content.GetType().Name}");
|
Console.WriteLine($"[ContentPageHandler] Creating handler for content: {content.GetType().Name}");
|
||||||
content.Handler = content.ToViewHandler(handler.MauiContext);
|
content.Handler = content.ToHandler(handler.MauiContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The content's handler should provide the platform view
|
// The content's handler should provide the platform view
|
||||||
@@ -209,38 +163,4 @@ public partial class ContentPageHandler : PageHandler
|
|||||||
handler.PlatformView.Content = null;
|
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.Graphics;
|
||||||
using Microsoft.Maui.Controls;
|
using Microsoft.Maui.Controls;
|
||||||
using Microsoft.Maui.Platform;
|
using Microsoft.Maui.Platform;
|
||||||
|
using SkiaSharp;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handler for Picker on Linux using Skia rendering.
|
/// Handler for Picker on Linux using Skia rendering.
|
||||||
/// Maps IPicker interface to SkiaPicker platform view.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
||||||
{
|
{
|
||||||
@@ -22,12 +22,10 @@ public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
|||||||
[nameof(IPicker.TitleColor)] = MapTitleColor,
|
[nameof(IPicker.TitleColor)] = MapTitleColor,
|
||||||
[nameof(IPicker.SelectedIndex)] = MapSelectedIndex,
|
[nameof(IPicker.SelectedIndex)] = MapSelectedIndex,
|
||||||
[nameof(IPicker.TextColor)] = MapTextColor,
|
[nameof(IPicker.TextColor)] = MapTextColor,
|
||||||
[nameof(ITextStyle.Font)] = MapFont,
|
|
||||||
[nameof(IPicker.CharacterSpacing)] = MapCharacterSpacing,
|
[nameof(IPicker.CharacterSpacing)] = MapCharacterSpacing,
|
||||||
[nameof(IPicker.HorizontalTextAlignment)] = MapHorizontalTextAlignment,
|
[nameof(IPicker.HorizontalTextAlignment)] = MapHorizontalTextAlignment,
|
||||||
[nameof(IPicker.VerticalTextAlignment)] = MapVerticalTextAlignment,
|
[nameof(IPicker.VerticalTextAlignment)] = MapVerticalTextAlignment,
|
||||||
[nameof(IView.Background)] = MapBackground,
|
[nameof(IView.Background)] = MapBackground,
|
||||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
|
||||||
[nameof(Picker.ItemsSource)] = MapItemsSource,
|
[nameof(Picker.ItemsSource)] = MapItemsSource,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -64,17 +62,8 @@ public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
|||||||
_itemsCollection.CollectionChanged += OnItemsCollectionChanged;
|
_itemsCollection.CollectionChanged += OnItemsCollectionChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load items and sync properties
|
// Load items
|
||||||
ReloadItems();
|
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)
|
protected override void DisconnectHandler(SkiaPicker platformView)
|
||||||
@@ -95,14 +84,11 @@ public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
|||||||
ReloadItems();
|
ReloadItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSelectedIndexChanged(object? sender, SelectedIndexChangedEventArgs e)
|
private void OnSelectedIndexChanged(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (VirtualView is null || PlatformView is null) return;
|
if (VirtualView is null || PlatformView is null) return;
|
||||||
|
|
||||||
if (VirtualView.SelectedIndex != e.NewIndex)
|
VirtualView.SelectedIndex = PlatformView.SelectedIndex;
|
||||||
{
|
|
||||||
VirtualView.SelectedIndex = e.NewIndex;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReloadItems()
|
private void ReloadItems()
|
||||||
@@ -124,68 +110,38 @@ public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
if (picker.TitleColor is not null)
|
if (picker.TitleColor is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.TitleColor = picker.TitleColor;
|
handler.PlatformView.TitleColor = picker.TitleColor.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapSelectedIndex(PickerHandler handler, IPicker picker)
|
public static void MapSelectedIndex(PickerHandler handler, IPicker picker)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
if (handler.PlatformView.SelectedIndex != picker.SelectedIndex)
|
|
||||||
{
|
|
||||||
handler.PlatformView.SelectedIndex = picker.SelectedIndex;
|
handler.PlatformView.SelectedIndex = picker.SelectedIndex;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static void MapTextColor(PickerHandler handler, IPicker picker)
|
public static void MapTextColor(PickerHandler handler, IPicker picker)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
if (picker.TextColor is not null)
|
if (picker.TextColor is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.TextColor = picker.TextColor;
|
handler.PlatformView.TextColor = picker.TextColor.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
public static void MapCharacterSpacing(PickerHandler handler, IPicker picker)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
// Character spacing could be implemented with custom text rendering
|
||||||
handler.PlatformView.CharacterSpacing = picker.CharacterSpacing;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapHorizontalTextAlignment(PickerHandler handler, IPicker picker)
|
public static void MapHorizontalTextAlignment(PickerHandler handler, IPicker picker)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
// Text alignment would require changes to SkiaPicker drawing
|
||||||
handler.PlatformView.HorizontalTextAlignment = picker.HorizontalTextAlignment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapVerticalTextAlignment(PickerHandler handler, IPicker picker)
|
public static void MapVerticalTextAlignment(PickerHandler handler, IPicker picker)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
// Text alignment would require changes to SkiaPicker drawing
|
||||||
handler.PlatformView.VerticalTextAlignment = picker.VerticalTextAlignment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapBackground(PickerHandler handler, IPicker picker)
|
public static void MapBackground(PickerHandler handler, IPicker picker)
|
||||||
@@ -194,17 +150,10 @@ public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
|||||||
|
|
||||||
if (picker.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (picker.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
public static void MapItemsSource(PickerHandler handler, IPicker picker)
|
||||||
{
|
{
|
||||||
handler.ReloadItems();
|
handler.ReloadItems();
|
||||||
|
|||||||
63
Handlers/ProgressBarHandler.Linux.cs
Normal file
63
Handlers/ProgressBarHandler.Linux.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// 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,11 +1,9 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// 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.Handlers;
|
||||||
using Microsoft.Maui.Graphics;
|
using Microsoft.Maui.Graphics;
|
||||||
using Microsoft.Maui.Platform;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||||
|
|
||||||
@@ -20,12 +18,7 @@ public partial class ProgressBarHandler : ViewHandler<IProgress, SkiaProgressBar
|
|||||||
{
|
{
|
||||||
[nameof(IProgress.Progress)] = MapProgress,
|
[nameof(IProgress.Progress)] = MapProgress,
|
||||||
[nameof(IProgress.ProgressColor)] = MapProgressColor,
|
[nameof(IProgress.ProgressColor)] = MapProgressColor,
|
||||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
|
||||||
[nameof(IView.Background)] = MapBackground,
|
[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)
|
public static CommandMapper<IProgress, ProgressBarHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||||
@@ -46,48 +39,6 @@ public partial class ProgressBarHandler : ViewHandler<IProgress, SkiaProgressBar
|
|||||||
return new 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)
|
public static void MapProgress(ProgressBarHandler handler, IProgress progress)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
@@ -99,18 +50,7 @@ public partial class ProgressBarHandler : ViewHandler<IProgress, SkiaProgressBar
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
if (progress.ProgressColor is not null)
|
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)
|
public static void MapBackground(ProgressBarHandler handler, IProgress progress)
|
||||||
@@ -119,49 +59,7 @@ public partial class ProgressBarHandler : ViewHandler<IProgress, SkiaProgressBar
|
|||||||
|
|
||||||
if (progress.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (progress.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
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)
|
if (radioButton.TextColor is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.TextColor = radioButton.TextColor;
|
handler.PlatformView.TextColor = radioButton.TextColor.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ public partial class RadioButtonHandler : ViewHandler<IRadioButton, SkiaRadioBut
|
|||||||
|
|
||||||
if (radioButton.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (radioButton.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,151 +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 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,7 +2,6 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
using Microsoft.Maui.Handlers;
|
using Microsoft.Maui.Handlers;
|
||||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||||
|
|
||||||
@@ -53,7 +52,7 @@ public partial class ScrollViewHandler : ViewHandler<IScrollView, SkiaScrollView
|
|||||||
// Create handler for content if it doesn't exist
|
// Create handler for content if it doesn't exist
|
||||||
if (content.Handler == null)
|
if (content.Handler == null)
|
||||||
{
|
{
|
||||||
content.Handler = content.ToViewHandler(handler.MauiContext);
|
content.Handler = content.ToHandler(handler.MauiContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.Handler?.PlatformView is SkiaView skiaContent)
|
if (content.Handler?.PlatformView is SkiaView skiaContent)
|
||||||
|
|||||||
106
Handlers/SearchBarHandler.Linux.cs
Normal file
106
Handlers/SearchBarHandler.Linux.cs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
// 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,11 +18,9 @@ public partial class SearchBarHandler : ViewHandler<ISearchBar, SkiaSearchBar>
|
|||||||
[nameof(ITextInput.Text)] = MapText,
|
[nameof(ITextInput.Text)] = MapText,
|
||||||
[nameof(ITextStyle.TextColor)] = MapTextColor,
|
[nameof(ITextStyle.TextColor)] = MapTextColor,
|
||||||
[nameof(ITextStyle.Font)] = MapFont,
|
[nameof(ITextStyle.Font)] = MapFont,
|
||||||
[nameof(ITextStyle.CharacterSpacing)] = MapCharacterSpacing,
|
|
||||||
[nameof(IPlaceholder.Placeholder)] = MapPlaceholder,
|
[nameof(IPlaceholder.Placeholder)] = MapPlaceholder,
|
||||||
[nameof(IPlaceholder.PlaceholderColor)] = MapPlaceholderColor,
|
[nameof(IPlaceholder.PlaceholderColor)] = MapPlaceholderColor,
|
||||||
[nameof(ISearchBar.CancelButtonColor)] = MapCancelButtonColor,
|
[nameof(ISearchBar.CancelButtonColor)] = MapCancelButtonColor,
|
||||||
[nameof(ISearchBar.HorizontalTextAlignment)] = MapHorizontalTextAlignment,
|
|
||||||
[nameof(IView.Background)] = MapBackground,
|
[nameof(IView.Background)] = MapBackground,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -86,7 +84,7 @@ public partial class SearchBarHandler : ViewHandler<ISearchBar, SkiaSearchBar>
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
if (searchBar.TextColor is not null)
|
if (searchBar.TextColor is not null)
|
||||||
handler.PlatformView.TextColor = searchBar.TextColor;
|
handler.PlatformView.TextColor = searchBar.TextColor.ToSKColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapFont(SearchBarHandler handler, ISearchBar searchBar)
|
public static void MapFont(SearchBarHandler handler, ISearchBar searchBar)
|
||||||
@@ -95,28 +93,10 @@ public partial class SearchBarHandler : ViewHandler<ISearchBar, SkiaSearchBar>
|
|||||||
|
|
||||||
var font = searchBar.Font;
|
var font = searchBar.Font;
|
||||||
if (font.Size > 0)
|
if (font.Size > 0)
|
||||||
handler.PlatformView.FontSize = font.Size;
|
handler.PlatformView.FontSize = (float)font.Size;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(font.Family))
|
if (!string.IsNullOrEmpty(font.Family))
|
||||||
handler.PlatformView.FontFamily = 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)
|
public static void MapPlaceholder(SearchBarHandler handler, ISearchBar searchBar)
|
||||||
@@ -130,7 +110,7 @@ public partial class SearchBarHandler : ViewHandler<ISearchBar, SkiaSearchBar>
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
if (searchBar.PlaceholderColor is not null)
|
if (searchBar.PlaceholderColor is not null)
|
||||||
handler.PlatformView.PlaceholderColor = searchBar.PlaceholderColor;
|
handler.PlatformView.PlaceholderColor = searchBar.PlaceholderColor.ToSKColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapCancelButtonColor(SearchBarHandler handler, ISearchBar searchBar)
|
public static void MapCancelButtonColor(SearchBarHandler handler, ISearchBar searchBar)
|
||||||
@@ -139,7 +119,7 @@ public partial class SearchBarHandler : ViewHandler<ISearchBar, SkiaSearchBar>
|
|||||||
|
|
||||||
// CancelButtonColor maps to ClearButtonColor
|
// CancelButtonColor maps to ClearButtonColor
|
||||||
if (searchBar.CancelButtonColor is not null)
|
if (searchBar.CancelButtonColor is not null)
|
||||||
handler.PlatformView.ClearButtonColor = searchBar.CancelButtonColor;
|
handler.PlatformView.ClearButtonColor = searchBar.CancelButtonColor.ToSKColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -149,7 +129,7 @@ public partial class SearchBarHandler : ViewHandler<ISearchBar, SkiaSearchBar>
|
|||||||
|
|
||||||
if (searchBar.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (searchBar.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
using Microsoft.Maui.Controls;
|
using Microsoft.Maui.Controls;
|
||||||
using Microsoft.Maui.Handlers;
|
using Microsoft.Maui.Handlers;
|
||||||
using Microsoft.Maui.Graphics;
|
using Microsoft.Maui.Graphics;
|
||||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||||
@@ -14,27 +13,12 @@ namespace Microsoft.Maui.Platform.Linux.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class ShellHandler : ViewHandler<Shell, SkiaShell>
|
public partial class ShellHandler : ViewHandler<Shell, SkiaShell>
|
||||||
{
|
{
|
||||||
private bool _isUpdatingFlyoutPresented;
|
|
||||||
|
|
||||||
public static IPropertyMapper<Shell, ShellHandler> Mapper = new PropertyMapper<Shell, ShellHandler>(ViewHandler.ViewMapper)
|
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)
|
public static CommandMapper<Shell, ShellHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||||
{
|
{
|
||||||
["GoToAsync"] = MapGoToAsync,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public ShellHandler() : base(Mapper, CommandMapper)
|
public ShellHandler() : base(Mapper, CommandMapper)
|
||||||
@@ -48,32 +32,20 @@ public partial class ShellHandler : ViewHandler<Shell, SkiaShell>
|
|||||||
|
|
||||||
protected override SkiaShell CreatePlatformView()
|
protected override SkiaShell CreatePlatformView()
|
||||||
{
|
{
|
||||||
Console.WriteLine("[ShellHandler] CreatePlatformView - creating SkiaShell");
|
|
||||||
return new SkiaShell();
|
return new SkiaShell();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ConnectHandler(SkiaShell platformView)
|
protected override void ConnectHandler(SkiaShell platformView)
|
||||||
{
|
{
|
||||||
Console.WriteLine("[ShellHandler] ConnectHandler - connecting to SkiaShell");
|
|
||||||
base.ConnectHandler(platformView);
|
base.ConnectHandler(platformView);
|
||||||
platformView.FlyoutIsPresentedChanged += OnFlyoutIsPresentedChanged;
|
platformView.FlyoutIsPresentedChanged += OnFlyoutIsPresentedChanged;
|
||||||
platformView.Navigated += OnNavigated;
|
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
|
// Subscribe to Shell navigation events
|
||||||
if (VirtualView != null)
|
if (VirtualView != null)
|
||||||
{
|
{
|
||||||
VirtualView.Navigating += OnShellNavigating;
|
VirtualView.Navigating += OnShellNavigating;
|
||||||
VirtualView.Navigated += OnShellNavigated;
|
VirtualView.Navigated += OnShellNavigated;
|
||||||
|
|
||||||
// Initial sync of shell items
|
|
||||||
SyncShellItems();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,9 +53,6 @@ public partial class ShellHandler : ViewHandler<Shell, SkiaShell>
|
|||||||
{
|
{
|
||||||
platformView.FlyoutIsPresentedChanged -= OnFlyoutIsPresentedChanged;
|
platformView.FlyoutIsPresentedChanged -= OnFlyoutIsPresentedChanged;
|
||||||
platformView.Navigated -= OnNavigated;
|
platformView.Navigated -= OnNavigated;
|
||||||
platformView.MauiShell = null;
|
|
||||||
platformView.ContentRenderer = null;
|
|
||||||
platformView.ColorRefresher = null;
|
|
||||||
|
|
||||||
if (VirtualView != null)
|
if (VirtualView != null)
|
||||||
{
|
{
|
||||||
@@ -96,20 +65,10 @@ public partial class ShellHandler : ViewHandler<Shell, SkiaShell>
|
|||||||
|
|
||||||
private void OnFlyoutIsPresentedChanged(object? sender, EventArgs e)
|
private void OnFlyoutIsPresentedChanged(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (VirtualView is null || PlatformView is null || _isUpdatingFlyoutPresented) return;
|
// Sync flyout state to virtual view
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_isUpdatingFlyoutPresented = true;
|
|
||||||
VirtualView.FlyoutIsPresented = PlatformView.FlyoutIsPresented;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_isUpdatingFlyoutPresented = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnNavigated(object? sender, Platform.ShellNavigationEventArgs e)
|
private void OnNavigated(object? sender, ShellNavigationEventArgs e)
|
||||||
{
|
{
|
||||||
// Handle platform navigation events
|
// Handle platform navigation events
|
||||||
}
|
}
|
||||||
@@ -131,289 +90,4 @@ public partial class ShellHandler : ViewHandler<Shell, SkiaShell>
|
|||||||
{
|
{
|
||||||
Console.WriteLine($"[ShellHandler] Shell Navigated to: {e.Current?.Location}");
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
123
Handlers/SliderHandler.Linux.cs
Normal file
123
Handlers/SliderHandler.Linux.cs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
// 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);
|
base.DisconnectHandler(platformView);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnValueChanged(object? sender, ValueChangedEventArgs e)
|
private void OnValueChanged(object? sender, SliderValueChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (VirtualView is null || PlatformView is null) return;
|
if (VirtualView is null || PlatformView is null) return;
|
||||||
|
|
||||||
@@ -112,16 +112,18 @@ public partial class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
|||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
|
// MinimumTrackColor maps to ActiveTrackColor (the filled portion)
|
||||||
if (slider.MinimumTrackColor is not null)
|
if (slider.MinimumTrackColor is not null)
|
||||||
handler.PlatformView.MinimumTrackColor = slider.MinimumTrackColor;
|
handler.PlatformView.ActiveTrackColor = slider.MinimumTrackColor.ToSKColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapMaximumTrackColor(SliderHandler handler, ISlider slider)
|
public static void MapMaximumTrackColor(SliderHandler handler, ISlider slider)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
|
// MaximumTrackColor maps to TrackColor (the unfilled portion)
|
||||||
if (slider.MaximumTrackColor is not null)
|
if (slider.MaximumTrackColor is not null)
|
||||||
handler.PlatformView.MaximumTrackColor = slider.MaximumTrackColor;
|
handler.PlatformView.TrackColor = slider.MaximumTrackColor.ToSKColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapThumbColor(SliderHandler handler, ISlider slider)
|
public static void MapThumbColor(SliderHandler handler, ISlider slider)
|
||||||
@@ -129,7 +131,7 @@ public partial class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
if (slider.ThumbColor is not null)
|
if (slider.ThumbColor is not null)
|
||||||
handler.PlatformView.ThumbColor = slider.ThumbColor;
|
handler.PlatformView.ThumbColor = slider.ThumbColor.ToSKColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapBackground(SliderHandler handler, ISlider slider)
|
public static void MapBackground(SliderHandler handler, ISlider slider)
|
||||||
@@ -138,7 +140,7 @@ public partial class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
|||||||
|
|
||||||
if (slider.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (slider.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,13 @@
|
|||||||
|
|
||||||
using Microsoft.Maui.Handlers;
|
using Microsoft.Maui.Handlers;
|
||||||
using Microsoft.Maui.Graphics;
|
using Microsoft.Maui.Graphics;
|
||||||
using Microsoft.Maui.Controls;
|
|
||||||
using Microsoft.Maui.Platform;
|
using Microsoft.Maui.Platform;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handler for Stepper on Linux using Skia rendering.
|
/// Handler for Stepper on Linux using Skia rendering.
|
||||||
/// Maps IStepper interface to SkiaStepper platform view.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
public partial class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
||||||
{
|
{
|
||||||
@@ -20,9 +19,7 @@ public partial class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
|||||||
[nameof(IStepper.Value)] = MapValue,
|
[nameof(IStepper.Value)] = MapValue,
|
||||||
[nameof(IStepper.Minimum)] = MapMinimum,
|
[nameof(IStepper.Minimum)] = MapMinimum,
|
||||||
[nameof(IStepper.Maximum)] = MapMaximum,
|
[nameof(IStepper.Maximum)] = MapMaximum,
|
||||||
["Increment"] = MapIncrement,
|
|
||||||
[nameof(IView.Background)] = MapBackground,
|
[nameof(IView.Background)] = MapBackground,
|
||||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static CommandMapper<IStepper, StepperHandler> CommandMapper =
|
public static CommandMapper<IStepper, StepperHandler> CommandMapper =
|
||||||
@@ -48,26 +45,6 @@ public partial class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
|||||||
{
|
{
|
||||||
base.ConnectHandler(platformView);
|
base.ConnectHandler(platformView);
|
||||||
platformView.ValueChanged += OnValueChanged;
|
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)
|
protected override void DisconnectHandler(SkiaStepper platformView)
|
||||||
@@ -76,21 +53,15 @@ public partial class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
|||||||
base.DisconnectHandler(platformView);
|
base.DisconnectHandler(platformView);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnValueChanged(object? sender, ValueChangedEventArgs e)
|
private void OnValueChanged(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (VirtualView is null || PlatformView is null) return;
|
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)
|
public static void MapValue(StepperHandler handler, IStepper stepper)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
if (Math.Abs(handler.PlatformView.Value - stepper.Value) > 0.0001)
|
|
||||||
handler.PlatformView.Value = stepper.Value;
|
handler.PlatformView.Value = stepper.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,24 +83,7 @@ public partial class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
|||||||
|
|
||||||
if (stepper.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (stepper.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,226 +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 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
94
Handlers/SwitchHandler.Linux.cs
Normal file
94
Handlers/SwitchHandler.Linux.cs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
// 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,7 +19,6 @@ public partial class SwitchHandler : ViewHandler<ISwitch, SkiaSwitch>
|
|||||||
[nameof(ISwitch.TrackColor)] = MapTrackColor,
|
[nameof(ISwitch.TrackColor)] = MapTrackColor,
|
||||||
[nameof(ISwitch.ThumbColor)] = MapThumbColor,
|
[nameof(ISwitch.ThumbColor)] = MapThumbColor,
|
||||||
[nameof(IView.Background)] = MapBackground,
|
[nameof(IView.Background)] = MapBackground,
|
||||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static CommandMapper<ISwitch, SwitchHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
public static CommandMapper<ISwitch, SwitchHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||||
@@ -70,12 +69,13 @@ public partial class SwitchHandler : ViewHandler<ISwitch, SkiaSwitch>
|
|||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
// TrackColor sets the On track color (MAUI's OnColor)
|
// TrackColor sets both On and Off track colors
|
||||||
if (@switch.TrackColor is not null)
|
if (@switch.TrackColor is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.OnTrackColor = @switch.TrackColor;
|
var color = @switch.TrackColor.ToSKColor();
|
||||||
// Off track is a lighter/desaturated version
|
handler.PlatformView.OnTrackColor = color;
|
||||||
handler.PlatformView.OffTrackColor = @switch.TrackColor.WithAlpha(0.5f);
|
// Off track could be a lighter version
|
||||||
|
handler.PlatformView.OffTrackColor = color.WithAlpha(128);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ public partial class SwitchHandler : ViewHandler<ISwitch, SkiaSwitch>
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
if (@switch.ThumbColor is not null)
|
if (@switch.ThumbColor is not null)
|
||||||
handler.PlatformView.ThumbColor = @switch.ThumbColor;
|
handler.PlatformView.ThumbColor = @switch.ThumbColor.ToSKColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapBackground(SwitchHandler handler, ISwitch @switch)
|
public static void MapBackground(SwitchHandler handler, ISwitch @switch)
|
||||||
@@ -93,14 +93,7 @@ public partial class SwitchHandler : ViewHandler<ISwitch, SkiaSwitch>
|
|||||||
|
|
||||||
if (@switch.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (@switch.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
// Background color for the switch container (not the track)
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
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,10 +1,8 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
using Microsoft.Maui.Controls;
|
|
||||||
using Microsoft.Maui.Handlers;
|
using Microsoft.Maui.Handlers;
|
||||||
using Microsoft.Maui.Graphics;
|
using Microsoft.Maui.Graphics;
|
||||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||||
@@ -15,14 +13,8 @@ namespace Microsoft.Maui.Platform.Linux.Handlers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class TabbedPageHandler : ViewHandler<ITabbedView, SkiaTabbedPage>
|
public partial class TabbedPageHandler : ViewHandler<ITabbedView, SkiaTabbedPage>
|
||||||
{
|
{
|
||||||
private bool _isUpdatingSelection;
|
|
||||||
|
|
||||||
public static IPropertyMapper<ITabbedView, TabbedPageHandler> Mapper = new PropertyMapper<ITabbedView, TabbedPageHandler>(ViewHandler.ViewMapper)
|
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)
|
public static CommandMapper<ITabbedView, TabbedPageHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||||
@@ -47,9 +39,6 @@ public partial class TabbedPageHandler : ViewHandler<ITabbedView, SkiaTabbedPage
|
|||||||
{
|
{
|
||||||
base.ConnectHandler(platformView);
|
base.ConnectHandler(platformView);
|
||||||
platformView.SelectedIndexChanged += OnSelectedIndexChanged;
|
platformView.SelectedIndexChanged += OnSelectedIndexChanged;
|
||||||
|
|
||||||
// Sync initial tabs
|
|
||||||
SyncTabs();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void DisconnectHandler(SkiaTabbedPage platformView)
|
protected override void DisconnectHandler(SkiaTabbedPage platformView)
|
||||||
@@ -61,104 +50,6 @@ public partial class TabbedPageHandler : ViewHandler<ITabbedView, SkiaTabbedPage
|
|||||||
|
|
||||||
private void OnSelectedIndexChanged(object? sender, EventArgs e)
|
private void OnSelectedIndexChanged(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (VirtualView is null || PlatformView is null || _isUpdatingSelection) return;
|
// Notify the virtual view of selection change
|
||||||
|
|
||||||
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,7 +21,6 @@ public partial class TimePickerHandler : ViewHandler<ITimePicker, SkiaTimePicker
|
|||||||
[nameof(ITimePicker.Format)] = MapFormat,
|
[nameof(ITimePicker.Format)] = MapFormat,
|
||||||
[nameof(ITimePicker.TextColor)] = MapTextColor,
|
[nameof(ITimePicker.TextColor)] = MapTextColor,
|
||||||
[nameof(ITimePicker.CharacterSpacing)] = MapCharacterSpacing,
|
[nameof(ITimePicker.CharacterSpacing)] = MapCharacterSpacing,
|
||||||
[nameof(ITextStyle.Font)] = MapFont,
|
|
||||||
[nameof(IView.Background)] = MapBackground,
|
[nameof(IView.Background)] = MapBackground,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -48,16 +47,6 @@ public partial class TimePickerHandler : ViewHandler<ITimePicker, SkiaTimePicker
|
|||||||
{
|
{
|
||||||
base.ConnectHandler(platformView);
|
base.ConnectHandler(platformView);
|
||||||
platformView.TimeSelected += OnTimeSelected;
|
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)
|
protected override void DisconnectHandler(SkiaTimePicker platformView)
|
||||||
@@ -66,11 +55,11 @@ public partial class TimePickerHandler : ViewHandler<ITimePicker, SkiaTimePicker
|
|||||||
base.DisconnectHandler(platformView);
|
base.DisconnectHandler(platformView);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTimeSelected(object? sender, TimeChangedEventArgs e)
|
private void OnTimeSelected(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (VirtualView is null || PlatformView is null) return;
|
if (VirtualView is null || PlatformView is null) return;
|
||||||
|
|
||||||
VirtualView.Time = e.NewTime;
|
VirtualView.Time = PlatformView.Time;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapTime(TimePickerHandler handler, ITimePicker timePicker)
|
public static void MapTime(TimePickerHandler handler, ITimePicker timePicker)
|
||||||
@@ -90,32 +79,13 @@ public partial class TimePickerHandler : ViewHandler<ITimePicker, SkiaTimePicker
|
|||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
if (timePicker.TextColor is not null)
|
if (timePicker.TextColor is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.TextColor = timePicker.TextColor;
|
handler.PlatformView.TextColor = timePicker.TextColor.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapCharacterSpacing(TimePickerHandler handler, ITimePicker timePicker)
|
public static void MapCharacterSpacing(TimePickerHandler handler, ITimePicker timePicker)
|
||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
// Character spacing would require custom text rendering
|
||||||
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)
|
public static void MapBackground(TimePickerHandler handler, ITimePicker timePicker)
|
||||||
@@ -124,7 +94,7 @@ public partial class TimePickerHandler : ViewHandler<ITimePicker, SkiaTimePicker
|
|||||||
|
|
||||||
if (timePicker.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
if (timePicker.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||||
{
|
{
|
||||||
handler.PlatformView.BackgroundColor = solidPaint.Color;
|
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,207 +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 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,7 +15,6 @@ public partial class WebViewHandler : ViewHandler<IWebView, SkiaWebView>
|
|||||||
public static IPropertyMapper<IWebView, WebViewHandler> Mapper = new PropertyMapper<IWebView, WebViewHandler>(ViewHandler.ViewMapper)
|
public static IPropertyMapper<IWebView, WebViewHandler> Mapper = new PropertyMapper<IWebView, WebViewHandler>(ViewHandler.ViewMapper)
|
||||||
{
|
{
|
||||||
[nameof(IWebView.Source)] = MapSource,
|
[nameof(IWebView.Source)] = MapSource,
|
||||||
[nameof(IWebView.UserAgent)] = MapUserAgent,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static CommandMapper<IWebView, WebViewHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
public static CommandMapper<IWebView, WebViewHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||||
@@ -23,8 +22,6 @@ public partial class WebViewHandler : ViewHandler<IWebView, SkiaWebView>
|
|||||||
[nameof(IWebView.GoBack)] = MapGoBack,
|
[nameof(IWebView.GoBack)] = MapGoBack,
|
||||||
[nameof(IWebView.GoForward)] = MapGoForward,
|
[nameof(IWebView.GoForward)] = MapGoForward,
|
||||||
[nameof(IWebView.Reload)] = MapReload,
|
[nameof(IWebView.Reload)] = MapReload,
|
||||||
[nameof(IWebView.Eval)] = MapEval,
|
|
||||||
[nameof(IWebView.EvaluateJavaScriptAsync)] = MapEvaluateJavaScriptAsync,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public WebViewHandler() : base(Mapper, CommandMapper)
|
public WebViewHandler() : base(Mapper, CommandMapper)
|
||||||
@@ -57,63 +54,29 @@ public partial class WebViewHandler : ViewHandler<IWebView, SkiaWebView>
|
|||||||
base.DisconnectHandler(platformView);
|
base.DisconnectHandler(platformView);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnNavigating(object? sender, Microsoft.Maui.Platform.WebNavigatingEventArgs e)
|
private void OnNavigating(object? sender, WebNavigatingEventArgs e)
|
||||||
{
|
{
|
||||||
IWebView virtualView = VirtualView;
|
// Forward to virtual view if needed
|
||||||
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, Microsoft.Maui.Platform.WebNavigatedEventArgs e)
|
private void OnNavigated(object? sender, WebNavigatedEventArgs e)
|
||||||
{
|
{
|
||||||
IWebView virtualView = VirtualView;
|
// Forward to virtual view if needed
|
||||||
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)
|
public static void MapSource(WebViewHandler handler, IWebView webView)
|
||||||
{
|
{
|
||||||
Console.WriteLine("[WebViewHandler] MapSource called");
|
if (handler.PlatformView == null) return;
|
||||||
if (handler.PlatformView == null)
|
|
||||||
{
|
|
||||||
Console.WriteLine("[WebViewHandler] PlatformView is null!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var source = webView.Source;
|
var source = webView.Source;
|
||||||
Console.WriteLine($"[WebViewHandler] Source type: {source?.GetType().Name ?? "null"}");
|
|
||||||
|
|
||||||
if (source is UrlWebViewSource urlSource)
|
if (source is UrlWebViewSource urlSource)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[WebViewHandler] Loading URL: {urlSource.Url}");
|
|
||||||
handler.PlatformView.Source = urlSource.Url ?? "";
|
handler.PlatformView.Source = urlSource.Url ?? "";
|
||||||
}
|
}
|
||||||
else if (source is HtmlWebViewSource htmlSource)
|
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 ?? "";
|
handler.PlatformView.Html = htmlSource.Html ?? "";
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine("[WebViewHandler] Unknown source type or null");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void MapGoBack(WebViewHandler handler, IWebView webView, object? args)
|
public static void MapGoBack(WebViewHandler handler, IWebView webView, object? args)
|
||||||
@@ -130,66 +93,4 @@ public partial class WebViewHandler : ViewHandler<IWebView, SkiaWebView>
|
|||||||
{
|
{
|
||||||
handler.PlatformView?.Reload();
|
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,20 +81,13 @@ public partial class WindowHandler : ElementHandler<IWindow, SkiaWindow>
|
|||||||
|
|
||||||
public static void MapContent(WindowHandler handler, IWindow window)
|
public static void MapContent(WindowHandler handler, IWindow window)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine($"[WindowHandler] MapContent - PlatformView={handler.PlatformView != null}");
|
|
||||||
if (handler.PlatformView is null) return;
|
if (handler.PlatformView is null) return;
|
||||||
|
|
||||||
var content = window.Content;
|
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)
|
if (content?.Handler?.PlatformView is SkiaView skiaContent)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine($"[WindowHandler] MapContent - setting SkiaView content: {skiaContent.GetType().Name}");
|
|
||||||
handler.PlatformView.Content = skiaContent;
|
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)
|
public static void MapX(WindowHandler handler, IWindow window)
|
||||||
@@ -184,8 +177,8 @@ public class SkiaWindow
|
|||||||
// Draw main content
|
// Draw main content
|
||||||
if (_content != null)
|
if (_content != null)
|
||||||
{
|
{
|
||||||
_content.Measure(new Size(_width, _height));
|
_content.Measure(new SKSize(_width, _height));
|
||||||
_content.Arrange(new Rect(0, 0, _width, _height));
|
_content.Arrange(new SKRect(0, 0, _width, _height));
|
||||||
_content.Draw(canvas);
|
_content.Draw(canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +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.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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +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.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +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.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,41 +7,37 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
|
|||||||
using Microsoft.Maui.ApplicationModel;
|
using Microsoft.Maui.ApplicationModel;
|
||||||
using Microsoft.Maui.ApplicationModel.Communication;
|
using Microsoft.Maui.ApplicationModel.Communication;
|
||||||
using Microsoft.Maui.ApplicationModel.DataTransfer;
|
using Microsoft.Maui.ApplicationModel.DataTransfer;
|
||||||
using Microsoft.Maui.Controls;
|
|
||||||
using Microsoft.Maui.Devices;
|
|
||||||
using Microsoft.Maui.Dispatching;
|
|
||||||
using Microsoft.Maui.Hosting;
|
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.Platform.Linux.Services;
|
||||||
|
using Microsoft.Maui.Platform.Linux.Converters;
|
||||||
using Microsoft.Maui.Storage;
|
using Microsoft.Maui.Storage;
|
||||||
|
using Microsoft.Maui.Platform.Linux.Handlers;
|
||||||
|
using Microsoft.Maui.Controls;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Hosting;
|
namespace Microsoft.Maui.Platform.Linux.Hosting;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for configuring MAUI applications for Linux.
|
||||||
|
/// </summary>
|
||||||
public static class LinuxMauiAppBuilderExtensions
|
public static class LinuxMauiAppBuilderExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Configures the MAUI application to run on Linux.
|
||||||
|
/// </summary>
|
||||||
public static MauiAppBuilder UseLinux(this MauiAppBuilder builder)
|
public static MauiAppBuilder UseLinux(this MauiAppBuilder builder)
|
||||||
{
|
{
|
||||||
return builder.UseLinux(null);
|
return builder.UseLinux(configure: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures the MAUI application to run on Linux with options.
|
||||||
|
/// </summary>
|
||||||
public static MauiAppBuilder UseLinux(this MauiAppBuilder builder, Action<LinuxApplicationOptions>? configure)
|
public static MauiAppBuilder UseLinux(this MauiAppBuilder builder, Action<LinuxApplicationOptions>? configure)
|
||||||
{
|
{
|
||||||
var options = new LinuxApplicationOptions();
|
var options = new LinuxApplicationOptions();
|
||||||
configure?.Invoke(options);
|
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
|
// Register platform services
|
||||||
builder.Services.TryAddSingleton<ILauncher, LauncherService>();
|
builder.Services.TryAddSingleton<ILauncher, LauncherService>();
|
||||||
builder.Services.TryAddSingleton<IPreferences, PreferencesService>();
|
builder.Services.TryAddSingleton<IPreferences, PreferencesService>();
|
||||||
@@ -54,29 +50,6 @@ public static class LinuxMauiAppBuilderExtensions
|
|||||||
builder.Services.TryAddSingleton<IBrowser, BrowserService>();
|
builder.Services.TryAddSingleton<IBrowser, BrowserService>();
|
||||||
builder.Services.TryAddSingleton<IEmail, EmailService>();
|
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
|
// Register type converters for XAML support
|
||||||
RegisterTypeConverters();
|
RegisterTypeConverters();
|
||||||
|
|
||||||
@@ -104,12 +77,11 @@ public static class LinuxMauiAppBuilderExtensions
|
|||||||
handlers.AddHandler<VerticalStackLayout, StackLayoutHandler>();
|
handlers.AddHandler<VerticalStackLayout, StackLayoutHandler>();
|
||||||
handlers.AddHandler<HorizontalStackLayout, StackLayoutHandler>();
|
handlers.AddHandler<HorizontalStackLayout, StackLayoutHandler>();
|
||||||
handlers.AddHandler<AbsoluteLayout, LayoutHandler>();
|
handlers.AddHandler<AbsoluteLayout, LayoutHandler>();
|
||||||
handlers.AddHandler<FlexLayout, FlexLayoutHandler>();
|
handlers.AddHandler<FlexLayout, LayoutHandler>();
|
||||||
handlers.AddHandler<ScrollView, ScrollViewHandler>();
|
handlers.AddHandler<ScrollView, ScrollViewHandler>();
|
||||||
handlers.AddHandler<Frame, FrameHandler>();
|
handlers.AddHandler<Frame, FrameHandler>();
|
||||||
handlers.AddHandler<Border, BorderHandler>();
|
handlers.AddHandler<Border, BorderHandler>();
|
||||||
handlers.AddHandler<ContentView, BorderHandler>();
|
handlers.AddHandler<ContentView, BorderHandler>();
|
||||||
handlers.AddHandler<RefreshView, RefreshViewHandler>();
|
|
||||||
|
|
||||||
// Picker controls
|
// Picker controls
|
||||||
handlers.AddHandler<Picker, PickerHandler>();
|
handlers.AddHandler<Picker, PickerHandler>();
|
||||||
@@ -126,15 +98,9 @@ public static class LinuxMauiAppBuilderExtensions
|
|||||||
handlers.AddHandler<ImageButton, ImageButtonHandler>();
|
handlers.AddHandler<ImageButton, ImageButtonHandler>();
|
||||||
handlers.AddHandler<GraphicsView, GraphicsViewHandler>();
|
handlers.AddHandler<GraphicsView, GraphicsViewHandler>();
|
||||||
|
|
||||||
// Web - use GtkWebViewHandler
|
|
||||||
handlers.AddHandler<WebView, GtkWebViewHandler>();
|
|
||||||
|
|
||||||
// Collection Views
|
// Collection Views
|
||||||
handlers.AddHandler<CollectionView, CollectionViewHandler>();
|
handlers.AddHandler<CollectionView, CollectionViewHandler>();
|
||||||
handlers.AddHandler<ListView, CollectionViewHandler>();
|
handlers.AddHandler<ListView, CollectionViewHandler>();
|
||||||
handlers.AddHandler<CarouselView, CarouselViewHandler>();
|
|
||||||
handlers.AddHandler<IndicatorView, IndicatorViewHandler>();
|
|
||||||
handlers.AddHandler<SwipeView, SwipeViewHandler>();
|
|
||||||
|
|
||||||
// Pages & Navigation
|
// Pages & Navigation
|
||||||
handlers.AddHandler<Page, PageHandler>();
|
handlers.AddHandler<Page, PageHandler>();
|
||||||
@@ -155,11 +121,33 @@ public static class LinuxMauiAppBuilderExtensions
|
|||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers custom type converters for Linux platform.
|
||||||
|
/// </summary>
|
||||||
private static void RegisterTypeConverters()
|
private static void RegisterTypeConverters()
|
||||||
{
|
{
|
||||||
|
// Register SkiaSharp type converters for XAML styling support
|
||||||
TypeDescriptor.AddAttributes(typeof(SKColor), new TypeConverterAttribute(typeof(SKColorTypeConverter)));
|
TypeDescriptor.AddAttributes(typeof(SKColor), new TypeConverterAttribute(typeof(SKColorTypeConverter)));
|
||||||
TypeDescriptor.AddAttributes(typeof(SKRect), new TypeConverterAttribute(typeof(SKRectTypeConverter)));
|
TypeDescriptor.AddAttributes(typeof(SKRect), new TypeConverterAttribute(typeof(SKRectTypeConverter)));
|
||||||
TypeDescriptor.AddAttributes(typeof(SKSize), new TypeConverterAttribute(typeof(SKSizeTypeConverter)));
|
TypeDescriptor.AddAttributes(typeof(SKSize), new TypeConverterAttribute(typeof(SKSizeTypeConverter)));
|
||||||
TypeDescriptor.AddAttributes(typeof(SKPoint), new TypeConverterAttribute(typeof(SKPointTypeConverter)));
|
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,10 +4,15 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Maui.Animations;
|
using Microsoft.Maui.Animations;
|
||||||
using Microsoft.Maui.Dispatching;
|
using Microsoft.Maui.Dispatching;
|
||||||
using Microsoft.Maui.Platform.Linux.Dispatching;
|
using Microsoft.Maui.Platform;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Hosting;
|
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
|
public class LinuxMauiContext : IMauiContext
|
||||||
{
|
{
|
||||||
private readonly IServiceProvider _services;
|
private readonly IServiceProvider _services;
|
||||||
@@ -16,12 +21,27 @@ public class LinuxMauiContext : IMauiContext
|
|||||||
private IAnimationManager? _animationManager;
|
private IAnimationManager? _animationManager;
|
||||||
private IDispatcher? _dispatcher;
|
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;
|
public IServiceProvider Services => _services;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public IMauiHandlersFactory Handlers => _handlers;
|
public IMauiHandlersFactory Handlers => _handlers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Linux application instance.
|
||||||
|
/// </summary>
|
||||||
public LinuxApplication LinuxApp => _linuxApp;
|
public LinuxApplication LinuxApp => _linuxApp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the animation manager.
|
||||||
|
/// </summary>
|
||||||
public IAnimationManager AnimationManager
|
public IAnimationManager AnimationManager
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -32,6 +52,9 @@ public class LinuxMauiContext : IMauiContext
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the dispatcher for UI thread operations.
|
||||||
|
/// </summary>
|
||||||
public IDispatcher Dispatcher
|
public IDispatcher Dispatcher
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -41,11 +64,236 @@ public class LinuxMauiContext : IMauiContext
|
|||||||
return _dispatcher;
|
return _dispatcher;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public LinuxMauiContext(IServiceProvider services, LinuxApplication linuxApp)
|
/// <summary>
|
||||||
|
/// Scoped MAUI context for a specific window or view hierarchy.
|
||||||
|
/// </summary>
|
||||||
|
public class ScopedLinuxMauiContext : IMauiContext
|
||||||
|
{
|
||||||
|
private readonly LinuxMauiContext _parent;
|
||||||
|
|
||||||
|
public ScopedLinuxMauiContext(LinuxMauiContext parent)
|
||||||
{
|
{
|
||||||
_services = services ?? throw new ArgumentNullException(nameof(services));
|
_parent = parent ?? throw new ArgumentNullException(nameof(parent));
|
||||||
_linuxApp = linuxApp ?? throw new ArgumentNullException(nameof(linuxApp));
|
}
|
||||||
_handlers = services.GetRequiredService<IMauiHandlersFactory>();
|
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,9 @@
|
|||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Maui.Controls;
|
|
||||||
using Microsoft.Maui.Controls.Hosting;
|
|
||||||
using Microsoft.Maui.Graphics;
|
|
||||||
using Microsoft.Maui.Hosting;
|
using Microsoft.Maui.Hosting;
|
||||||
using Microsoft.Maui.Platform.Linux.Services;
|
using Microsoft.Maui.Controls;
|
||||||
|
using Microsoft.Maui.Platform;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.Maui.Platform.Linux.Hosting;
|
namespace Microsoft.Maui.Platform.Linux.Hosting;
|
||||||
@@ -46,10 +44,6 @@ public static class LinuxProgramHost
|
|||||||
?? new LinuxApplicationOptions();
|
?? new LinuxApplicationOptions();
|
||||||
ParseCommandLineOptions(args, options);
|
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
|
// Create Linux application
|
||||||
using var linuxApp = new LinuxApplication();
|
using var linuxApp = new LinuxApplication();
|
||||||
linuxApp.Initialize(options);
|
linuxApp.Initialize(options);
|
||||||
@@ -192,33 +186,33 @@ public static class LinuxProgramHost
|
|||||||
{
|
{
|
||||||
Orientation = StackOrientation.Vertical,
|
Orientation = StackOrientation.Vertical,
|
||||||
Spacing = 15,
|
Spacing = 15,
|
||||||
BackgroundColor = Color.FromRgb(0xF5, 0xF5, 0xF5)
|
BackgroundColor = new SKColor(0xF5, 0xF5, 0xF5)
|
||||||
};
|
};
|
||||||
root.Padding = new Thickness(20, 20, 20, 20);
|
root.Padding = new SKRect(20, 20, 20, 20);
|
||||||
|
|
||||||
// ========== TITLE ==========
|
// ========== TITLE ==========
|
||||||
root.AddChild(new SkiaLabel
|
root.AddChild(new SkiaLabel
|
||||||
{
|
{
|
||||||
Text = "OpenMaui Linux Control Demo",
|
Text = "OpenMaui Linux Control Demo",
|
||||||
FontSize = 28,
|
FontSize = 28,
|
||||||
TextColor = Color.FromRgb(0x1A, 0x23, 0x7E),
|
TextColor = new SKColor(0x1A, 0x23, 0x7E),
|
||||||
FontAttributes = FontAttributes.Bold
|
IsBold = true
|
||||||
});
|
});
|
||||||
root.AddChild(new SkiaLabel
|
root.AddChild(new SkiaLabel
|
||||||
{
|
{
|
||||||
Text = "All controls rendered using SkiaSharp on X11",
|
Text = "All controls rendered using SkiaSharp on X11",
|
||||||
FontSize = 14,
|
FontSize = 14,
|
||||||
TextColor = Colors.Gray
|
TextColor = SKColors.Gray
|
||||||
});
|
});
|
||||||
|
|
||||||
// ========== LABELS SECTION ==========
|
// ========== LABELS SECTION ==========
|
||||||
root.AddChild(CreateSeparator());
|
root.AddChild(CreateSeparator());
|
||||||
root.AddChild(CreateSectionHeader("Labels"));
|
root.AddChild(CreateSectionHeader("Labels"));
|
||||||
var labelSection = new SkiaStackLayout { Orientation = StackOrientation.Vertical, Spacing = 5 };
|
var labelSection = new SkiaStackLayout { Orientation = StackOrientation.Vertical, Spacing = 5 };
|
||||||
labelSection.AddChild(new SkiaLabel { Text = "Normal Label", FontSize = 16, TextColor = Colors.Black });
|
labelSection.AddChild(new SkiaLabel { Text = "Normal Label", FontSize = 16, TextColor = SKColors.Black });
|
||||||
labelSection.AddChild(new SkiaLabel { Text = "Bold Label", FontSize = 16, TextColor = Colors.Black, FontAttributes = FontAttributes.Bold });
|
labelSection.AddChild(new SkiaLabel { Text = "Bold Label", FontSize = 16, TextColor = SKColors.Black, IsBold = true });
|
||||||
labelSection.AddChild(new SkiaLabel { Text = "Italic Label", FontSize = 16, TextColor = Colors.Gray, FontAttributes = FontAttributes.Italic });
|
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 = Color.FromRgb(0xE9, 0x1E, 0x63) });
|
labelSection.AddChild(new SkiaLabel { Text = "Colored Label (Pink)", FontSize = 16, TextColor = new SKColor(0xE9, 0x1E, 0x63) });
|
||||||
root.AddChild(labelSection);
|
root.AddChild(labelSection);
|
||||||
|
|
||||||
// ========== BUTTONS SECTION ==========
|
// ========== BUTTONS SECTION ==========
|
||||||
@@ -227,20 +221,20 @@ public static class LinuxProgramHost
|
|||||||
var buttonSection = new SkiaStackLayout { Orientation = StackOrientation.Horizontal, Spacing = 10 };
|
var buttonSection = new SkiaStackLayout { Orientation = StackOrientation.Horizontal, Spacing = 10 };
|
||||||
|
|
||||||
var btnPrimary = new SkiaButton { Text = "Primary", FontSize = 14 };
|
var btnPrimary = new SkiaButton { Text = "Primary", FontSize = 14 };
|
||||||
btnPrimary.BackgroundColor = Color.FromRgb(0x21, 0x96, 0xF3);
|
btnPrimary.BackgroundColor = new SKColor(0x21, 0x96, 0xF3);
|
||||||
btnPrimary.TextColor = Colors.White;
|
btnPrimary.TextColor = SKColors.White;
|
||||||
var clickCount = 0;
|
var clickCount = 0;
|
||||||
btnPrimary.Clicked += (s, e) => { clickCount++; btnPrimary.Text = $"Clicked {clickCount}x"; };
|
btnPrimary.Clicked += (s, e) => { clickCount++; btnPrimary.Text = $"Clicked {clickCount}x"; };
|
||||||
buttonSection.AddChild(btnPrimary);
|
buttonSection.AddChild(btnPrimary);
|
||||||
|
|
||||||
var btnSuccess = new SkiaButton { Text = "Success", FontSize = 14 };
|
var btnSuccess = new SkiaButton { Text = "Success", FontSize = 14 };
|
||||||
btnSuccess.BackgroundColor = Color.FromRgb(0x4C, 0xAF, 0x50);
|
btnSuccess.BackgroundColor = new SKColor(0x4C, 0xAF, 0x50);
|
||||||
btnSuccess.TextColor = Colors.White;
|
btnSuccess.TextColor = SKColors.White;
|
||||||
buttonSection.AddChild(btnSuccess);
|
buttonSection.AddChild(btnSuccess);
|
||||||
|
|
||||||
var btnDanger = new SkiaButton { Text = "Danger", FontSize = 14 };
|
var btnDanger = new SkiaButton { Text = "Danger", FontSize = 14 };
|
||||||
btnDanger.BackgroundColor = Color.FromRgb(0xF4, 0x43, 0x36);
|
btnDanger.BackgroundColor = new SKColor(0xF4, 0x43, 0x36);
|
||||||
btnDanger.TextColor = Colors.White;
|
btnDanger.TextColor = SKColors.White;
|
||||||
buttonSection.AddChild(btnDanger);
|
buttonSection.AddChild(btnDanger);
|
||||||
|
|
||||||
root.AddChild(buttonSection);
|
root.AddChild(buttonSection);
|
||||||
@@ -255,7 +249,7 @@ public static class LinuxProgramHost
|
|||||||
root.AddChild(CreateSeparator());
|
root.AddChild(CreateSeparator());
|
||||||
root.AddChild(CreateSectionHeader("SearchBar"));
|
root.AddChild(CreateSectionHeader("SearchBar"));
|
||||||
var searchBar = new SkiaSearchBar { Placeholder = "Search for items..." };
|
var searchBar = new SkiaSearchBar { Placeholder = "Search for items..." };
|
||||||
var searchResultLabel = new SkiaLabel { Text = "", FontSize = 12, TextColor = Colors.Gray };
|
var searchResultLabel = new SkiaLabel { Text = "", FontSize = 12, TextColor = SKColors.Gray };
|
||||||
searchBar.TextChanged += (s, e) => searchResultLabel.Text = $"Searching: {e.NewTextValue}";
|
searchBar.TextChanged += (s, e) => searchResultLabel.Text = $"Searching: {e.NewTextValue}";
|
||||||
searchBar.SearchButtonPressed += (s, e) => searchResultLabel.Text = $"Search submitted: {searchBar.Text}";
|
searchBar.SearchButtonPressed += (s, e) => searchResultLabel.Text = $"Search submitted: {searchBar.Text}";
|
||||||
root.AddChild(searchBar);
|
root.AddChild(searchBar);
|
||||||
@@ -268,7 +262,7 @@ public static class LinuxProgramHost
|
|||||||
{
|
{
|
||||||
Placeholder = "Enter multiple lines of text...",
|
Placeholder = "Enter multiple lines of text...",
|
||||||
FontSize = 14,
|
FontSize = 14,
|
||||||
BackgroundColor = Colors.White
|
BackgroundColor = SKColors.White
|
||||||
};
|
};
|
||||||
root.AddChild(editor);
|
root.AddChild(editor);
|
||||||
|
|
||||||
@@ -330,7 +324,7 @@ public static class LinuxProgramHost
|
|||||||
root.AddChild(CreateSectionHeader("ProgressBar"));
|
root.AddChild(CreateSectionHeader("ProgressBar"));
|
||||||
var progress = new SkiaProgressBar { Progress = 0.7f };
|
var progress = new SkiaProgressBar { Progress = 0.7f };
|
||||||
root.AddChild(progress);
|
root.AddChild(progress);
|
||||||
root.AddChild(new SkiaLabel { Text = "70% Complete", FontSize = 12, TextColor = Colors.Gray });
|
root.AddChild(new SkiaLabel { Text = "70% Complete", FontSize = 12, TextColor = SKColors.Gray });
|
||||||
|
|
||||||
// ========== ACTIVITYINDICATOR SECTION ==========
|
// ========== ACTIVITYINDICATOR SECTION ==========
|
||||||
root.AddChild(CreateSeparator());
|
root.AddChild(CreateSeparator());
|
||||||
@@ -338,7 +332,7 @@ public static class LinuxProgramHost
|
|||||||
var activitySection = new SkiaStackLayout { Orientation = StackOrientation.Horizontal, Spacing = 10 };
|
var activitySection = new SkiaStackLayout { Orientation = StackOrientation.Horizontal, Spacing = 10 };
|
||||||
var activity = new SkiaActivityIndicator { IsRunning = true };
|
var activity = new SkiaActivityIndicator { IsRunning = true };
|
||||||
activitySection.AddChild(activity);
|
activitySection.AddChild(activity);
|
||||||
activitySection.AddChild(new SkiaLabel { Text = "Loading...", FontSize = 14, TextColor = Colors.Gray });
|
activitySection.AddChild(new SkiaLabel { Text = "Loading...", FontSize = 14, TextColor = SKColors.Gray });
|
||||||
root.AddChild(activitySection);
|
root.AddChild(activitySection);
|
||||||
|
|
||||||
// ========== PICKER SECTION ==========
|
// ========== PICKER SECTION ==========
|
||||||
@@ -346,7 +340,7 @@ public static class LinuxProgramHost
|
|||||||
root.AddChild(CreateSectionHeader("Picker (Dropdown)"));
|
root.AddChild(CreateSectionHeader("Picker (Dropdown)"));
|
||||||
var picker = new SkiaPicker { Title = "Select an item" };
|
var picker = new SkiaPicker { Title = "Select an item" };
|
||||||
picker.SetItems(new[] { "Apple", "Banana", "Cherry", "Date", "Elderberry", "Fig", "Grape" });
|
picker.SetItems(new[] { "Apple", "Banana", "Cherry", "Date", "Elderberry", "Fig", "Grape" });
|
||||||
var pickerLabel = new SkiaLabel { Text = "Selected: (none)", FontSize = 12, TextColor = Colors.Gray };
|
var pickerLabel = new SkiaLabel { Text = "Selected: (none)", FontSize = 12, TextColor = SKColors.Gray };
|
||||||
picker.SelectedIndexChanged += (s, e) => pickerLabel.Text = $"Selected: {picker.SelectedItem}";
|
picker.SelectedIndexChanged += (s, e) => pickerLabel.Text = $"Selected: {picker.SelectedItem}";
|
||||||
root.AddChild(picker);
|
root.AddChild(picker);
|
||||||
root.AddChild(pickerLabel);
|
root.AddChild(pickerLabel);
|
||||||
@@ -355,7 +349,7 @@ public static class LinuxProgramHost
|
|||||||
root.AddChild(CreateSeparator());
|
root.AddChild(CreateSeparator());
|
||||||
root.AddChild(CreateSectionHeader("DatePicker"));
|
root.AddChild(CreateSectionHeader("DatePicker"));
|
||||||
var datePicker = new SkiaDatePicker { Date = DateTime.Today };
|
var datePicker = new SkiaDatePicker { Date = DateTime.Today };
|
||||||
var dateLabel = new SkiaLabel { Text = $"Date: {DateTime.Today:d}", FontSize = 12, TextColor = Colors.Gray };
|
var dateLabel = new SkiaLabel { Text = $"Date: {DateTime.Today:d}", FontSize = 12, TextColor = SKColors.Gray };
|
||||||
datePicker.DateSelected += (s, e) => dateLabel.Text = $"Date: {datePicker.Date:d}";
|
datePicker.DateSelected += (s, e) => dateLabel.Text = $"Date: {datePicker.Date:d}";
|
||||||
root.AddChild(datePicker);
|
root.AddChild(datePicker);
|
||||||
root.AddChild(dateLabel);
|
root.AddChild(dateLabel);
|
||||||
@@ -364,7 +358,7 @@ public static class LinuxProgramHost
|
|||||||
root.AddChild(CreateSeparator());
|
root.AddChild(CreateSeparator());
|
||||||
root.AddChild(CreateSectionHeader("TimePicker"));
|
root.AddChild(CreateSectionHeader("TimePicker"));
|
||||||
var timePicker = new SkiaTimePicker();
|
var timePicker = new SkiaTimePicker();
|
||||||
var timeLabel = new SkiaLabel { Text = $"Time: {DateTime.Now:t}", FontSize = 12, TextColor = Colors.Gray };
|
var timeLabel = new SkiaLabel { Text = $"Time: {DateTime.Now:t}", FontSize = 12, TextColor = SKColors.Gray };
|
||||||
timePicker.TimeSelected += (s, e) => timeLabel.Text = $"Time: {DateTime.Today.Add(timePicker.Time):t}";
|
timePicker.TimeSelected += (s, e) => timeLabel.Text = $"Time: {DateTime.Today.Add(timePicker.Time):t}";
|
||||||
root.AddChild(timePicker);
|
root.AddChild(timePicker);
|
||||||
root.AddChild(timeLabel);
|
root.AddChild(timeLabel);
|
||||||
@@ -376,18 +370,18 @@ public static class LinuxProgramHost
|
|||||||
{
|
{
|
||||||
CornerRadius = 8,
|
CornerRadius = 8,
|
||||||
StrokeThickness = 2,
|
StrokeThickness = 2,
|
||||||
Stroke = Color.FromRgb(0x21, 0x96, 0xF3),
|
Stroke = new SKColor(0x21, 0x96, 0xF3),
|
||||||
BackgroundColor = Color.FromRgb(0xE3, 0xF2, 0xFD)
|
BackgroundColor = new SKColor(0xE3, 0xF2, 0xFD)
|
||||||
};
|
};
|
||||||
border.SetPadding(15);
|
border.SetPadding(15);
|
||||||
border.AddChild(new SkiaLabel { Text = "Content inside a styled Border", FontSize = 14, TextColor = Color.FromRgb(0x1A, 0x23, 0x7E) });
|
border.AddChild(new SkiaLabel { Text = "Content inside a styled Border", FontSize = 14, TextColor = new SKColor(0x1A, 0x23, 0x7E) });
|
||||||
root.AddChild(border);
|
root.AddChild(border);
|
||||||
|
|
||||||
// ========== FRAME SECTION ==========
|
// ========== FRAME SECTION ==========
|
||||||
root.AddChild(CreateSeparator());
|
root.AddChild(CreateSeparator());
|
||||||
root.AddChild(CreateSectionHeader("Frame (with shadow)"));
|
root.AddChild(CreateSectionHeader("Frame (with shadow)"));
|
||||||
var frame = new SkiaFrame();
|
var frame = new SkiaFrame();
|
||||||
frame.BackgroundColor = Colors.White;
|
frame.BackgroundColor = SKColors.White;
|
||||||
frame.AddChild(new SkiaLabel { Text = "Content inside a Frame with shadow effect", FontSize = 14 });
|
frame.AddChild(new SkiaLabel { Text = "Content inside a Frame with shadow effect", FontSize = 14 });
|
||||||
root.AddChild(frame);
|
root.AddChild(frame);
|
||||||
|
|
||||||
@@ -401,7 +395,7 @@ public static class LinuxProgramHost
|
|||||||
Footer = "End of list"
|
Footer = "End of list"
|
||||||
};
|
};
|
||||||
collectionView.ItemsSource =(new object[] { "Apple", "Banana", "Cherry", "Date", "Elderberry", "Fig", "Grape", "Honeydew" });
|
collectionView.ItemsSource =(new object[] { "Apple", "Banana", "Cherry", "Date", "Elderberry", "Fig", "Grape", "Honeydew" });
|
||||||
var collectionLabel = new SkiaLabel { Text = "Selected: (none)", FontSize = 12, TextColor = Colors.Gray };
|
var collectionLabel = new SkiaLabel { Text = "Selected: (none)", FontSize = 12, TextColor = SKColors.Gray };
|
||||||
collectionView.SelectionChanged += (s, e) =>
|
collectionView.SelectionChanged += (s, e) =>
|
||||||
{
|
{
|
||||||
var selected = e.CurrentSelection.FirstOrDefault();
|
var selected = e.CurrentSelection.FirstOrDefault();
|
||||||
@@ -419,15 +413,18 @@ public static class LinuxProgramHost
|
|||||||
var imgBtn = new SkiaImageButton
|
var imgBtn = new SkiaImageButton
|
||||||
{
|
{
|
||||||
CornerRadius = 8,
|
CornerRadius = 8,
|
||||||
StrokeColor = Color.FromRgb(0x21, 0x96, 0xF3),
|
StrokeColor = new SKColor(0x21, 0x96, 0xF3),
|
||||||
StrokeThickness = 1,
|
StrokeThickness = 1,
|
||||||
ImageBackgroundColor = Color.FromRgb(0xE3, 0xF2, 0xFD),
|
BackgroundColor = new SKColor(0xE3, 0xF2, 0xFD),
|
||||||
Padding = new Thickness(10)
|
PaddingLeft = 10,
|
||||||
|
PaddingRight = 10,
|
||||||
|
PaddingTop = 10,
|
||||||
|
PaddingBottom = 10
|
||||||
};
|
};
|
||||||
// Generate a simple star icon bitmap
|
// Generate a simple star icon bitmap
|
||||||
var iconBitmap = CreateStarIcon(32, new SKColor(0x21, 0x96, 0xF3));
|
var iconBitmap = CreateStarIcon(32, new SKColor(0x21, 0x96, 0xF3));
|
||||||
imgBtn.Bitmap = iconBitmap;
|
imgBtn.Bitmap = iconBitmap;
|
||||||
var imgBtnLabel = new SkiaLabel { Text = "Click the star!", FontSize = 12, TextColor = Colors.Gray };
|
var imgBtnLabel = new SkiaLabel { Text = "Click the star!", FontSize = 12, TextColor = SKColors.Gray };
|
||||||
imgBtn.Clicked += (s, e) => imgBtnLabel.Text = "Star clicked!";
|
imgBtn.Clicked += (s, e) => imgBtnLabel.Text = "Star clicked!";
|
||||||
imageButtonSection.AddChild(imgBtn);
|
imageButtonSection.AddChild(imgBtn);
|
||||||
imageButtonSection.AddChild(imgBtnLabel);
|
imageButtonSection.AddChild(imgBtnLabel);
|
||||||
@@ -443,7 +440,7 @@ public static class LinuxProgramHost
|
|||||||
var sampleBitmap = CreateSampleImage(80, 60);
|
var sampleBitmap = CreateSampleImage(80, 60);
|
||||||
img.Bitmap = sampleBitmap;
|
img.Bitmap = sampleBitmap;
|
||||||
imageSection.AddChild(img);
|
imageSection.AddChild(img);
|
||||||
imageSection.AddChild(new SkiaLabel { Text = "Sample generated image", FontSize = 12, TextColor = Colors.Gray });
|
imageSection.AddChild(new SkiaLabel { Text = "Sample generated image", FontSize = 12, TextColor = SKColors.Gray });
|
||||||
root.AddChild(imageSection);
|
root.AddChild(imageSection);
|
||||||
|
|
||||||
// ========== FOOTER ==========
|
// ========== FOOTER ==========
|
||||||
@@ -452,14 +449,14 @@ public static class LinuxProgramHost
|
|||||||
{
|
{
|
||||||
Text = "All 25+ controls are interactive - try them all!",
|
Text = "All 25+ controls are interactive - try them all!",
|
||||||
FontSize = 16,
|
FontSize = 16,
|
||||||
TextColor = Color.FromRgb(0x4C, 0xAF, 0x50),
|
TextColor = new SKColor(0x4C, 0xAF, 0x50),
|
||||||
FontAttributes = FontAttributes.Bold
|
IsBold = true
|
||||||
});
|
});
|
||||||
root.AddChild(new SkiaLabel
|
root.AddChild(new SkiaLabel
|
||||||
{
|
{
|
||||||
Text = "Scroll down to see more controls",
|
Text = "Scroll down to see more controls",
|
||||||
FontSize = 12,
|
FontSize = 12,
|
||||||
TextColor = Colors.Gray
|
TextColor = SKColors.Gray
|
||||||
});
|
});
|
||||||
|
|
||||||
scroll.Content = root;
|
scroll.Content = root;
|
||||||
@@ -472,14 +469,14 @@ public static class LinuxProgramHost
|
|||||||
{
|
{
|
||||||
Text = text,
|
Text = text,
|
||||||
FontSize = 18,
|
FontSize = 18,
|
||||||
TextColor = Color.FromRgb(0x37, 0x47, 0x4F),
|
TextColor = new SKColor(0x37, 0x47, 0x4F),
|
||||||
FontAttributes = FontAttributes.Bold
|
IsBold = true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SkiaView CreateSeparator()
|
private static SkiaView CreateSeparator()
|
||||||
{
|
{
|
||||||
var sep = new SkiaLabel { Text = "", BackgroundColor = Color.FromRgb(0xE0, 0xE0, 0xE0), RequestedHeight = 1 };
|
var sep = new SkiaLabel { Text = "", BackgroundColor = new SKColor(0xE0, 0xE0, 0xE0), RequestedHeight = 1 };
|
||||||
return sep;
|
return sep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,47 +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.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,9 +1,7 @@
|
|||||||
// Licensed to the .NET Foundation under one or more agreements.
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
using System.Reflection;
|
|
||||||
using Microsoft.Maui.Controls;
|
using Microsoft.Maui.Controls;
|
||||||
using Microsoft.Maui.Graphics;
|
|
||||||
using Microsoft.Maui.Platform;
|
using Microsoft.Maui.Platform;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
|
|
||||||
@@ -90,19 +88,29 @@ public class LinuxViewRenderer
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Render the page through the proper handler system
|
// Render the page content
|
||||||
// This ensures all properties (including BackgroundColor via AppThemeBinding) are mapped
|
SkiaView? pageContent = null;
|
||||||
var skiaPage = CurrentRenderer.RenderPage(page);
|
if (page is ContentPage contentPage && contentPage.Content != null)
|
||||||
|
|
||||||
if (skiaPage == null)
|
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[PushPage] Failed to render page through handler");
|
pageContent = CurrentRenderer.RenderView(contentPage.Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageContent == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[PushPage] Failed to render page content");
|
||||||
return false;
|
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
|
// Push onto SkiaShell's navigation stack
|
||||||
CurrentSkiaShell.PushAsync(skiaPage, page.Title ?? "Detail");
|
CurrentSkiaShell.PushAsync(pageContent, page.Title ?? "Detail");
|
||||||
Console.WriteLine($"[PushPage] Successfully pushed page via handler system");
|
Console.WriteLine($"[PushPage] Successfully pushed page");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -154,11 +162,18 @@ public class LinuxViewRenderer
|
|||||||
page.Handler?.DisconnectHandler();
|
page.Handler?.DisconnectHandler();
|
||||||
var handler = page.ToHandler(_mauiContext);
|
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)
|
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;
|
return skiaPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,41 +198,9 @@ public class LinuxViewRenderer
|
|||||||
FlyoutBehavior.Locked => ShellFlyoutBehavior.Locked,
|
FlyoutBehavior.Locked => ShellFlyoutBehavior.Locked,
|
||||||
FlyoutBehavior.Disabled => ShellFlyoutBehavior.Disabled,
|
FlyoutBehavior.Disabled => ShellFlyoutBehavior.Disabled,
|
||||||
_ => ShellFlyoutBehavior.Flyout
|
_ => 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
|
// Process shell items into sections
|
||||||
foreach (var item in shell.Items)
|
foreach (var item in shell.Items)
|
||||||
{
|
{
|
||||||
@@ -227,10 +210,6 @@ public class LinuxViewRenderer
|
|||||||
// Store reference to SkiaShell for navigation
|
// Store reference to SkiaShell for navigation
|
||||||
CurrentSkiaShell = skiaShell;
|
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
|
// Subscribe to MAUI Shell navigation events to update SkiaShell
|
||||||
shell.Navigated += OnShellNavigated;
|
shell.Navigated += OnShellNavigated;
|
||||||
shell.Navigating += (s, e) => Console.WriteLine($"[Navigation] Navigating: {e.Target}");
|
shell.Navigating += (s, e) => Console.WriteLine($"[Navigation] Navigating: {e.Target}");
|
||||||
@@ -244,51 +223,6 @@ public class LinuxViewRenderer
|
|||||||
return skiaShell;
|
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>
|
/// <summary>
|
||||||
/// Handles MAUI Shell navigation events and updates SkiaShell accordingly.
|
/// Handles MAUI Shell navigation events and updates SkiaShell accordingly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -356,8 +290,7 @@ public class LinuxViewRenderer
|
|||||||
var shellContent = new ShellContent
|
var shellContent = new ShellContent
|
||||||
{
|
{
|
||||||
Title = content.Title ?? shellSection.Title ?? flyoutItem.Title ?? "",
|
Title = content.Title ?? shellSection.Title ?? flyoutItem.Title ?? "",
|
||||||
Route = content.Route ?? "",
|
Route = content.Route ?? ""
|
||||||
MauiShellContent = content
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create the page content
|
// Create the page content
|
||||||
@@ -395,8 +328,7 @@ public class LinuxViewRenderer
|
|||||||
var shellContent = new ShellContent
|
var shellContent = new ShellContent
|
||||||
{
|
{
|
||||||
Title = content.Title ?? tab.Title ?? "",
|
Title = content.Title ?? tab.Title ?? "",
|
||||||
Route = content.Route ?? "",
|
Route = content.Route ?? ""
|
||||||
MauiShellContent = content
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var pageContent = CreateShellContentPage(content);
|
var pageContent = CreateShellContentPage(content);
|
||||||
@@ -427,8 +359,7 @@ public class LinuxViewRenderer
|
|||||||
var shellContent = new ShellContent
|
var shellContent = new ShellContent
|
||||||
{
|
{
|
||||||
Title = content.Title ?? "",
|
Title = content.Title ?? "",
|
||||||
Route = content.Route ?? "",
|
Route = content.Route ?? ""
|
||||||
MauiShellContent = content
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var pageContent = CreateShellContentPage(content);
|
var pageContent = CreateShellContentPage(content);
|
||||||
@@ -471,33 +402,17 @@ public class LinuxViewRenderer
|
|||||||
var contentView = RenderView(cp.Content);
|
var contentView = RenderView(cp.Content);
|
||||||
if (contentView != null)
|
if (contentView != null)
|
||||||
{
|
{
|
||||||
// Get page background color if set
|
if (contentView is SkiaScrollView)
|
||||||
Color? bgColor = null;
|
|
||||||
if (cp.BackgroundColor != null && cp.BackgroundColor != Colors.Transparent)
|
|
||||||
{
|
{
|
||||||
bgColor = cp.BackgroundColor;
|
return contentView;
|
||||||
Console.WriteLine($"[CreateShellContentPage] Page BackgroundColor: {bgColor}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contentView is SkiaScrollView scrollView)
|
|
||||||
{
|
|
||||||
if (bgColor != null)
|
|
||||||
{
|
|
||||||
scrollView.BackgroundColor = bgColor;
|
|
||||||
}
|
|
||||||
return scrollView;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var newScrollView = new SkiaScrollView
|
var scrollView = new SkiaScrollView
|
||||||
{
|
{
|
||||||
Content = contentView
|
Content = contentView
|
||||||
};
|
};
|
||||||
if (bgColor != null)
|
return scrollView;
|
||||||
{
|
|
||||||
newScrollView.BackgroundColor = bgColor;
|
|
||||||
}
|
|
||||||
return newScrollView;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -555,9 +470,28 @@ public class LinuxViewRenderer
|
|||||||
return new SkiaLabel
|
return new SkiaLabel
|
||||||
{
|
{
|
||||||
Text = $"[{view.GetType().Name}]",
|
Text = $"[{view.GetType().Name}]",
|
||||||
TextColor = Colors.Gray,
|
TextColor = SKColors.Gray,
|
||||||
FontSize = 12
|
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!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
190
Hosting/MauiAppBuilderExtensions.cs
Normal file
190
Hosting/MauiAppBuilderExtensions.cs
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
// 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +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;
|
|
||||||
|
|
||||||
[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;
|
|
||||||
}
|
|
||||||
@@ -1,345 +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 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
239
Interop/X11.cs
@@ -1,239 +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;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
482
Interop/X11Interop.cs
Normal file
482
Interop/X11Interop.cs
Normal file
@@ -0,0 +1,482 @@
|
|||||||
|
// 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
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
@@ -1,13 +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;
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
public struct XClassHint
|
|
||||||
{
|
|
||||||
public IntPtr res_name;
|
|
||||||
public IntPtr res_class;
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
@@ -1,37 +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;
|
|
||||||
|
|
||||||
[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;
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
@@ -1,139 +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>
|
|
||||||
/// 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;
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
// 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