Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a4e35cd39 | |||
| 33914bf572 | |||
| 1f096c38dc |
@@ -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,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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
@@ -1,63 +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.Graphics;
|
||||
using Microsoft.Maui.Handlers;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
namespace Microsoft.Maui.Platform;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for ActivityIndicator control.
|
||||
/// </summary>
|
||||
public class ActivityIndicatorHandler : ViewHandler<IActivityIndicator, SkiaActivityIndicator>
|
||||
public partial class ActivityIndicatorHandler : ViewHandler<IActivityIndicator, SkiaActivityIndicator>
|
||||
{
|
||||
public static IPropertyMapper<IActivityIndicator, ActivityIndicatorHandler> Mapper = new PropertyMapper<IActivityIndicator, ActivityIndicatorHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
["IsRunning"] = MapIsRunning,
|
||||
["Color"] = MapColor,
|
||||
["Background"] = MapBackground
|
||||
[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)
|
||||
{
|
||||
}
|
||||
public ActivityIndicatorHandler() : base(Mapper, CommandMapper) { }
|
||||
|
||||
public ActivityIndicatorHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaActivityIndicator CreatePlatformView()
|
||||
{
|
||||
return new SkiaActivityIndicator();
|
||||
}
|
||||
protected override SkiaActivityIndicator CreatePlatformView() => new SkiaActivityIndicator();
|
||||
|
||||
public static void MapIsRunning(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.IsRunning = activityIndicator.IsRunning;
|
||||
}
|
||||
handler.PlatformView.IsRunning = activityIndicator.IsRunning;
|
||||
}
|
||||
|
||||
public static void MapColor(ActivityIndicatorHandler handler, IActivityIndicator activityIndicator)
|
||||
{
|
||||
if (handler.PlatformView != null && activityIndicator.Color != null)
|
||||
{
|
||||
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 (handler.PlatformView != null)
|
||||
if (activityIndicator.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
if (activityIndicator.Background is SolidPaint solidPaint && solidPaint.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
@@ -49,28 +48,13 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
||||
protected override void ConnectHandler(SkiaBorder platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
if (VirtualView is View view)
|
||||
{
|
||||
platformView.MauiView = view;
|
||||
}
|
||||
platformView.Tapped += OnPlatformViewTapped;
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaBorder platformView)
|
||||
{
|
||||
platformView.Tapped -= OnPlatformViewTapped;
|
||||
platformView.MauiView = null;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnPlatformViewTapped(object? sender, EventArgs e)
|
||||
{
|
||||
if (VirtualView is View view)
|
||||
{
|
||||
GestureManager.ProcessTap(view, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapContent(BorderHandler handler, IBorderView border)
|
||||
{
|
||||
if (handler.PlatformView is null || handler.MauiContext is null) return;
|
||||
@@ -84,7 +68,7 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
||||
if (content.Handler == null)
|
||||
{
|
||||
Console.WriteLine($"[BorderHandler] Creating handler for content: {content.GetType().Name}");
|
||||
content.Handler = content.ToViewHandler(handler.MauiContext);
|
||||
content.Handler = content.ToHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (content.Handler?.PlatformView is SkiaView skiaContent)
|
||||
|
||||
@@ -1,42 +1,57 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
namespace Microsoft.Maui.Platform;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for Button control.
|
||||
/// </summary>
|
||||
public class ButtonHandler : ViewHandler<IButton, SkiaButton>
|
||||
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)
|
||||
{
|
||||
["StrokeColor"] = MapStrokeColor,
|
||||
["StrokeThickness"] = MapStrokeThickness,
|
||||
["CornerRadius"] = MapCornerRadius,
|
||||
["Background"] = MapBackground,
|
||||
["Padding"] = MapPadding,
|
||||
["IsEnabled"] = MapIsEnabled
|
||||
[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,
|
||||
};
|
||||
|
||||
public static CommandMapper<IButton, ButtonHandler> CommandMapper = new(ViewHandler.ViewCommandMapper);
|
||||
/// <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, CommandMapper? commandMapper = null)
|
||||
public ButtonHandler(IPropertyMapper? mapper)
|
||||
: base(mapper ?? Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public ButtonHandler(IPropertyMapper? mapper, CommandMapper? commandMapper)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaButton CreatePlatformView()
|
||||
{
|
||||
return new SkiaButton();
|
||||
var button = new SkiaButton();
|
||||
return button;
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaButton platformView)
|
||||
@@ -46,13 +61,18 @@ public class ButtonHandler : ViewHandler<IButton, SkiaButton>
|
||||
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)
|
||||
{
|
||||
MapStrokeColor(this, VirtualView);
|
||||
MapStrokeThickness(this, VirtualView);
|
||||
MapCornerRadius(this, VirtualView);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -80,66 +100,80 @@ public class ButtonHandler : ViewHandler<IButton, SkiaButton>
|
||||
VirtualView?.Released();
|
||||
}
|
||||
|
||||
public static void MapStrokeColor(ButtonHandler handler, IButton button)
|
||||
public static void MapText(ButtonHandler handler, IButton button)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
var strokeColor = button.StrokeColor;
|
||||
if (strokeColor != null)
|
||||
{
|
||||
handler.PlatformView.BorderColor = strokeColor.ToSKColor();
|
||||
}
|
||||
}
|
||||
handler.PlatformView.Text = button.Text ?? "";
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapStrokeThickness(ButtonHandler handler, IButton button)
|
||||
public static void MapTextColor(ButtonHandler handler, IButton button)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
if (button.TextColor != null)
|
||||
{
|
||||
handler.PlatformView.BorderWidth = (float)button.StrokeThickness;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapCornerRadius(ButtonHandler handler, IButton button)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.CornerRadius = button.CornerRadius;
|
||||
handler.PlatformView.TextColor = button.TextColor.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackground(ButtonHandler handler, IButton button)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
var background = button.Background;
|
||||
if (background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
var background = button.Background;
|
||||
if (background is SolidPaint solidPaint && solidPaint.Color != null)
|
||||
{
|
||||
handler.PlatformView.ButtonBackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
// 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)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
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)
|
||||
{
|
||||
var padding = button.Padding;
|
||||
handler.PlatformView.Padding = new SKRect(
|
||||
(float)padding.Left,
|
||||
(float)padding.Top,
|
||||
(float)padding.Right,
|
||||
(float)padding.Bottom);
|
||||
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)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
Console.WriteLine($"[ButtonHandler] MapIsEnabled - Text='{handler.PlatformView.Text}', IsEnabled={button.IsEnabled}");
|
||||
handler.PlatformView.IsEnabled = button.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
Console.WriteLine($"[ButtonHandler] MapIsEnabled called - Text='{handler.PlatformView.Text}', IsEnabled={button.IsEnabled}");
|
||||
handler.PlatformView.IsEnabled = button.IsEnabled;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,45 @@
|
||||
// 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.Graphics;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Primitives;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
namespace Microsoft.Maui.Platform;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for CheckBox control.
|
||||
/// </summary>
|
||||
public class CheckBoxHandler : ViewHandler<ICheckBox, SkiaCheckBox>
|
||||
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)
|
||||
{
|
||||
["IsChecked"] = MapIsChecked,
|
||||
["Foreground"] = MapForeground,
|
||||
["Background"] = MapBackground,
|
||||
["IsEnabled"] = MapIsEnabled,
|
||||
["VerticalLayoutAlignment"] = MapVerticalLayoutAlignment,
|
||||
["HorizontalLayoutAlignment"] = MapHorizontalLayoutAlignment
|
||||
[nameof(ICheckBox.IsChecked)] = MapIsChecked,
|
||||
[nameof(ICheckBox.Foreground)] = MapForeground,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
public static CommandMapper<ICheckBox, CheckBoxHandler> CommandMapper = new(ViewHandler.ViewCommandMapper);
|
||||
/// <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, CommandMapper? commandMapper = null)
|
||||
public CheckBoxHandler(IPropertyMapper? mapper)
|
||||
: base(mapper ?? Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public CheckBoxHandler(IPropertyMapper? mapper, CommandMapper? commandMapper)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
@@ -61,7 +71,7 @@ public class CheckBoxHandler : ViewHandler<ICheckBox, SkiaCheckBox>
|
||||
|
||||
public static void MapIsChecked(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
if (handler.PlatformView.IsChecked != checkBox.IsChecked)
|
||||
{
|
||||
handler.PlatformView.IsChecked = checkBox.IsChecked;
|
||||
}
|
||||
@@ -69,61 +79,35 @@ public class CheckBoxHandler : ViewHandler<ICheckBox, SkiaCheckBox>
|
||||
|
||||
public static void MapForeground(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
var foreground = checkBox.Foreground;
|
||||
if (foreground is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
if (checkBox.Foreground is SolidPaint solidPaint && solidPaint.Color != null)
|
||||
{
|
||||
handler.PlatformView.CheckColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackground(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
if (checkBox.Background is SolidPaint solidPaint && solidPaint.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.BoxColor = solidBrush.Color.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
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.IsEnabled = checkBox.IsEnabled;
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapVerticalLayoutAlignment(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
public static void MapBackgroundColor(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
if (checkBox is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.VerticalOptions = (int)checkBox.VerticalLayoutAlignment switch
|
||||
{
|
||||
1 => LayoutOptions.Start,
|
||||
2 => LayoutOptions.Center,
|
||||
3 => LayoutOptions.End,
|
||||
0 => LayoutOptions.Fill,
|
||||
_ => LayoutOptions.Fill
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapHorizontalLayoutAlignment(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.HorizontalOptions = (int)checkBox.HorizontalLayoutAlignment switch
|
||||
{
|
||||
1 => LayoutOptions.Start,
|
||||
2 => LayoutOptions.Center,
|
||||
3 => LayoutOptions.End,
|
||||
0 => LayoutOptions.Fill,
|
||||
_ => LayoutOptions.Start
|
||||
};
|
||||
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.Foreground)] = MapForeground,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
[nameof(IView.VerticalLayoutAlignment)] = MapVerticalLayoutAlignment,
|
||||
[nameof(IView.HorizontalLayoutAlignment)] = MapHorizontalLayoutAlignment,
|
||||
};
|
||||
@@ -87,12 +86,6 @@ public partial class CheckBoxHandler : ViewHandler<ICheckBox, SkiaCheckBox>
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.IsEnabled = checkBox.IsEnabled;
|
||||
}
|
||||
|
||||
public static void MapVerticalLayoutAlignment(CheckBoxHandler handler, ICheckBox checkBox)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
@@ -124,49 +123,7 @@ public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCol
|
||||
|
||||
private void OnItemTapped(object? sender, ItemsViewItemTappedEventArgs e)
|
||||
{
|
||||
if (VirtualView is null || _isUpdatingSelection) return;
|
||||
|
||||
try
|
||||
{
|
||||
_isUpdatingSelection = true;
|
||||
|
||||
Console.WriteLine($"[CollectionViewHandler] OnItemTapped index={e.Index}, item={e.Item}, SelectionMode={VirtualView.SelectionMode}");
|
||||
|
||||
// Try to get the item view and process gestures
|
||||
var skiaView = PlatformView?.GetItemView(e.Index);
|
||||
Console.WriteLine($"[CollectionViewHandler] GetItemView({e.Index}) returned: {skiaView?.GetType().Name ?? "null"}, MauiView={skiaView?.MauiView?.GetType().Name ?? "null"}");
|
||||
|
||||
if (skiaView?.MauiView != null)
|
||||
{
|
||||
Console.WriteLine($"[CollectionViewHandler] Found MauiView: {skiaView.MauiView.GetType().Name}, GestureRecognizers={skiaView.MauiView.GestureRecognizers?.Count ?? 0}");
|
||||
if (GestureManager.ProcessTap(skiaView.MauiView, 0, 0))
|
||||
{
|
||||
Console.WriteLine("[CollectionViewHandler] Gesture processed successfully");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle selection if gesture wasn't processed
|
||||
if (VirtualView.SelectionMode == SelectionMode.Single)
|
||||
{
|
||||
VirtualView.SelectedItem = e.Item;
|
||||
}
|
||||
else if (VirtualView.SelectionMode == SelectionMode.Multiple)
|
||||
{
|
||||
if (VirtualView.SelectedItems.Contains(e.Item))
|
||||
{
|
||||
VirtualView.SelectedItems.Remove(e.Item);
|
||||
}
|
||||
else
|
||||
{
|
||||
VirtualView.SelectedItems.Add(e.Item);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isUpdatingSelection = false;
|
||||
}
|
||||
// Item tap is handled through selection
|
||||
}
|
||||
|
||||
public static void MapItemsSource(CollectionViewHandler handler, CollectionView collectionView)
|
||||
@@ -201,14 +158,11 @@ public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCol
|
||||
// Create handler for the view
|
||||
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)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -220,7 +174,7 @@ public partial class CollectionViewHandler : ViewHandler<CollectionView, SkiaCol
|
||||
{
|
||||
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)
|
||||
|
||||
@@ -49,17 +49,6 @@ public partial class DatePickerHandler : ViewHandler<IDatePicker, SkiaDatePicker
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.DateSelected += OnDateSelected;
|
||||
|
||||
// Apply dark theme colors if dark mode is active
|
||||
var current = Application.Current;
|
||||
if (current != null && (int)current.UserAppTheme == 2) // Dark theme
|
||||
{
|
||||
platformView.CalendarBackgroundColor = new SKColor(30, 30, 30);
|
||||
platformView.TextColor = new SKColor(224, 224, 224);
|
||||
platformView.BorderColor = new SKColor(97, 97, 97);
|
||||
platformView.DisabledDayColor = new SKColor(97, 97, 97);
|
||||
platformView.BackgroundColor = new SKColor(45, 45, 45);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaDatePicker platformView)
|
||||
|
||||
@@ -1,187 +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;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Handlers;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for Editor control.
|
||||
/// </summary>
|
||||
public class EditorHandler : ViewHandler<IEditor, SkiaEditor>
|
||||
{
|
||||
public static IPropertyMapper<IEditor, EditorHandler> Mapper = new PropertyMapper<IEditor, EditorHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
["Text"] = MapText,
|
||||
["Placeholder"] = MapPlaceholder,
|
||||
["PlaceholderColor"] = MapPlaceholderColor,
|
||||
["TextColor"] = MapTextColor,
|
||||
["CharacterSpacing"] = MapCharacterSpacing,
|
||||
["IsReadOnly"] = MapIsReadOnly,
|
||||
["IsTextPredictionEnabled"] = MapIsTextPredictionEnabled,
|
||||
["MaxLength"] = MapMaxLength,
|
||||
["CursorPosition"] = MapCursorPosition,
|
||||
["SelectionLength"] = MapSelectionLength,
|
||||
["Keyboard"] = MapKeyboard,
|
||||
["HorizontalTextAlignment"] = MapHorizontalTextAlignment,
|
||||
["VerticalTextAlignment"] = MapVerticalTextAlignment,
|
||||
["Background"] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor
|
||||
};
|
||||
|
||||
public static CommandMapper<IEditor, EditorHandler> CommandMapper = new(ViewHandler.ViewCommandMapper);
|
||||
|
||||
public EditorHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public EditorHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaEditor CreatePlatformView()
|
||||
{
|
||||
return new SkiaEditor();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaEditor platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.TextChanged += OnTextChanged;
|
||||
platformView.Completed += OnCompleted;
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaEditor platformView)
|
||||
{
|
||||
platformView.TextChanged -= OnTextChanged;
|
||||
platformView.Completed -= OnCompleted;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnTextChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (VirtualView != null && PlatformView != null)
|
||||
{
|
||||
VirtualView.Text = PlatformView.Text;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCompleted(object? sender, EventArgs e)
|
||||
{
|
||||
// Editor completed - no specific action needed
|
||||
}
|
||||
|
||||
public static void MapText(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.Text = editor.Text ?? "";
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapPlaceholder(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.Placeholder = editor.Placeholder ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapPlaceholderColor(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
if (handler.PlatformView != null && editor.PlaceholderColor != null)
|
||||
{
|
||||
handler.PlatformView.PlaceholderColor = editor.PlaceholderColor.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapTextColor(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
if (handler.PlatformView != null && editor.TextColor != null)
|
||||
{
|
||||
handler.PlatformView.TextColor = editor.TextColor.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapCharacterSpacing(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
// Character spacing not implemented for editor
|
||||
}
|
||||
|
||||
public static void MapIsReadOnly(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.IsReadOnly = editor.IsReadOnly;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIsTextPredictionEnabled(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
// Text prediction is a mobile feature
|
||||
}
|
||||
|
||||
public static void MapMaxLength(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.MaxLength = editor.MaxLength;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapCursorPosition(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.CursorPosition = editor.CursorPosition;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapSelectionLength(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
// Selection length not implemented
|
||||
}
|
||||
|
||||
public static void MapKeyboard(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
// Keyboard type is a mobile feature
|
||||
}
|
||||
|
||||
public static void MapHorizontalTextAlignment(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
// Horizontal text alignment not implemented
|
||||
}
|
||||
|
||||
public static void MapVerticalTextAlignment(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
// Vertical text alignment not implemented
|
||||
}
|
||||
|
||||
public static void MapBackground(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
if (editor.Background is SolidPaint solidPaint && solidPaint.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(EditorHandler handler, IEditor editor)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
if (editor is VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +1,55 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
namespace Microsoft.Maui.Platform;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for Entry control.
|
||||
/// </summary>
|
||||
public class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
||||
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)
|
||||
{
|
||||
["Text"] = MapText,
|
||||
["TextColor"] = MapTextColor,
|
||||
["Font"] = MapFont,
|
||||
["CharacterSpacing"] = MapCharacterSpacing,
|
||||
["Placeholder"] = MapPlaceholder,
|
||||
["PlaceholderColor"] = MapPlaceholderColor,
|
||||
["IsReadOnly"] = MapIsReadOnly,
|
||||
["MaxLength"] = MapMaxLength,
|
||||
["CursorPosition"] = MapCursorPosition,
|
||||
["SelectionLength"] = MapSelectionLength,
|
||||
["IsPassword"] = MapIsPassword,
|
||||
["ReturnType"] = MapReturnType,
|
||||
["ClearButtonVisibility"] = MapClearButtonVisibility,
|
||||
["HorizontalTextAlignment"] = MapHorizontalTextAlignment,
|
||||
["VerticalTextAlignment"] = MapVerticalTextAlignment,
|
||||
["Background"] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor
|
||||
[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,
|
||||
};
|
||||
|
||||
public static CommandMapper<IEntry, EntryHandler> CommandMapper = new(ViewHandler.ViewCommandMapper);
|
||||
/// <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, CommandMapper? commandMapper = null)
|
||||
public EntryHandler(IPropertyMapper? mapper)
|
||||
: base(mapper ?? Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public EntryHandler(IPropertyMapper? mapper, CommandMapper? commandMapper)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
@@ -67,9 +75,9 @@ public class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
||||
|
||||
private void OnTextChanged(object? sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (VirtualView != null && PlatformView != null && VirtualView.Text != e.NewTextValue)
|
||||
if (VirtualView != null && VirtualView.Text != e.NewText)
|
||||
{
|
||||
VirtualView.Text = e.NewTextValue ?? string.Empty;
|
||||
VirtualView.Text = e.NewText;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,173 +88,112 @@ public class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
||||
|
||||
public static void MapText(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView != null && handler.PlatformView.Text != entry.Text)
|
||||
if (handler.PlatformView.Text != entry.Text)
|
||||
{
|
||||
handler.PlatformView.Text = entry.Text ?? string.Empty;
|
||||
handler.PlatformView.Invalidate();
|
||||
handler.PlatformView.Text = entry.Text ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapTextColor(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView != null && entry.TextColor != null)
|
||||
if (entry.TextColor != null)
|
||||
{
|
||||
handler.PlatformView.TextColor = entry.TextColor.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapFont(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
var font = entry.Font;
|
||||
if (font.Size > 0)
|
||||
{
|
||||
handler.PlatformView.FontSize = (float)font.Size;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(font.Family))
|
||||
{
|
||||
handler.PlatformView.FontFamily = font.Family;
|
||||
}
|
||||
handler.PlatformView.IsBold = (int)font.Weight >= 700;
|
||||
handler.PlatformView.IsItalic = font.Slant == FontSlant.Italic || font.Slant == FontSlant.Oblique;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapCharacterSpacing(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.CharacterSpacing = (float)entry.CharacterSpacing;
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapPlaceholder(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.Placeholder = entry.Placeholder ?? string.Empty;
|
||||
}
|
||||
handler.PlatformView.Placeholder = entry.Placeholder ?? "";
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapPlaceholderColor(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView != null && entry.PlaceholderColor != null)
|
||||
if (entry.PlaceholderColor != null)
|
||||
{
|
||||
handler.PlatformView.PlaceholderColor = entry.PlaceholderColor.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapIsReadOnly(EntryHandler handler, IEntry entry)
|
||||
public static void MapFont(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
var font = entry.Font;
|
||||
if (font.Family != null)
|
||||
{
|
||||
handler.PlatformView.IsReadOnly = entry.IsReadOnly;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapMaxLength(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.MaxLength = entry.MaxLength;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapCursorPosition(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.CursorPosition = entry.CursorPosition;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapSelectionLength(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.SelectionLength = entry.SelectionLength;
|
||||
handler.PlatformView.FontFamily = font.Family;
|
||||
}
|
||||
handler.PlatformView.FontSize = (float)font.Size;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapIsPassword(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.IsPassword = entry.IsPassword;
|
||||
}
|
||||
handler.PlatformView.IsPassword = entry.IsPassword;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapReturnType(EntryHandler handler, IEntry entry)
|
||||
public static void MapMaxLength(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
// ReturnType affects keyboard on mobile; access PlatformView to ensure it exists
|
||||
_ = handler.PlatformView;
|
||||
handler.PlatformView.MaxLength = entry.MaxLength;
|
||||
}
|
||||
|
||||
public static void MapClearButtonVisibility(EntryHandler handler, IEntry entry)
|
||||
public static void MapIsReadOnly(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
// ClearButtonVisibility.WhileEditing = 1
|
||||
handler.PlatformView.ShowClearButton = (int)entry.ClearButtonVisibility == 1;
|
||||
}
|
||||
handler.PlatformView.IsReadOnly = entry.IsReadOnly;
|
||||
}
|
||||
|
||||
public static void MapHorizontalTextAlignment(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
handler.PlatformView.HorizontalTextAlignment = entry.HorizontalTextAlignment switch
|
||||
{
|
||||
handler.PlatformView.HorizontalTextAlignment = (int)entry.HorizontalTextAlignment switch
|
||||
{
|
||||
0 => TextAlignment.Start,
|
||||
1 => TextAlignment.Center,
|
||||
2 => TextAlignment.End,
|
||||
_ => TextAlignment.Start
|
||||
};
|
||||
}
|
||||
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(EntryHandler handler, IEntry entry)
|
||||
public static void MapCursorPosition(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.VerticalTextAlignment = (int)entry.VerticalTextAlignment switch
|
||||
{
|
||||
0 => TextAlignment.Start,
|
||||
1 => TextAlignment.Center,
|
||||
2 => TextAlignment.End,
|
||||
_ => TextAlignment.Center
|
||||
};
|
||||
}
|
||||
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)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
var background = entry.Background;
|
||||
if (background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
if (entry.Background is SolidPaint solidPaint && solidPaint.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(EntryHandler handler, IEntry entry)
|
||||
{
|
||||
if (handler.PlatformView == null)
|
||||
if (entry is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry is Entry mauiEntry)
|
||||
{
|
||||
Console.WriteLine($"[EntryHandler] MapBackgroundColor: {mauiEntry.BackgroundColor}");
|
||||
if (mauiEntry.BackgroundColor != null)
|
||||
{
|
||||
var color = mauiEntry.BackgroundColor.ToSKColor();
|
||||
Console.WriteLine($"[EntryHandler] Setting EntryBackgroundColor to: {color}");
|
||||
handler.PlatformView.EntryBackgroundColor = color;
|
||||
}
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Controls;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
@@ -32,7 +31,6 @@ public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
||||
[nameof(ITextAlignment.HorizontalTextAlignment)] = MapHorizontalTextAlignment,
|
||||
[nameof(ITextAlignment.VerticalTextAlignment)] = MapVerticalTextAlignment,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
public static CommandMapper<IEntry, EntryHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -214,17 +212,4 @@ public partial class EntryHandler : ViewHandler<IEntry, SkiaEntry>
|
||||
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)
|
||||
{
|
||||
Console.WriteLine($"[EntryHandler] MapBackgroundColor: {ve.BackgroundColor}");
|
||||
var color = ve.BackgroundColor.ToSKColor();
|
||||
Console.WriteLine($"[EntryHandler] Setting EntryBackgroundColor to: {color}");
|
||||
handler.PlatformView.EntryBackgroundColor = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.SpaceAround,
|
||||
_ => FlexAlignContent.Stretch,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
@@ -38,31 +37,6 @@ public partial class FrameHandler : ViewHandler<Frame, SkiaFrame>
|
||||
return new SkiaFrame();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaFrame platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
if (VirtualView is View view)
|
||||
{
|
||||
platformView.MauiView = view;
|
||||
}
|
||||
platformView.Tapped += OnPlatformViewTapped;
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaFrame platformView)
|
||||
{
|
||||
platformView.Tapped -= OnPlatformViewTapped;
|
||||
platformView.MauiView = null;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnPlatformViewTapped(object? sender, EventArgs e)
|
||||
{
|
||||
if (VirtualView is View view)
|
||||
{
|
||||
GestureManager.ProcessTap(view, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBorderColor(FrameHandler handler, Frame frame)
|
||||
{
|
||||
if (frame.BorderColor != null)
|
||||
@@ -118,7 +92,7 @@ public partial class FrameHandler : ViewHandler<Frame, SkiaFrame>
|
||||
// Create handler for content if it doesn't exist
|
||||
if (content.Handler == null)
|
||||
{
|
||||
content.Handler = content.ToViewHandler(handler.MauiContext);
|
||||
content.Handler = content.ToHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (content.Handler?.PlatformView is SkiaView skiaContent)
|
||||
|
||||
@@ -1,563 +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, 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; }
|
||||
}
|
||||
|
||||
private enum PointerEventType
|
||||
{
|
||||
Entered,
|
||||
Exited,
|
||||
Pressed,
|
||||
Moved,
|
||||
Released
|
||||
}
|
||||
|
||||
private static MethodInfo? _sendTappedMethod;
|
||||
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;
|
||||
|
||||
/// <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>
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
@@ -1,233 +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.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;
|
||||
}
|
||||
Console.WriteLine("[GtkWebViewHandler] ConnectHandler - WebView ready");
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(GtkWebViewProxy platformView)
|
||||
{
|
||||
if (_platformWebView != null)
|
||||
{
|
||||
_platformWebView.NavigationStarted -= OnNavigationStarted;
|
||||
_platformWebView.NavigationCompleted -= OnNavigationCompleted;
|
||||
UnregisterFromHost();
|
||||
_platformWebView.Dispose();
|
||||
_platformWebView = null;
|
||||
}
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
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,164 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.Maui.Platform.Linux.Native;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <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 WebKitNative.LoadChangedCallback? _loadChangedCallback;
|
||||
|
||||
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 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);
|
||||
Console.WriteLine("[GtkWebViewPlatformView] Created WebKitWebView widget");
|
||||
}
|
||||
|
||||
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;
|
||||
if (_widget != IntPtr.Zero)
|
||||
{
|
||||
WebKitNative.DisconnectLoadChanged(_widget);
|
||||
}
|
||||
_widget = IntPtr.Zero;
|
||||
_loadChangedCallback = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +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.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(SKRect bounds)
|
||||
{
|
||||
base.Arrange(bounds);
|
||||
var windowBounds = TransformToWindow(bounds);
|
||||
_handler.RegisterWithHost(windowBounds);
|
||||
}
|
||||
|
||||
private SKRect TransformToWindow(SKRect localBounds)
|
||||
{
|
||||
float x = localBounds.Left;
|
||||
float y = localBounds.Top;
|
||||
|
||||
for (var parent = Parent; parent != null; parent = parent.Parent)
|
||||
{
|
||||
x += parent.Bounds.Left;
|
||||
y += parent.Bounds.Top;
|
||||
}
|
||||
|
||||
return new SKRect(x, y, x + localBounds.Width, y + localBounds.Height);
|
||||
}
|
||||
|
||||
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(Bounds, 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,7 +24,6 @@ public partial class ImageButtonHandler : ViewHandler<IImageButton, SkiaImageBut
|
||||
[nameof(IButtonStroke.CornerRadius)] = MapCornerRadius,
|
||||
[nameof(IPadding.Padding)] = MapPadding,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
public static CommandMapper<IImageButton, ImageButtonHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -155,16 +154,6 @@ public partial class ImageButtonHandler : ViewHandler<IImageButton, SkiaImageBut
|
||||
}
|
||||
}
|
||||
|
||||
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.BackgroundColor = imgBtn.BackgroundColor.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
// Image source loading helper
|
||||
private ImageSourceServiceResultManager _sourceLoader = null!;
|
||||
|
||||
|
||||
@@ -1,308 +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;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for Image control.
|
||||
/// </summary>
|
||||
public class ImageHandler : ViewHandler<IImage, SkiaImage>
|
||||
{
|
||||
internal class ImageSourceServiceResultManager
|
||||
{
|
||||
private readonly ImageHandler _handler;
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
public ImageSourceServiceResultManager(ImageHandler handler)
|
||||
{
|
||||
_handler = handler;
|
||||
}
|
||||
|
||||
public async void UpdateImageSourceAsync()
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_cts = new CancellationTokenSource();
|
||||
var token = _cts.Token;
|
||||
|
||||
try
|
||||
{
|
||||
var source = _handler.VirtualView?.Source;
|
||||
if (source == null)
|
||||
{
|
||||
_handler.PlatformView?.LoadFromData(Array.Empty<byte>());
|
||||
return;
|
||||
}
|
||||
|
||||
if (_handler.VirtualView is IImageSourcePart imagePart)
|
||||
{
|
||||
imagePart.UpdateIsLoading(true);
|
||||
}
|
||||
|
||||
if (source is IFileImageSource fileSource)
|
||||
{
|
||||
var file = fileSource.File;
|
||||
if (!string.IsNullOrEmpty(file))
|
||||
{
|
||||
await _handler.PlatformView.LoadFromFileAsync(file);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (source is IUriImageSource uriSource)
|
||||
{
|
||||
var uri = uriSource.Uri;
|
||||
if (uri != null)
|
||||
{
|
||||
await _handler.PlatformView.LoadFromUriAsync(uri);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (source is IStreamImageSource streamSource)
|
||||
{
|
||||
var stream = await streamSource.GetStreamAsync(token);
|
||||
if (stream != null)
|
||||
{
|
||||
await _handler.PlatformView.LoadFromStreamAsync(stream);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (source is FontImageSource fontSource)
|
||||
{
|
||||
var bitmap = RenderFontImageSource(fontSource, _handler.PlatformView.WidthRequest, _handler.PlatformView.HeightRequest);
|
||||
if (bitmap != null)
|
||||
{
|
||||
_handler.PlatformView.LoadFromBitmap(bitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Cancelled - ignore
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (_handler.VirtualView is IImageSourcePart imagePart)
|
||||
{
|
||||
imagePart.UpdateIsLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static SKBitmap? RenderFontImageSource(FontImageSource fontSource, double requestedWidth, double requestedHeight)
|
||||
{
|
||||
var 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);
|
||||
|
||||
var color = fontSource.Color?.ToSKColor() ?? SKColors.Black;
|
||||
var bitmap = new SKBitmap(size, size, false);
|
||||
|
||||
using var canvas = new SKCanvas(bitmap);
|
||||
canvas.Clear(SKColors.Transparent);
|
||||
|
||||
SKTypeface? typeface = null;
|
||||
if (!string.IsNullOrEmpty(fontSource.FontFamily))
|
||||
{
|
||||
var fontPaths = new[]
|
||||
{
|
||||
"/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 (var path in fontPaths)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
typeface = SKTypeface.FromFile(path);
|
||||
if (typeface != null)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeface == null)
|
||||
{
|
||||
typeface = SKTypeface.FromFamilyName(fontSource.FontFamily);
|
||||
}
|
||||
}
|
||||
|
||||
typeface ??= SKTypeface.Default;
|
||||
|
||||
float fontSize = size * 0.8f;
|
||||
using var font = new SKFont(typeface, fontSize);
|
||||
using var paint = new SKPaint(font)
|
||||
{
|
||||
Color = color,
|
||||
IsAntialias = true,
|
||||
TextAlign = SKTextAlign.Center
|
||||
};
|
||||
|
||||
var bounds = new SKRect();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public static IPropertyMapper<IImage, ImageHandler> Mapper = new PropertyMapper<IImage, ImageHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
["Aspect"] = MapAspect,
|
||||
["IsOpaque"] = MapIsOpaque,
|
||||
["Source"] = MapSource,
|
||||
["Background"] = MapBackground,
|
||||
["Width"] = MapWidth,
|
||||
["Height"] = MapHeight
|
||||
};
|
||||
|
||||
public static CommandMapper<IImage, ImageHandler> CommandMapper = new(ViewHandler.ViewCommandMapper);
|
||||
|
||||
private ImageSourceServiceResultManager? _sourceLoader;
|
||||
private ImageSourceServiceResultManager SourceLoader => _sourceLoader ??= new ImageSourceServiceResultManager(this);
|
||||
|
||||
public ImageHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public ImageHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaImage CreatePlatformView()
|
||||
{
|
||||
return new SkiaImage();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaImage platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.ImageLoaded += OnImageLoaded;
|
||||
platformView.ImageLoadingError += OnImageLoadingError;
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaImage platformView)
|
||||
{
|
||||
platformView.ImageLoaded -= OnImageLoaded;
|
||||
platformView.ImageLoadingError -= OnImageLoadingError;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnImageLoaded(object? sender, EventArgs e)
|
||||
{
|
||||
if (VirtualView is IImageSourcePart imagePart)
|
||||
{
|
||||
imagePart.UpdateIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnImageLoadingError(object? sender, ImageLoadingErrorEventArgs e)
|
||||
{
|
||||
if (VirtualView is IImageSourcePart imagePart)
|
||||
{
|
||||
imagePart.UpdateIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapAspect(ImageHandler handler, IImage image)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.Aspect = image.Aspect;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIsOpaque(ImageHandler handler, IImage image)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.IsOpaque = image.IsOpaque;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapSource(ImageHandler handler, IImage image)
|
||||
{
|
||||
if (handler.PlatformView == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (image is Image mauiImage)
|
||||
{
|
||||
if (mauiImage.WidthRequest > 0)
|
||||
{
|
||||
handler.PlatformView.WidthRequest = mauiImage.WidthRequest;
|
||||
}
|
||||
if (mauiImage.HeightRequest > 0)
|
||||
{
|
||||
handler.PlatformView.HeightRequest = mauiImage.HeightRequest;
|
||||
}
|
||||
}
|
||||
|
||||
handler.SourceLoader.UpdateImageSourceAsync();
|
||||
}
|
||||
|
||||
public static void MapBackground(ImageHandler handler, IImage image)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
if (image.Background is SolidPaint solidPaint && solidPaint.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapWidth(ImageHandler handler, IImage image)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
if (image is Image mauiImage && mauiImage.WidthRequest > 0)
|
||||
{
|
||||
handler.PlatformView.WidthRequest = mauiImage.WidthRequest;
|
||||
Console.WriteLine($"[ImageHandler] MapWidth: {mauiImage.WidthRequest}");
|
||||
}
|
||||
else if (image.Width > 0)
|
||||
{
|
||||
handler.PlatformView.WidthRequest = image.Width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapHeight(ImageHandler handler, IImage image)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
if (image is Image mauiImage && mauiImage.HeightRequest > 0)
|
||||
{
|
||||
handler.PlatformView.HeightRequest = mauiImage.HeightRequest;
|
||||
Console.WriteLine($"[ImageHandler] MapHeight: {mauiImage.HeightRequest}");
|
||||
}
|
||||
else if (image.Height > 0)
|
||||
{
|
||||
handler.PlatformView.HeightRequest = image.Height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.IO;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using SkiaSharp;
|
||||
@@ -22,8 +20,6 @@ public partial class ImageHandler : ViewHandler<IImage, SkiaImage>
|
||||
[nameof(IImage.IsOpaque)] = MapIsOpaque,
|
||||
[nameof(IImageSourcePart.Source)] = MapSource,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["Width"] = MapWidth,
|
||||
["Height"] = MapHeight,
|
||||
};
|
||||
|
||||
public static CommandMapper<IImage, ImageHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -92,19 +88,6 @@ public partial class ImageHandler : ViewHandler<IImage, SkiaImage>
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
// Extract width/height requests from Image control
|
||||
if (image is Image img)
|
||||
{
|
||||
if (img.WidthRequest > 0)
|
||||
{
|
||||
handler.PlatformView.WidthRequest = img.WidthRequest;
|
||||
}
|
||||
if (img.HeightRequest > 0)
|
||||
{
|
||||
handler.PlatformView.HeightRequest = img.HeightRequest;
|
||||
}
|
||||
}
|
||||
|
||||
handler.SourceLoader.UpdateImageSourceAsync();
|
||||
}
|
||||
|
||||
@@ -118,36 +101,6 @@ public partial class ImageHandler : ViewHandler<IImage, SkiaImage>
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Image source loading helper
|
||||
private ImageSourceServiceResultManager _sourceLoader = null!;
|
||||
|
||||
@@ -209,14 +162,6 @@ public partial class ImageHandler : ViewHandler<IImage, SkiaImage>
|
||||
await _handler.PlatformView!.LoadFromStreamAsync(stream);
|
||||
}
|
||||
}
|
||||
else if (source is FontImageSource fontSource)
|
||||
{
|
||||
var bitmap = RenderFontImageSource(fontSource, _handler.PlatformView!.WidthRequest, _handler.PlatformView.HeightRequest);
|
||||
if (bitmap != null)
|
||||
{
|
||||
_handler.PlatformView.LoadFromBitmap(bitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -231,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,49 +1,52 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Platform.Linux.Window;
|
||||
using Microsoft.Maui.Primitives;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
namespace Microsoft.Maui.Platform;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for Label control.
|
||||
/// </summary>
|
||||
public class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
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)
|
||||
{
|
||||
["Text"] = MapText,
|
||||
["TextColor"] = MapTextColor,
|
||||
["Font"] = MapFont,
|
||||
["CharacterSpacing"] = MapCharacterSpacing,
|
||||
["HorizontalTextAlignment"] = MapHorizontalTextAlignment,
|
||||
["VerticalTextAlignment"] = MapVerticalTextAlignment,
|
||||
["TextDecorations"] = MapTextDecorations,
|
||||
["LineHeight"] = MapLineHeight,
|
||||
["LineBreakMode"] = MapLineBreakMode,
|
||||
["MaxLines"] = MapMaxLines,
|
||||
["Padding"] = MapPadding,
|
||||
["Background"] = MapBackground,
|
||||
["VerticalLayoutAlignment"] = MapVerticalLayoutAlignment,
|
||||
["HorizontalLayoutAlignment"] = MapHorizontalLayoutAlignment,
|
||||
["FormattedText"] = MapFormattedText
|
||||
[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,
|
||||
};
|
||||
|
||||
public static CommandMapper<ILabel, LabelHandler> CommandMapper = new(ViewHandler.ViewCommandMapper);
|
||||
/// <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, CommandMapper? commandMapper = null)
|
||||
public LabelHandler(IPropertyMapper? mapper)
|
||||
: base(mapper ?? Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public LabelHandler(IPropertyMapper? mapper, CommandMapper? commandMapper)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
@@ -53,263 +56,119 @@ public class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
return new SkiaLabel();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaLabel platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
|
||||
if (VirtualView is View view)
|
||||
{
|
||||
platformView.MauiView = view;
|
||||
if (view.GestureRecognizers.OfType<TapGestureRecognizer>().Any())
|
||||
{
|
||||
platformView.CursorType = CursorType.Hand;
|
||||
}
|
||||
}
|
||||
|
||||
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, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapText(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.Text = label.Text ?? string.Empty;
|
||||
}
|
||||
handler.PlatformView.Text = label.Text ?? "";
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapTextColor(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView != null && label.TextColor != null)
|
||||
if (label.TextColor != null)
|
||||
{
|
||||
handler.PlatformView.TextColor = label.TextColor.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapFont(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
var font = label.Font;
|
||||
if (font.Family != null)
|
||||
{
|
||||
var font = label.Font;
|
||||
if (font.Size > 0)
|
||||
{
|
||||
handler.PlatformView.FontSize = (float)font.Size;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(font.Family))
|
||||
{
|
||||
handler.PlatformView.FontFamily = font.Family;
|
||||
}
|
||||
handler.PlatformView.IsBold = (int)font.Weight >= 700;
|
||||
handler.PlatformView.IsItalic = font.Slant == FontSlant.Italic || font.Slant == FontSlant.Oblique;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapCharacterSpacing(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.CharacterSpacing = (float)label.CharacterSpacing;
|
||||
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)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
handler.PlatformView.HorizontalTextAlignment = label.HorizontalTextAlignment switch
|
||||
{
|
||||
handler.PlatformView.HorizontalTextAlignment = (int)label.HorizontalTextAlignment switch
|
||||
{
|
||||
0 => TextAlignment.Start,
|
||||
1 => TextAlignment.Center,
|
||||
2 => TextAlignment.End,
|
||||
_ => TextAlignment.Start
|
||||
};
|
||||
}
|
||||
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)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
handler.PlatformView.VerticalTextAlignment = label.VerticalTextAlignment switch
|
||||
{
|
||||
handler.PlatformView.VerticalTextAlignment = (int)label.VerticalTextAlignment switch
|
||||
{
|
||||
0 => TextAlignment.Start,
|
||||
1 => TextAlignment.Center,
|
||||
2 => TextAlignment.End,
|
||||
_ => TextAlignment.Center
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapTextDecorations(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.IsUnderline = (label.TextDecorations & TextDecorations.Underline) != 0;
|
||||
handler.PlatformView.IsStrikethrough = (label.TextDecorations & TextDecorations.Strikethrough) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapLineHeight(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.LineHeight = (float)label.LineHeight;
|
||||
}
|
||||
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)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
handler.PlatformView.LineBreakMode = label.LineBreakMode switch
|
||||
{
|
||||
if (label is Label mauiLabel)
|
||||
{
|
||||
handler.PlatformView.LineBreakMode = (int)mauiLabel.LineBreakMode switch
|
||||
{
|
||||
0 => LineBreakMode.NoWrap,
|
||||
1 => LineBreakMode.WordWrap,
|
||||
2 => LineBreakMode.CharacterWrap,
|
||||
3 => LineBreakMode.HeadTruncation,
|
||||
4 => LineBreakMode.TailTruncation,
|
||||
5 => LineBreakMode.MiddleTruncation,
|
||||
_ => LineBreakMode.TailTruncation
|
||||
};
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
if (label is Label mauiLabel)
|
||||
{
|
||||
handler.PlatformView.MaxLines = mauiLabel.MaxLines;
|
||||
}
|
||||
}
|
||||
handler.PlatformView.MaxLines = label.MaxLines;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapPadding(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
var padding = label.Padding;
|
||||
handler.PlatformView.Padding = new SKRect(
|
||||
(float)padding.Left,
|
||||
(float)padding.Top,
|
||||
(float)padding.Right,
|
||||
(float)padding.Bottom);
|
||||
}
|
||||
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 (handler.PlatformView != null)
|
||||
if (label.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
if (label.Background is SolidPaint solidPaint && solidPaint.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapVerticalLayoutAlignment(LabelHandler handler, ILabel label)
|
||||
public static void MapBackgroundColor(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
if (label is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.VerticalOptions = (int)label.VerticalLayoutAlignment switch
|
||||
{
|
||||
1 => LayoutOptions.Start,
|
||||
2 => LayoutOptions.Center,
|
||||
3 => LayoutOptions.End,
|
||||
0 => LayoutOptions.Fill,
|
||||
_ => LayoutOptions.Start
|
||||
};
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapHorizontalLayoutAlignment(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.HorizontalOptions = (int)label.HorizontalLayoutAlignment switch
|
||||
{
|
||||
1 => LayoutOptions.Start,
|
||||
2 => LayoutOptions.Center,
|
||||
3 => LayoutOptions.End,
|
||||
0 => LayoutOptions.Fill,
|
||||
_ => LayoutOptions.Start
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapFormattedText(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (label is not Label mauiLabel)
|
||||
{
|
||||
handler.PlatformView.FormattedSpans = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var formattedText = mauiLabel.FormattedText;
|
||||
if (formattedText == null || formattedText.Spans.Count == 0)
|
||||
{
|
||||
handler.PlatformView.FormattedSpans = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var spans = new List<SkiaTextSpan>();
|
||||
foreach (var span in formattedText.Spans)
|
||||
{
|
||||
var skiaSpan = new SkiaTextSpan
|
||||
{
|
||||
Text = span.Text ?? "",
|
||||
IsBold = span.FontAttributes.HasFlag(FontAttributes.Bold),
|
||||
IsItalic = span.FontAttributes.HasFlag(FontAttributes.Italic),
|
||||
IsUnderline = (span.TextDecorations & TextDecorations.Underline) != 0,
|
||||
IsStrikethrough = (span.TextDecorations & TextDecorations.Strikethrough) != 0,
|
||||
CharacterSpacing = (float)span.CharacterSpacing,
|
||||
LineHeight = (float)span.LineHeight
|
||||
};
|
||||
|
||||
if (span.TextColor != null)
|
||||
{
|
||||
skiaSpan.TextColor = span.TextColor.ToSKColor();
|
||||
}
|
||||
if (span.BackgroundColor != null)
|
||||
{
|
||||
skiaSpan.BackgroundColor = span.BackgroundColor.ToSKColor();
|
||||
}
|
||||
if (!string.IsNullOrEmpty(span.FontFamily))
|
||||
{
|
||||
skiaSpan.FontFamily = span.FontFamily;
|
||||
}
|
||||
if (span.FontSize > 0)
|
||||
{
|
||||
skiaSpan.FontSize = (float)span.FontSize;
|
||||
}
|
||||
|
||||
spans.Add(skiaSpan);
|
||||
}
|
||||
|
||||
handler.PlatformView.FormattedSpans = spans;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Linq;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Platform.Linux.Window;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
@@ -32,7 +29,6 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(IView.VerticalLayoutAlignment)] = MapVerticalLayoutAlignment,
|
||||
[nameof(IView.HorizontalLayoutAlignment)] = MapHorizontalLayoutAlignment,
|
||||
["FormattedText"] = MapFormattedText,
|
||||
};
|
||||
|
||||
public static CommandMapper<ILabel, LabelHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -53,39 +49,6 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
return new SkiaLabel();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaLabel platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
|
||||
if (VirtualView is View view)
|
||||
{
|
||||
platformView.MauiView = view;
|
||||
|
||||
// Set hand cursor if the label has tap gesture recognizers
|
||||
if (view.GestureRecognizers.OfType<TapGestureRecognizer>().Any())
|
||||
{
|
||||
platformView.CursorType = CursorType.Hand;
|
||||
}
|
||||
}
|
||||
|
||||
platformView.Tapped += OnPlatformViewTapped;
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaLabel platformView)
|
||||
{
|
||||
platformView.Tapped -= OnPlatformViewTapped;
|
||||
platformView.MauiView = null;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnPlatformViewTapped(object? sender, EventArgs e)
|
||||
{
|
||||
if (VirtualView is View view)
|
||||
{
|
||||
GestureManager.ProcessTap(view, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapText(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
@@ -242,53 +205,4 @@ public partial class LabelHandler : ViewHandler<ILabel, SkiaLabel>
|
||||
_ => LayoutOptions.Start
|
||||
};
|
||||
}
|
||||
|
||||
public static void MapFormattedText(LabelHandler handler, ILabel label)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (label is not Label mauiLabel)
|
||||
{
|
||||
handler.PlatformView.FormattedSpans = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var formattedText = mauiLabel.FormattedText;
|
||||
if (formattedText == null || formattedText.Spans.Count == 0)
|
||||
{
|
||||
handler.PlatformView.FormattedSpans = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var spans = new List<SkiaTextSpan>();
|
||||
foreach (var span in formattedText.Spans)
|
||||
{
|
||||
var skiaSpan = new SkiaTextSpan
|
||||
{
|
||||
Text = span.Text ?? "",
|
||||
IsBold = span.FontAttributes.HasFlag(FontAttributes.Bold),
|
||||
IsItalic = span.FontAttributes.HasFlag(FontAttributes.Italic),
|
||||
IsUnderline = (span.TextDecorations & TextDecorations.Underline) != 0,
|
||||
IsStrikethrough = (span.TextDecorations & TextDecorations.Strikethrough) != 0,
|
||||
CharacterSpacing = (float)span.CharacterSpacing,
|
||||
LineHeight = (float)span.LineHeight
|
||||
};
|
||||
|
||||
if (span.TextColor != null)
|
||||
skiaSpan.TextColor = span.TextColor.ToSKColor();
|
||||
|
||||
if (span.BackgroundColor != null)
|
||||
skiaSpan.BackgroundColor = span.BackgroundColor.ToSKColor();
|
||||
|
||||
if (!string.IsNullOrEmpty(span.FontFamily))
|
||||
skiaSpan.FontFamily = span.FontFamily;
|
||||
|
||||
if (span.FontSize > 0)
|
||||
skiaSpan.FontSize = (float)span.FontSize;
|
||||
|
||||
spans.Add(skiaSpan);
|
||||
}
|
||||
|
||||
handler.PlatformView.FormattedSpans = spans;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform;
|
||||
@@ -79,7 +78,7 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
||||
// Create handler for child if it doesn't exist
|
||||
if (child.Handler == null)
|
||||
{
|
||||
child.Handler = child.ToViewHandler(MauiContext);
|
||||
child.Handler = child.ToHandler(MauiContext);
|
||||
}
|
||||
|
||||
if (child.Handler?.PlatformView is SkiaView skiaChild)
|
||||
@@ -300,7 +299,7 @@ public partial class GridHandler : LayoutHandler
|
||||
// Create handler for child if it doesn't exist
|
||||
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"}");
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
@@ -65,7 +64,7 @@ public partial class LayoutHandler : ViewHandler<ILayout, SkiaLayoutView>
|
||||
// Create handler for child if it doesn't exist
|
||||
if (child.Handler == null)
|
||||
{
|
||||
child.Handler = child.ToViewHandler(MauiContext);
|
||||
child.Handler = child.ToHandler(MauiContext);
|
||||
}
|
||||
|
||||
// Add child's platform view to our layout
|
||||
@@ -285,7 +284,7 @@ public partial class GridHandler : LayoutHandler
|
||||
// Create handler for child if it doesn't exist
|
||||
if (child.Handler == null)
|
||||
{
|
||||
child.Handler = child.ToViewHandler(MauiContext);
|
||||
child.Handler = child.ToHandler(MauiContext);
|
||||
}
|
||||
|
||||
// Get grid position from attached properties
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.IO;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Platform;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using SkiaSharp;
|
||||
using Svg.Skia;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
@@ -103,7 +100,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
if (page.Handler == null)
|
||||
{
|
||||
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}");
|
||||
@@ -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}");
|
||||
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)
|
||||
{
|
||||
@@ -166,7 +163,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
contentPage.ToolbarItems.Clear();
|
||||
foreach (var item in page.ToolbarItems)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] Adding toolbar item: '{item.Text}', 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)
|
||||
// Only Secondary goes to overflow menu
|
||||
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
|
||||
{
|
||||
Text = item.Text ?? "",
|
||||
Icon = icon,
|
||||
Order = order,
|
||||
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)
|
||||
{
|
||||
try
|
||||
@@ -282,7 +221,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
if (e.Page.Handler == null)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] Creating handler for page: {e.Page.GetType().Name}");
|
||||
e.Page.Handler = e.Page.ToViewHandler(MauiContext);
|
||||
e.Page.Handler = e.Page.ToHandler(MauiContext);
|
||||
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.TitleBarColor = PlatformView.BarBackgroundColor;
|
||||
skiaPage.TitleTextColor = PlatformView.BarTextColor;
|
||||
skiaPage.Title = e.Page.Title ?? "";
|
||||
|
||||
// Handle content if null
|
||||
if (skiaPage.Content == null && e.Page is ContentPage contentPage && contentPage.Content != null)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] Content is null, creating handler for: {contentPage.Content.GetType().Name}");
|
||||
if (contentPage.Content.Handler == null)
|
||||
{
|
||||
contentPage.Content.Handler = contentPage.Content.ToViewHandler(MauiContext);
|
||||
}
|
||||
if (contentPage.Content.Handler?.PlatformView is SkiaView skiaContent)
|
||||
{
|
||||
skiaPage.Content = skiaContent;
|
||||
Console.WriteLine($"[NavigationPageHandler] Set content to: {skiaContent.GetType().Name}");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"[NavigationPageHandler] Mapping toolbar items");
|
||||
MapToolbarItems(skiaPage, e.Page);
|
||||
Console.WriteLine($"[NavigationPageHandler] Pushing page to platform");
|
||||
PlatformView.Push(skiaPage, false);
|
||||
Console.WriteLine($"[NavigationPageHandler] Push complete, thread={Environment.CurrentManagedThreadId}");
|
||||
PlatformView.Push(skiaPage, true);
|
||||
Console.WriteLine($"[NavigationPageHandler] Push complete");
|
||||
}
|
||||
Console.WriteLine("[NavigationPageHandler] OnVirtualViewPushed returning");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -329,13 +250,13 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] VirtualView Popped: {e.Page?.Title}");
|
||||
// Pop on the platform side to sync with MAUI navigation
|
||||
PlatformView?.Pop();
|
||||
PlatformView?.Pop(true);
|
||||
}
|
||||
|
||||
private void OnVirtualViewPoppedToRoot(object? sender, Microsoft.Maui.Controls.NavigationEventArgs e)
|
||||
{
|
||||
Console.WriteLine($"[NavigationPageHandler] VirtualView PoppedToRoot");
|
||||
PlatformView?.PopToRoot();
|
||||
PlatformView?.PopToRoot(true);
|
||||
}
|
||||
|
||||
private void OnPushed(object? sender, NavigationEventArgs e)
|
||||
@@ -413,7 +334,7 @@ public partial class NavigationPageHandler : ViewHandler<NavigationPage, SkiaNav
|
||||
// Ensure handler exists
|
||||
if (page.Handler == null)
|
||||
{
|
||||
page.Handler = page.ToViewHandler(handler.MauiContext);
|
||||
page.Handler = page.ToHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (page.Handler?.PlatformView is SkiaPage skiaPage)
|
||||
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
@@ -22,7 +21,6 @@ public partial class PageHandler : ViewHandler<Page, SkiaPage>
|
||||
[nameof(Page.BackgroundImageSource)] = MapBackgroundImageSource,
|
||||
[nameof(Page.Padding)] = MapPadding,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(VisualElement.BackgroundColor)] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
public static CommandMapper<Page, PageHandler> CommandMapper =
|
||||
@@ -102,18 +100,6 @@ public partial class PageHandler : ViewHandler<Page, SkiaPage>
|
||||
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.ToSKColor();
|
||||
Console.WriteLine($"[PageHandler] MapBackgroundColor: {backgroundColor}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -158,7 +144,7 @@ public partial class ContentPageHandler : PageHandler
|
||||
if (content.Handler == null)
|
||||
{
|
||||
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
|
||||
|
||||
@@ -1,178 +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;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Handlers;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for Picker control.
|
||||
/// </summary>
|
||||
public class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
||||
{
|
||||
public static IPropertyMapper<IPicker, PickerHandler> Mapper = new PropertyMapper<IPicker, PickerHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
["Title"] = MapTitle,
|
||||
["TitleColor"] = MapTitleColor,
|
||||
["SelectedIndex"] = MapSelectedIndex,
|
||||
["TextColor"] = MapTextColor,
|
||||
["Font"] = MapFont,
|
||||
["CharacterSpacing"] = MapCharacterSpacing,
|
||||
["HorizontalTextAlignment"] = MapHorizontalTextAlignment,
|
||||
["VerticalTextAlignment"] = MapVerticalTextAlignment,
|
||||
["Background"] = MapBackground,
|
||||
["ItemsSource"] = MapItemsSource
|
||||
};
|
||||
|
||||
public static CommandMapper<IPicker, PickerHandler> CommandMapper = new(ViewHandler.ViewCommandMapper);
|
||||
|
||||
private INotifyCollectionChanged? _itemsCollection;
|
||||
|
||||
public PickerHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public PickerHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaPicker CreatePlatformView()
|
||||
{
|
||||
return new SkiaPicker();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaPicker platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.SelectedIndexChanged += OnSelectedIndexChanged;
|
||||
|
||||
if (VirtualView is Picker picker && picker.Items is INotifyCollectionChanged itemsCollection)
|
||||
{
|
||||
_itemsCollection = itemsCollection;
|
||||
_itemsCollection.CollectionChanged += OnItemsCollectionChanged;
|
||||
}
|
||||
|
||||
ReloadItems();
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaPicker platformView)
|
||||
{
|
||||
platformView.SelectedIndexChanged -= OnSelectedIndexChanged;
|
||||
|
||||
if (_itemsCollection != null)
|
||||
{
|
||||
_itemsCollection.CollectionChanged -= OnItemsCollectionChanged;
|
||||
_itemsCollection = null;
|
||||
}
|
||||
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
ReloadItems();
|
||||
}
|
||||
|
||||
private void OnSelectedIndexChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (VirtualView != null && PlatformView != null)
|
||||
{
|
||||
VirtualView.SelectedIndex = PlatformView.SelectedIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReloadItems()
|
||||
{
|
||||
if (PlatformView != null && VirtualView != null)
|
||||
{
|
||||
var items = VirtualView.GetItemsAsArray();
|
||||
PlatformView.SetItems(items.Select(i => i?.ToString() ?? ""));
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapTitle(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.Title = picker.Title ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapTitleColor(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
if (handler.PlatformView != null && picker.TitleColor != null)
|
||||
{
|
||||
handler.PlatformView.TitleColor = picker.TitleColor.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapSelectedIndex(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.SelectedIndex = picker.SelectedIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapTextColor(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
if (handler.PlatformView != null && picker.TextColor != null)
|
||||
{
|
||||
handler.PlatformView.TextColor = picker.TextColor.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapFont(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
var font = picker.Font;
|
||||
if (!string.IsNullOrEmpty(font.Family))
|
||||
{
|
||||
handler.PlatformView.FontFamily = font.Family;
|
||||
}
|
||||
if (font.Size > 0)
|
||||
{
|
||||
handler.PlatformView.FontSize = (float)font.Size;
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapCharacterSpacing(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
// Character spacing not implemented
|
||||
}
|
||||
|
||||
public static void MapHorizontalTextAlignment(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
// Horizontal text alignment not implemented
|
||||
}
|
||||
|
||||
public static void MapVerticalTextAlignment(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
// Vertical text alignment not implemented
|
||||
}
|
||||
|
||||
public static void MapBackground(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
if (picker.Background is SolidPaint solidPaint && solidPaint.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapItemsSource(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
handler.ReloadItems();
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
||||
[nameof(IPicker.TitleColor)] = MapTitleColor,
|
||||
[nameof(IPicker.SelectedIndex)] = MapSelectedIndex,
|
||||
[nameof(IPicker.TextColor)] = MapTextColor,
|
||||
[nameof(ITextStyle.Font)] = MapFont,
|
||||
[nameof(IPicker.CharacterSpacing)] = MapCharacterSpacing,
|
||||
[nameof(IPicker.HorizontalTextAlignment)] = MapHorizontalTextAlignment,
|
||||
[nameof(IPicker.VerticalTextAlignment)] = MapVerticalTextAlignment,
|
||||
@@ -130,22 +129,6 @@ public partial class PickerHandler : ViewHandler<IPicker, SkiaPicker>
|
||||
}
|
||||
}
|
||||
|
||||
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 = (float)font.Size;
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapCharacterSpacing(PickerHandler handler, IPicker picker)
|
||||
{
|
||||
// Character spacing could be implemented with custom text rendering
|
||||
|
||||
@@ -1,71 +1,29 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Handlers;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
namespace Microsoft.Maui.Platform;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for ProgressBar control.
|
||||
/// </summary>
|
||||
public class ProgressBarHandler : ViewHandler<IProgress, SkiaProgressBar>
|
||||
public partial class ProgressBarHandler : ViewHandler<IProgress, SkiaProgressBar>
|
||||
{
|
||||
public static IPropertyMapper<IProgress, ProgressBarHandler> Mapper = new PropertyMapper<IProgress, ProgressBarHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
["Progress"] = MapProgress,
|
||||
["ProgressColor"] = MapProgressColor,
|
||||
["IsEnabled"] = MapIsEnabled,
|
||||
["Background"] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor
|
||||
[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)
|
||||
{
|
||||
}
|
||||
public ProgressBarHandler() : base(Mapper, CommandMapper) { }
|
||||
|
||||
protected override SkiaProgressBar CreatePlatformView()
|
||||
{
|
||||
return new SkiaProgressBar();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaProgressBar platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
|
||||
if (VirtualView is BindableObject bindable)
|
||||
{
|
||||
bindable.PropertyChanged += OnVirtualViewPropertyChanged;
|
||||
}
|
||||
|
||||
if (VirtualView is VisualElement ve)
|
||||
{
|
||||
platformView.IsVisible = ve.IsVisible;
|
||||
}
|
||||
}
|
||||
|
||||
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 ve && e.PropertyName == "IsVisible")
|
||||
{
|
||||
PlatformView.IsVisible = ve.IsVisible;
|
||||
PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
protected override SkiaProgressBar CreatePlatformView() => new SkiaProgressBar();
|
||||
|
||||
public static void MapProgress(ProgressBarHandler handler, IProgress progress)
|
||||
{
|
||||
@@ -75,9 +33,7 @@ public class ProgressBarHandler : ViewHandler<IProgress, SkiaProgressBar>
|
||||
public static void MapProgressColor(ProgressBarHandler handler, IProgress progress)
|
||||
{
|
||||
if (progress.ProgressColor != null)
|
||||
{
|
||||
handler.PlatformView.ProgressColor = progress.ProgressColor.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
@@ -89,16 +45,16 @@ public class ProgressBarHandler : ViewHandler<IProgress, SkiaProgressBar>
|
||||
|
||||
public static void MapBackground(ProgressBarHandler handler, IProgress progress)
|
||||
{
|
||||
if (progress.Background is SolidPaint solidPaint && solidPaint.Color != null)
|
||||
if (progress.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackgroundColor(ProgressBarHandler handler, IProgress progress)
|
||||
{
|
||||
if (progress is VisualElement ve && ve.BackgroundColor != null)
|
||||
if (progress is Microsoft.Maui.Controls.VisualElement ve && ve.BackgroundColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.ComponentModel;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using SkiaSharp;
|
||||
@@ -20,9 +18,7 @@ public partial class ProgressBarHandler : ViewHandler<IProgress, SkiaProgressBar
|
||||
{
|
||||
[nameof(IProgress.Progress)] = MapProgress,
|
||||
[nameof(IProgress.ProgressColor)] = MapProgressColor,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
};
|
||||
|
||||
public static CommandMapper<IProgress, ProgressBarHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -43,40 +39,6 @@ public partial class ProgressBarHandler : ViewHandler<IProgress, SkiaProgressBar
|
||||
return new SkiaProgressBar();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaProgressBar platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
|
||||
if (VirtualView is BindableObject bindable)
|
||||
{
|
||||
bindable.PropertyChanged += OnVirtualViewPropertyChanged;
|
||||
}
|
||||
|
||||
if (VirtualView is VisualElement visualElement)
|
||||
{
|
||||
platformView.IsVisible = visualElement.IsVisible;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaProgressBar platformView)
|
||||
{
|
||||
if (VirtualView is BindableObject bindable)
|
||||
{
|
||||
bindable.PropertyChanged -= OnVirtualViewPropertyChanged;
|
||||
}
|
||||
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnVirtualViewPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (VirtualView is VisualElement visualElement && e.PropertyName == nameof(VisualElement.IsVisible))
|
||||
{
|
||||
PlatformView.IsVisible = visualElement.IsVisible;
|
||||
PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapProgress(ProgressBarHandler handler, IProgress progress)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
@@ -88,18 +50,7 @@ public partial class ProgressBarHandler : ViewHandler<IProgress, SkiaProgressBar
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (progress.ProgressColor is not null)
|
||||
{
|
||||
handler.PlatformView.ProgressColor = progress.ProgressColor.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.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)
|
||||
@@ -109,18 +60,6 @@ public partial class ProgressBarHandler : ViewHandler<IProgress, SkiaProgressBar
|
||||
if (progress.Background is SolidPaint solidPaint && solidPaint.Color is not null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
handler.PlatformView.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.BackgroundColor = visualElement.BackgroundColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,105 +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;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Handlers;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for RadioButton control.
|
||||
/// </summary>
|
||||
public class RadioButtonHandler : ViewHandler<IRadioButton, SkiaRadioButton>
|
||||
{
|
||||
public static IPropertyMapper<IRadioButton, RadioButtonHandler> Mapper = new PropertyMapper<IRadioButton, RadioButtonHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
["IsChecked"] = MapIsChecked,
|
||||
["TextColor"] = MapTextColor,
|
||||
["Font"] = MapFont,
|
||||
["Background"] = MapBackground
|
||||
};
|
||||
|
||||
public static CommandMapper<IRadioButton, RadioButtonHandler> CommandMapper = new(ViewHandler.ViewCommandMapper);
|
||||
|
||||
public RadioButtonHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public RadioButtonHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaRadioButton CreatePlatformView()
|
||||
{
|
||||
return new SkiaRadioButton();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaRadioButton platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.CheckedChanged += OnCheckedChanged;
|
||||
|
||||
if (VirtualView is RadioButton radioButton)
|
||||
{
|
||||
platformView.Content = radioButton.Content?.ToString() ?? "";
|
||||
platformView.GroupName = radioButton.GroupName;
|
||||
platformView.Value = radioButton.Value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaRadioButton platformView)
|
||||
{
|
||||
platformView.CheckedChanged -= OnCheckedChanged;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnCheckedChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (VirtualView != null && PlatformView != null)
|
||||
{
|
||||
VirtualView.IsChecked = PlatformView.IsChecked;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIsChecked(RadioButtonHandler handler, IRadioButton radioButton)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.IsChecked = radioButton.IsChecked;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapTextColor(RadioButtonHandler handler, IRadioButton radioButton)
|
||||
{
|
||||
if (handler.PlatformView != null && radioButton.TextColor != null)
|
||||
{
|
||||
handler.PlatformView.TextColor = radioButton.TextColor.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapFont(RadioButtonHandler handler, IRadioButton radioButton)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
var font = radioButton.Font;
|
||||
if (font.Size > 0)
|
||||
{
|
||||
handler.PlatformView.FontSize = (float)font.Size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackground(RadioButtonHandler handler, IRadioButton radioButton)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
if (radioButton.Background is SolidPaint solidPaint && solidPaint.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +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;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for ScrollView control.
|
||||
/// </summary>
|
||||
public class ScrollViewHandler : ViewHandler<IScrollView, SkiaScrollView>
|
||||
{
|
||||
public static IPropertyMapper<IScrollView, ScrollViewHandler> Mapper = new PropertyMapper<IScrollView, ScrollViewHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
["Content"] = MapContent,
|
||||
["HorizontalScrollBarVisibility"] = MapHorizontalScrollBarVisibility,
|
||||
["VerticalScrollBarVisibility"] = MapVerticalScrollBarVisibility,
|
||||
["Orientation"] = MapOrientation
|
||||
};
|
||||
|
||||
public static CommandMapper<IScrollView, ScrollViewHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
{
|
||||
["RequestScrollTo"] = MapRequestScrollTo
|
||||
};
|
||||
|
||||
public ScrollViewHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public ScrollViewHandler(IPropertyMapper? mapper)
|
||||
: base(mapper ?? Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaScrollView CreatePlatformView()
|
||||
{
|
||||
return new SkiaScrollView();
|
||||
}
|
||||
|
||||
public static void MapContent(ScrollViewHandler handler, IScrollView scrollView)
|
||||
{
|
||||
if (handler.PlatformView == null || handler.MauiContext == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var presentedContent = scrollView.PresentedContent;
|
||||
if (presentedContent != null)
|
||||
{
|
||||
Console.WriteLine("[ScrollViewHandler] MapContent: " + presentedContent.GetType().Name);
|
||||
|
||||
if (presentedContent.Handler == null)
|
||||
{
|
||||
presentedContent.Handler = presentedContent.ToViewHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (presentedContent.Handler?.PlatformView is SkiaView skiaView)
|
||||
{
|
||||
Console.WriteLine("[ScrollViewHandler] Setting content: " + skiaView.GetType().Name);
|
||||
handler.PlatformView.Content = skiaView;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
handler.PlatformView.Content = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapHorizontalScrollBarVisibility(ScrollViewHandler handler, IScrollView scrollView)
|
||||
{
|
||||
handler.PlatformView.HorizontalScrollBarVisibility = (int)scrollView.HorizontalScrollBarVisibility switch
|
||||
{
|
||||
1 => ScrollBarVisibility.Always,
|
||||
2 => ScrollBarVisibility.Never,
|
||||
_ => ScrollBarVisibility.Default
|
||||
};
|
||||
}
|
||||
|
||||
public static void MapVerticalScrollBarVisibility(ScrollViewHandler handler, IScrollView scrollView)
|
||||
{
|
||||
handler.PlatformView.VerticalScrollBarVisibility = (int)scrollView.VerticalScrollBarVisibility switch
|
||||
{
|
||||
1 => ScrollBarVisibility.Always,
|
||||
2 => ScrollBarVisibility.Never,
|
||||
_ => ScrollBarVisibility.Default
|
||||
};
|
||||
}
|
||||
|
||||
public static void MapOrientation(ScrollViewHandler handler, IScrollView scrollView)
|
||||
{
|
||||
handler.PlatformView.Orientation = ((int)scrollView.Orientation - 1) switch
|
||||
{
|
||||
0 => ScrollOrientation.Horizontal,
|
||||
1 => ScrollOrientation.Both,
|
||||
2 => ScrollOrientation.Neither,
|
||||
_ => ScrollOrientation.Vertical
|
||||
};
|
||||
}
|
||||
|
||||
public static void MapRequestScrollTo(ScrollViewHandler handler, IScrollView scrollView, object? args)
|
||||
{
|
||||
if (args is ScrollToRequest request)
|
||||
{
|
||||
handler.PlatformView.ScrollTo((float)request.HorizontalOffset, (float)request.VerticalOffset, !request.Instant);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
@@ -53,7 +52,7 @@ public partial class ScrollViewHandler : ViewHandler<IScrollView, SkiaScrollView
|
||||
// Create handler for content if it doesn't exist
|
||||
if (content.Handler == null)
|
||||
{
|
||||
content.Handler = content.ToViewHandler(handler.MauiContext);
|
||||
content.Handler = content.ToHandler(handler.MauiContext);
|
||||
}
|
||||
|
||||
if (content.Handler?.PlatformView is SkiaView skiaContent)
|
||||
|
||||
@@ -1,43 +1,31 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Handlers;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
namespace Microsoft.Maui.Platform;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for SearchBar control.
|
||||
/// </summary>
|
||||
public class SearchBarHandler : ViewHandler<ISearchBar, SkiaSearchBar>
|
||||
public partial class SearchBarHandler : ViewHandler<ISearchBar, SkiaSearchBar>
|
||||
{
|
||||
public static IPropertyMapper<ISearchBar, SearchBarHandler> Mapper = new PropertyMapper<ISearchBar, SearchBarHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
["Text"] = MapText,
|
||||
["TextColor"] = MapTextColor,
|
||||
["Font"] = MapFont,
|
||||
["Placeholder"] = MapPlaceholder,
|
||||
["PlaceholderColor"] = MapPlaceholderColor,
|
||||
["CancelButtonColor"] = MapCancelButtonColor,
|
||||
["Background"] = MapBackground
|
||||
[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)
|
||||
{
|
||||
}
|
||||
public SearchBarHandler() : base(Mapper, CommandMapper) { }
|
||||
|
||||
public SearchBarHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaSearchBar CreatePlatformView()
|
||||
{
|
||||
return new SkiaSearchBar();
|
||||
}
|
||||
protected override SkiaSearchBar CreatePlatformView() => new SkiaSearchBar();
|
||||
|
||||
protected override void ConnectHandler(SkiaSearchBar platformView)
|
||||
{
|
||||
@@ -55,9 +43,9 @@ public class SearchBarHandler : ViewHandler<ISearchBar, SkiaSearchBar>
|
||||
|
||||
private void OnTextChanged(object? sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (VirtualView != null && PlatformView != null && VirtualView.Text != e.NewTextValue)
|
||||
if (VirtualView != null && VirtualView.Text != e.NewText)
|
||||
{
|
||||
VirtualView.Text = e.NewTextValue ?? string.Empty;
|
||||
VirtualView.Text = e.NewText;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,68 +56,51 @@ public class SearchBarHandler : ViewHandler<ISearchBar, SkiaSearchBar>
|
||||
|
||||
public static void MapText(SearchBarHandler handler, ISearchBar searchBar)
|
||||
{
|
||||
if (handler.PlatformView != null && handler.PlatformView.Text != searchBar.Text)
|
||||
if (handler.PlatformView.Text != searchBar.Text)
|
||||
{
|
||||
handler.PlatformView.Text = searchBar.Text ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapTextColor(SearchBarHandler handler, ISearchBar searchBar)
|
||||
{
|
||||
if (handler.PlatformView != null && searchBar.TextColor != null)
|
||||
{
|
||||
handler.PlatformView.TextColor = searchBar.TextColor.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapFont(SearchBarHandler handler, ISearchBar searchBar)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
var font = searchBar.Font;
|
||||
if (font.Size > 0)
|
||||
{
|
||||
handler.PlatformView.FontSize = (float)font.Size;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(font.Family))
|
||||
{
|
||||
handler.PlatformView.FontFamily = font.Family;
|
||||
}
|
||||
handler.PlatformView.Text = searchBar.Text ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapPlaceholder(SearchBarHandler handler, ISearchBar searchBar)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.Placeholder = searchBar.Placeholder ?? string.Empty;
|
||||
}
|
||||
handler.PlatformView.Placeholder = searchBar.Placeholder ?? "";
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapPlaceholderColor(SearchBarHandler handler, ISearchBar searchBar)
|
||||
{
|
||||
if (handler.PlatformView != null && searchBar.PlaceholderColor != null)
|
||||
{
|
||||
if (searchBar.PlaceholderColor != null)
|
||||
handler.PlatformView.PlaceholderColor = searchBar.PlaceholderColor.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapCancelButtonColor(SearchBarHandler handler, ISearchBar searchBar)
|
||||
public static void MapTextColor(SearchBarHandler handler, ISearchBar searchBar)
|
||||
{
|
||||
if (handler.PlatformView != null && searchBar.CancelButtonColor != null)
|
||||
{
|
||||
handler.PlatformView.ClearButtonColor = searchBar.CancelButtonColor.ToSKColor();
|
||||
}
|
||||
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 (handler.PlatformView != null)
|
||||
{
|
||||
if (searchBar.Background is SolidPaint solidPaint && solidPaint.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
}
|
||||
if (searchBar.Background is SolidColorBrush solidBrush && solidBrush.Color != null)
|
||||
handler.PlatformView.BackgroundColor = solidBrush.Color.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,33 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Handlers;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
namespace Microsoft.Maui.Platform;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for Slider control.
|
||||
/// </summary>
|
||||
public class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
||||
public partial class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
||||
{
|
||||
public static IPropertyMapper<ISlider, SliderHandler> Mapper = new PropertyMapper<ISlider, SliderHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
["Minimum"] = MapMinimum,
|
||||
["Maximum"] = MapMaximum,
|
||||
["Value"] = MapValue,
|
||||
["MinimumTrackColor"] = MapMinimumTrackColor,
|
||||
["MaximumTrackColor"] = MapMaximumTrackColor,
|
||||
["ThumbColor"] = MapThumbColor,
|
||||
["Background"] = MapBackground,
|
||||
["IsEnabled"] = MapIsEnabled
|
||||
[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)
|
||||
{
|
||||
}
|
||||
public SliderHandler() : base(Mapper, CommandMapper) { }
|
||||
|
||||
public SliderHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaSlider CreatePlatformView()
|
||||
{
|
||||
return new SkiaSlider();
|
||||
}
|
||||
protected override SkiaSlider CreatePlatformView() => new SkiaSlider();
|
||||
|
||||
protected override void ConnectHandler(SkiaSlider platformView)
|
||||
{
|
||||
@@ -46,14 +35,6 @@ public class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
||||
platformView.ValueChanged += OnValueChanged;
|
||||
platformView.DragStarted += OnDragStarted;
|
||||
platformView.DragCompleted += OnDragCompleted;
|
||||
|
||||
if (VirtualView != null)
|
||||
{
|
||||
MapMinimum(this, VirtualView);
|
||||
MapMaximum(this, VirtualView);
|
||||
MapValue(this, VirtualView);
|
||||
MapIsEnabled(this, VirtualView);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaSlider platformView)
|
||||
@@ -66,41 +47,30 @@ public class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
||||
|
||||
private void OnValueChanged(object? sender, SliderValueChangedEventArgs e)
|
||||
{
|
||||
if (VirtualView != null && PlatformView != null && Math.Abs(VirtualView.Value - e.NewValue) > 0.0001)
|
||||
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();
|
||||
}
|
||||
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)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.Minimum = slider.Minimum;
|
||||
}
|
||||
handler.PlatformView.Minimum = slider.Minimum;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapMaximum(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.Maximum = slider.Maximum;
|
||||
}
|
||||
handler.PlatformView.Maximum = slider.Maximum;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapValue(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
if (handler.PlatformView != null && Math.Abs(handler.PlatformView.Value - slider.Value) > 0.0001)
|
||||
if (Math.Abs(handler.PlatformView.Value - slider.Value) > 0.001)
|
||||
{
|
||||
handler.PlatformView.Value = slider.Value;
|
||||
}
|
||||
@@ -108,44 +78,45 @@ public class SliderHandler : ViewHandler<ISlider, SkiaSlider>
|
||||
|
||||
public static void MapMinimumTrackColor(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
if (handler.PlatformView != null && slider.MinimumTrackColor != null)
|
||||
{
|
||||
if (slider.MinimumTrackColor != null)
|
||||
handler.PlatformView.ActiveTrackColor = slider.MinimumTrackColor.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapMaximumTrackColor(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
if (handler.PlatformView != null && slider.MaximumTrackColor != null)
|
||||
{
|
||||
if (slider.MaximumTrackColor != null)
|
||||
handler.PlatformView.TrackColor = slider.MaximumTrackColor.ToSKColor();
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapThumbColor(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
if (handler.PlatformView != null && slider.ThumbColor != null)
|
||||
{
|
||||
if (slider.ThumbColor != null)
|
||||
handler.PlatformView.ThumbColor = slider.ThumbColor.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackground(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
if (slider.Background is SolidPaint solidPaint && solidPaint.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(SliderHandler handler, ISlider slider)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
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.IsEnabled = slider.IsEnabled;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,127 +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;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Handlers;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for Stepper control.
|
||||
/// </summary>
|
||||
public class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
||||
{
|
||||
public static IPropertyMapper<IStepper, StepperHandler> Mapper = new PropertyMapper<IStepper, StepperHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
["Value"] = MapValue,
|
||||
["Minimum"] = MapMinimum,
|
||||
["Maximum"] = MapMaximum,
|
||||
["Increment"] = MapIncrement,
|
||||
["Background"] = MapBackground,
|
||||
["IsEnabled"] = MapIsEnabled
|
||||
};
|
||||
|
||||
public static CommandMapper<IStepper, StepperHandler> CommandMapper = new(ViewHandler.ViewCommandMapper);
|
||||
|
||||
public StepperHandler() : base(Mapper, CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
public StepperHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaStepper CreatePlatformView()
|
||||
{
|
||||
return new SkiaStepper();
|
||||
}
|
||||
|
||||
protected override void ConnectHandler(SkiaStepper platformView)
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.ValueChanged += OnValueChanged;
|
||||
|
||||
// Apply dark theme colors if needed
|
||||
if (Application.Current?.UserAppTheme == AppTheme.Dark)
|
||||
{
|
||||
platformView.ButtonBackgroundColor = new SKColor(66, 66, 66);
|
||||
platformView.ButtonPressedColor = new SKColor(97, 97, 97);
|
||||
platformView.ButtonDisabledColor = new SKColor(48, 48, 48);
|
||||
platformView.SymbolColor = new SKColor(224, 224, 224);
|
||||
platformView.SymbolDisabledColor = new SKColor(97, 97, 97);
|
||||
platformView.BorderColor = new SKColor(97, 97, 97);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaStepper platformView)
|
||||
{
|
||||
platformView.ValueChanged -= OnValueChanged;
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnValueChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (VirtualView != null && PlatformView != null)
|
||||
{
|
||||
VirtualView.Value = PlatformView.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapValue(StepperHandler handler, IStepper stepper)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.Value = stepper.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapMinimum(StepperHandler handler, IStepper stepper)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.Minimum = stepper.Minimum;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapMaximum(StepperHandler handler, IStepper stepper)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.Maximum = stepper.Maximum;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackground(StepperHandler handler, IStepper stepper)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
if (stepper.Background is SolidPaint solidPaint && solidPaint.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIncrement(StepperHandler handler, IStepper stepper)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
if (stepper is Stepper mauiStepper)
|
||||
{
|
||||
handler.PlatformView.Increment = mauiStepper.Increment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(StepperHandler handler, IStepper stepper)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
handler.PlatformView.IsEnabled = stepper.IsEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using Microsoft.Maui.Handlers;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Platform;
|
||||
using SkiaSharp;
|
||||
|
||||
@@ -20,9 +19,7 @@ public partial class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
||||
[nameof(IStepper.Value)] = MapValue,
|
||||
[nameof(IStepper.Minimum)] = MapMinimum,
|
||||
[nameof(IStepper.Maximum)] = MapMaximum,
|
||||
["Increment"] = MapIncrement,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
};
|
||||
|
||||
public static CommandMapper<IStepper, StepperHandler> CommandMapper =
|
||||
@@ -48,17 +45,6 @@ public partial class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.ValueChanged += OnValueChanged;
|
||||
|
||||
// Apply dark theme colors if needed
|
||||
if (Application.Current?.UserAppTheme == AppTheme.Dark)
|
||||
{
|
||||
platformView.ButtonBackgroundColor = new SKColor(66, 66, 66);
|
||||
platformView.ButtonPressedColor = new SKColor(97, 97, 97);
|
||||
platformView.ButtonDisabledColor = new SKColor(48, 48, 48);
|
||||
platformView.SymbolColor = new SKColor(224, 224, 224);
|
||||
platformView.SymbolDisabledColor = new SKColor(97, 97, 97);
|
||||
platformView.BorderColor = new SKColor(97, 97, 97);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaStepper platformView)
|
||||
@@ -100,20 +86,4 @@ public partial class StepperHandler : ViewHandler<IStepper, SkiaStepper>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,30 @@
|
||||
// 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.Handlers;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Handlers;
|
||||
namespace Microsoft.Maui.Platform;
|
||||
|
||||
/// <summary>
|
||||
/// Linux handler for Switch control.
|
||||
/// </summary>
|
||||
public class SwitchHandler : ViewHandler<ISwitch, SkiaSwitch>
|
||||
public partial class SwitchHandler : ViewHandler<ISwitch, SkiaSwitch>
|
||||
{
|
||||
public static IPropertyMapper<ISwitch, SwitchHandler> Mapper = new PropertyMapper<ISwitch, SwitchHandler>(ViewHandler.ViewMapper)
|
||||
{
|
||||
["IsOn"] = MapIsOn,
|
||||
["TrackColor"] = MapTrackColor,
|
||||
["ThumbColor"] = MapThumbColor,
|
||||
["Background"] = MapBackground,
|
||||
["IsEnabled"] = MapIsEnabled
|
||||
[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)
|
||||
{
|
||||
}
|
||||
public SwitchHandler() : base(Mapper, CommandMapper) { }
|
||||
|
||||
public SwitchHandler(IPropertyMapper? mapper, CommandMapper? commandMapper = null)
|
||||
: base(mapper ?? Mapper, commandMapper ?? CommandMapper)
|
||||
{
|
||||
}
|
||||
|
||||
protected override SkiaSwitch CreatePlatformView()
|
||||
{
|
||||
return new SkiaSwitch();
|
||||
}
|
||||
protected override SkiaSwitch CreatePlatformView() => new SkiaSwitch();
|
||||
|
||||
protected override void ConnectHandler(SkiaSwitch platformView)
|
||||
{
|
||||
@@ -59,7 +48,7 @@ public class SwitchHandler : ViewHandler<ISwitch, SkiaSwitch>
|
||||
|
||||
public static void MapIsOn(SwitchHandler handler, ISwitch @switch)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
if (handler.PlatformView.IsOn != @switch.IsOn)
|
||||
{
|
||||
handler.PlatformView.IsOn = @switch.IsOn;
|
||||
}
|
||||
@@ -67,38 +56,39 @@ public class SwitchHandler : ViewHandler<ISwitch, SkiaSwitch>
|
||||
|
||||
public static void MapTrackColor(SwitchHandler handler, ISwitch @switch)
|
||||
{
|
||||
if (handler.PlatformView != null && @switch.TrackColor != null)
|
||||
{
|
||||
var onTrackColor = @switch.TrackColor.ToSKColor();
|
||||
handler.PlatformView.OnTrackColor = onTrackColor;
|
||||
handler.PlatformView.OffTrackColor = onTrackColor.WithAlpha(128);
|
||||
}
|
||||
if (@switch.TrackColor != null)
|
||||
handler.PlatformView.OnTrackColor = @switch.TrackColor.ToSKColor();
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapThumbColor(SwitchHandler handler, ISwitch @switch)
|
||||
{
|
||||
if (handler.PlatformView != null && @switch.ThumbColor != null)
|
||||
{
|
||||
if (@switch.ThumbColor != null)
|
||||
handler.PlatformView.ThumbColor = @switch.ThumbColor.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapBackground(SwitchHandler handler, ISwitch @switch)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
{
|
||||
if (@switch.Background is SolidPaint solidPaint && solidPaint.Color != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(SwitchHandler handler, ISwitch @switch)
|
||||
{
|
||||
if (handler.PlatformView != null)
|
||||
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.IsEnabled = @switch.IsEnabled;
|
||||
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.ThumbColor)] = MapThumbColor,
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
[nameof(IView.IsEnabled)] = MapIsEnabled,
|
||||
};
|
||||
|
||||
public static CommandMapper<ISwitch, SwitchHandler> CommandMapper = new(ViewHandler.ViewCommandMapper)
|
||||
@@ -97,10 +96,4 @@ public partial class SwitchHandler : ViewHandler<ISwitch, SkiaSwitch>
|
||||
handler.PlatformView.BackgroundColor = solidPaint.Color.ToSKColor();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapIsEnabled(SwitchHandler handler, ISwitch @switch)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
handler.PlatformView.IsEnabled = @switch.IsEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,16 +47,6 @@ public partial class TimePickerHandler : ViewHandler<ITimePicker, SkiaTimePicker
|
||||
{
|
||||
base.ConnectHandler(platformView);
|
||||
platformView.TimeSelected += OnTimeSelected;
|
||||
|
||||
// Apply dark theme colors if needed
|
||||
if (Application.Current?.UserAppTheme == AppTheme.Dark)
|
||||
{
|
||||
platformView.ClockBackgroundColor = new SKColor(30, 30, 30);
|
||||
platformView.ClockFaceColor = new SKColor(45, 45, 45);
|
||||
platformView.TextColor = new SKColor(224, 224, 224);
|
||||
platformView.BorderColor = new SKColor(97, 97, 97);
|
||||
platformView.BackgroundColor = new SKColor(45, 45, 45);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaTimePicker platformView)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -54,63 +54,29 @@ public partial class WebViewHandler : ViewHandler<IWebView, SkiaWebView>
|
||||
base.DisconnectHandler(platformView);
|
||||
}
|
||||
|
||||
private void OnNavigating(object? sender, Microsoft.Maui.Platform.WebNavigatingEventArgs e)
|
||||
private void OnNavigating(object? sender, WebNavigatingEventArgs e)
|
||||
{
|
||||
IWebView virtualView = VirtualView;
|
||||
IWebViewController? controller = virtualView as IWebViewController;
|
||||
if (controller != null)
|
||||
{
|
||||
var args = new Microsoft.Maui.Controls.WebNavigatingEventArgs(
|
||||
WebNavigationEvent.NewPage,
|
||||
null,
|
||||
e.Url);
|
||||
controller.SendNavigating(args);
|
||||
}
|
||||
// Forward to virtual view if needed
|
||||
}
|
||||
|
||||
private void OnNavigated(object? sender, Microsoft.Maui.Platform.WebNavigatedEventArgs e)
|
||||
private void OnNavigated(object? sender, WebNavigatedEventArgs e)
|
||||
{
|
||||
IWebView virtualView = VirtualView;
|
||||
IWebViewController? controller = virtualView as IWebViewController;
|
||||
if (controller != null)
|
||||
{
|
||||
WebNavigationResult result = e.Success ? WebNavigationResult.Success : WebNavigationResult.Failure;
|
||||
var args = new Microsoft.Maui.Controls.WebNavigatedEventArgs(
|
||||
WebNavigationEvent.NewPage,
|
||||
null,
|
||||
e.Url,
|
||||
result);
|
||||
controller.SendNavigated(args);
|
||||
}
|
||||
// Forward to virtual view if needed
|
||||
}
|
||||
|
||||
public static void MapSource(WebViewHandler handler, IWebView webView)
|
||||
{
|
||||
Console.WriteLine("[WebViewHandler] MapSource called");
|
||||
if (handler.PlatformView == null)
|
||||
{
|
||||
Console.WriteLine("[WebViewHandler] PlatformView is null!");
|
||||
return;
|
||||
}
|
||||
if (handler.PlatformView == null) return;
|
||||
|
||||
var source = webView.Source;
|
||||
Console.WriteLine($"[WebViewHandler] Source type: {source?.GetType().Name ?? "null"}");
|
||||
|
||||
if (source is UrlWebViewSource urlSource)
|
||||
{
|
||||
Console.WriteLine($"[WebViewHandler] Loading URL: {urlSource.Url}");
|
||||
handler.PlatformView.Source = urlSource.Url ?? "";
|
||||
}
|
||||
else if (source is HtmlWebViewSource htmlSource)
|
||||
{
|
||||
Console.WriteLine($"[WebViewHandler] Loading HTML ({htmlSource.Html?.Length ?? 0} chars)");
|
||||
Console.WriteLine($"[WebViewHandler] HTML preview: {htmlSource.Html?.Substring(0, Math.Min(100, htmlSource.Html?.Length ?? 0))}...");
|
||||
handler.PlatformView.Html = htmlSource.Html ?? "";
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("[WebViewHandler] Unknown source type or null");
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapGoBack(WebViewHandler handler, IWebView webView, object? args)
|
||||
|
||||
@@ -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.Communication;
|
||||
using Microsoft.Maui.ApplicationModel.DataTransfer;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Devices;
|
||||
using Microsoft.Maui.Dispatching;
|
||||
using Microsoft.Maui.Hosting;
|
||||
using Microsoft.Maui.Networking;
|
||||
using Microsoft.Maui.Platform.Linux.Converters;
|
||||
using Microsoft.Maui.Platform.Linux.Dispatching;
|
||||
using Microsoft.Maui.Platform.Linux.Handlers;
|
||||
using Microsoft.Maui.Platform.Linux.Services;
|
||||
using Microsoft.Maui.Platform.Linux.Converters;
|
||||
using Microsoft.Maui.Storage;
|
||||
using Microsoft.Maui.Platform.Linux.Handlers;
|
||||
using Microsoft.Maui.Controls;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Hosting;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for configuring MAUI applications for Linux.
|
||||
/// </summary>
|
||||
public static class LinuxMauiAppBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures the MAUI application to run on Linux.
|
||||
/// </summary>
|
||||
public static MauiAppBuilder UseLinux(this MauiAppBuilder builder)
|
||||
{
|
||||
return builder.UseLinux(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)
|
||||
{
|
||||
var options = new LinuxApplicationOptions();
|
||||
configure?.Invoke(options);
|
||||
|
||||
// Register dispatcher provider
|
||||
builder.Services.TryAddSingleton<IDispatcherProvider>(LinuxDispatcherProvider.Instance);
|
||||
|
||||
// Register device services
|
||||
builder.Services.TryAddSingleton<IDeviceInfo>(DeviceInfoService.Instance);
|
||||
builder.Services.TryAddSingleton<IDeviceDisplay>(DeviceDisplayService.Instance);
|
||||
builder.Services.TryAddSingleton<IAppInfo>(AppInfoService.Instance);
|
||||
builder.Services.TryAddSingleton<IConnectivity>(ConnectivityService.Instance);
|
||||
|
||||
// Register platform services
|
||||
builder.Services.TryAddSingleton<ILauncher, LauncherService>();
|
||||
builder.Services.TryAddSingleton<IPreferences, PreferencesService>();
|
||||
@@ -54,9 +50,6 @@ public static class LinuxMauiAppBuilderExtensions
|
||||
builder.Services.TryAddSingleton<IBrowser, BrowserService>();
|
||||
builder.Services.TryAddSingleton<IEmail, EmailService>();
|
||||
|
||||
// Register GTK host service
|
||||
builder.Services.TryAddSingleton(_ => GtkHostService.Instance);
|
||||
|
||||
// Register type converters for XAML support
|
||||
RegisterTypeConverters();
|
||||
|
||||
@@ -105,9 +98,6 @@ public static class LinuxMauiAppBuilderExtensions
|
||||
handlers.AddHandler<ImageButton, ImageButtonHandler>();
|
||||
handlers.AddHandler<GraphicsView, GraphicsViewHandler>();
|
||||
|
||||
// Web - use GtkWebViewHandler
|
||||
handlers.AddHandler<WebView, GtkWebViewHandler>();
|
||||
|
||||
// Collection Views
|
||||
handlers.AddHandler<CollectionView, CollectionViewHandler>();
|
||||
handlers.AddHandler<ListView, CollectionViewHandler>();
|
||||
@@ -131,11 +121,33 @@ public static class LinuxMauiAppBuilderExtensions
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers custom type converters for Linux platform.
|
||||
/// </summary>
|
||||
private static void RegisterTypeConverters()
|
||||
{
|
||||
// Register SkiaSharp type converters for XAML styling support
|
||||
TypeDescriptor.AddAttributes(typeof(SKColor), new TypeConverterAttribute(typeof(SKColorTypeConverter)));
|
||||
TypeDescriptor.AddAttributes(typeof(SKRect), new TypeConverterAttribute(typeof(SKRectTypeConverter)));
|
||||
TypeDescriptor.AddAttributes(typeof(SKSize), new TypeConverterAttribute(typeof(SKSizeTypeConverter)));
|
||||
TypeDescriptor.AddAttributes(typeof(SKPoint), new TypeConverterAttribute(typeof(SKPointTypeConverter)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler registration extensions.
|
||||
/// </summary>
|
||||
public static class HandlerMappingExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a handler for the specified view type.
|
||||
/// </summary>
|
||||
public static IMauiHandlersCollection AddHandler<TView, THandler>(
|
||||
this IMauiHandlersCollection handlers)
|
||||
where TView : class
|
||||
where THandler : class
|
||||
{
|
||||
handlers.AddHandler(typeof(TView), typeof(THandler));
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,15 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Maui.Animations;
|
||||
using Microsoft.Maui.Dispatching;
|
||||
using Microsoft.Maui.Platform.Linux.Dispatching;
|
||||
using Microsoft.Maui.Platform;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Hosting;
|
||||
|
||||
/// <summary>
|
||||
/// Linux-specific implementation of IMauiContext.
|
||||
/// Provides the infrastructure for creating handlers and accessing platform services.
|
||||
/// </summary>
|
||||
public class LinuxMauiContext : IMauiContext
|
||||
{
|
||||
private readonly IServiceProvider _services;
|
||||
@@ -16,12 +21,27 @@ public class LinuxMauiContext : IMauiContext
|
||||
private IAnimationManager? _animationManager;
|
||||
private IDispatcher? _dispatcher;
|
||||
|
||||
public LinuxMauiContext(IServiceProvider services, LinuxApplication linuxApp)
|
||||
{
|
||||
_services = services ?? throw new ArgumentNullException(nameof(services));
|
||||
_linuxApp = linuxApp ?? throw new ArgumentNullException(nameof(linuxApp));
|
||||
_handlers = services.GetRequiredService<IMauiHandlersFactory>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IServiceProvider Services => _services;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IMauiHandlersFactory Handlers => _handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Linux application instance.
|
||||
/// </summary>
|
||||
public LinuxApplication LinuxApp => _linuxApp;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the animation manager.
|
||||
/// </summary>
|
||||
public IAnimationManager AnimationManager
|
||||
{
|
||||
get
|
||||
@@ -32,6 +52,9 @@ public class LinuxMauiContext : IMauiContext
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dispatcher for UI thread operations.
|
||||
/// </summary>
|
||||
public IDispatcher Dispatcher
|
||||
{
|
||||
get
|
||||
@@ -41,11 +64,236 @@ public class LinuxMauiContext : IMauiContext
|
||||
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));
|
||||
_linuxApp = linuxApp ?? throw new ArgumentNullException(nameof(linuxApp));
|
||||
_handlers = services.GetRequiredService<IMauiHandlersFactory>();
|
||||
_parent = parent ?? throw new ArgumentNullException(nameof(parent));
|
||||
}
|
||||
|
||||
public IServiceProvider Services => _parent.Services;
|
||||
public IMauiHandlersFactory Handlers => _parent.Handlers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linux dispatcher for UI thread operations.
|
||||
/// </summary>
|
||||
internal class LinuxDispatcher : IDispatcher
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly Queue<Action> _queue = new();
|
||||
private bool _isDispatching;
|
||||
|
||||
public bool IsDispatchRequired => false; // Linux uses single-threaded event loop
|
||||
|
||||
public IDispatcherTimer CreateTimer()
|
||||
{
|
||||
return new LinuxDispatcherTimer();
|
||||
}
|
||||
|
||||
public bool Dispatch(Action action)
|
||||
{
|
||||
if (action == null)
|
||||
return false;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_queue.Enqueue(action);
|
||||
}
|
||||
|
||||
ProcessQueue();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool DispatchDelayed(TimeSpan delay, Action action)
|
||||
{
|
||||
if (action == null)
|
||||
return false;
|
||||
|
||||
Task.Delay(delay).ContinueWith(_ => Dispatch(action));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ProcessQueue()
|
||||
{
|
||||
if (_isDispatching)
|
||||
return;
|
||||
|
||||
_isDispatching = true;
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Action? action;
|
||||
lock (_lock)
|
||||
{
|
||||
if (_queue.Count == 0)
|
||||
break;
|
||||
action = _queue.Dequeue();
|
||||
}
|
||||
action?.Invoke();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isDispatching = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linux dispatcher timer implementation.
|
||||
/// </summary>
|
||||
internal class LinuxDispatcherTimer : IDispatcherTimer
|
||||
{
|
||||
private Timer? _timer;
|
||||
private TimeSpan _interval = TimeSpan.FromMilliseconds(16); // ~60fps default
|
||||
private bool _isRunning;
|
||||
private bool _isRepeating = true;
|
||||
|
||||
public TimeSpan Interval
|
||||
{
|
||||
get => _interval;
|
||||
set => _interval = value;
|
||||
}
|
||||
|
||||
public bool IsRunning => _isRunning;
|
||||
|
||||
public bool IsRepeating
|
||||
{
|
||||
get => _isRepeating;
|
||||
set => _isRepeating = value;
|
||||
}
|
||||
|
||||
public event EventHandler? Tick;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (_isRunning)
|
||||
return;
|
||||
|
||||
_isRunning = true;
|
||||
_timer = new Timer(OnTimerCallback, null, _interval, _isRepeating ? _interval : Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_isRunning = false;
|
||||
_timer?.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
|
||||
private void OnTimerCallback(object? state)
|
||||
{
|
||||
Tick?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
if (!_isRepeating)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linux animation manager.
|
||||
/// </summary>
|
||||
internal class LinuxAnimationManager : IAnimationManager
|
||||
{
|
||||
private readonly List<Microsoft.Maui.Animations.Animation> _animations = new();
|
||||
private readonly ITicker _ticker;
|
||||
|
||||
public LinuxAnimationManager(ITicker ticker)
|
||||
{
|
||||
_ticker = ticker;
|
||||
_ticker.Fire = OnTickerFire;
|
||||
}
|
||||
|
||||
public double SpeedModifier { get; set; } = 1.0;
|
||||
public bool AutoStartTicker { get; set; } = true;
|
||||
|
||||
public ITicker Ticker => _ticker;
|
||||
|
||||
public void Add(Microsoft.Maui.Animations.Animation animation)
|
||||
{
|
||||
_animations.Add(animation);
|
||||
|
||||
if (AutoStartTicker && !_ticker.IsRunning)
|
||||
{
|
||||
_ticker.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(Microsoft.Maui.Animations.Animation animation)
|
||||
{
|
||||
_animations.Remove(animation);
|
||||
|
||||
if (_animations.Count == 0 && _ticker.IsRunning)
|
||||
{
|
||||
_ticker.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTickerFire()
|
||||
{
|
||||
var animations = _animations.ToArray();
|
||||
foreach (var animation in animations)
|
||||
{
|
||||
animation.Tick(16.0 / 1000.0 * SpeedModifier); // ~60fps
|
||||
if (animation.HasFinished)
|
||||
{
|
||||
Remove(animation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linux ticker for animation timing.
|
||||
/// </summary>
|
||||
internal class LinuxTicker : ITicker
|
||||
{
|
||||
private Timer? _timer;
|
||||
private bool _isRunning;
|
||||
private int _maxFps = 60;
|
||||
|
||||
public bool IsRunning => _isRunning;
|
||||
|
||||
public bool SystemEnabled => true;
|
||||
|
||||
public int MaxFps
|
||||
{
|
||||
get => _maxFps;
|
||||
set => _maxFps = Math.Max(1, Math.Min(120, value));
|
||||
}
|
||||
|
||||
public Action? Fire { get; set; }
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (_isRunning)
|
||||
return;
|
||||
|
||||
_isRunning = true;
|
||||
var interval = TimeSpan.FromMilliseconds(1000.0 / _maxFps);
|
||||
_timer = new Timer(OnTimerCallback, null, TimeSpan.Zero, interval);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_isRunning = false;
|
||||
_timer?.Dispose();
|
||||
_timer = null;
|
||||
}
|
||||
|
||||
private void OnTimerCallback(object? state)
|
||||
{
|
||||
Fire?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Controls.Hosting;
|
||||
using Microsoft.Maui.Hosting;
|
||||
using Microsoft.Maui.Platform.Linux.Services;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Platform;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Hosting;
|
||||
@@ -45,10 +44,6 @@ public static class LinuxProgramHost
|
||||
?? new LinuxApplicationOptions();
|
||||
ParseCommandLineOptions(args, options);
|
||||
|
||||
// Initialize GTK for WebView support
|
||||
GtkHostService.Instance.Initialize(options.Title ?? "MAUI Application", options.Width, options.Height);
|
||||
Console.WriteLine("[LinuxProgramHost] GTK initialized for WebView support");
|
||||
|
||||
// Create Linux application
|
||||
using var linuxApp = new LinuxApplication();
|
||||
linuxApp.Initialize(options);
|
||||
|
||||
@@ -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.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Reflection;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using Microsoft.Maui.Platform;
|
||||
using SkiaSharp;
|
||||
|
||||
@@ -200,27 +198,8 @@ public class LinuxViewRenderer
|
||||
FlyoutBehavior.Locked => ShellFlyoutBehavior.Locked,
|
||||
FlyoutBehavior.Disabled => ShellFlyoutBehavior.Disabled,
|
||||
_ => ShellFlyoutBehavior.Flyout
|
||||
},
|
||||
MauiShell = shell
|
||||
};
|
||||
|
||||
// Apply shell colors based on theme
|
||||
ApplyShellColors(skiaShell, shell);
|
||||
|
||||
// Render flyout header if present
|
||||
if (shell.FlyoutHeader is View headerView)
|
||||
{
|
||||
var skiaHeader = RenderView(headerView);
|
||||
if (skiaHeader != null)
|
||||
{
|
||||
skiaShell.FlyoutHeaderView = skiaHeader;
|
||||
skiaShell.FlyoutHeaderHeight = (float)(headerView.HeightRequest > 0 ? headerView.HeightRequest : 140.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Set flyout footer with version info
|
||||
var version = Assembly.GetEntryAssembly()?.GetName().Version;
|
||||
skiaShell.FlyoutFooterText = $"Version {version?.Major ?? 1}.{version?.Minor ?? 0}.{version?.Build ?? 0}";
|
||||
};
|
||||
|
||||
// Process shell items into sections
|
||||
foreach (var item in shell.Items)
|
||||
@@ -231,10 +210,6 @@ public class LinuxViewRenderer
|
||||
// Store reference to SkiaShell for navigation
|
||||
CurrentSkiaShell = skiaShell;
|
||||
|
||||
// Set up content renderer and color refresher delegates
|
||||
skiaShell.ContentRenderer = CreateShellContentPage;
|
||||
skiaShell.ColorRefresher = ApplyShellColors;
|
||||
|
||||
// Subscribe to MAUI Shell navigation events to update SkiaShell
|
||||
shell.Navigated += OnShellNavigated;
|
||||
shell.Navigating += (s, e) => Console.WriteLine($"[Navigation] Navigating: {e.Target}");
|
||||
@@ -248,61 +223,6 @@ public class LinuxViewRenderer
|
||||
return skiaShell;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies shell colors based on the current theme (dark/light mode).
|
||||
/// </summary>
|
||||
private static void ApplyShellColors(SkiaShell skiaShell, Shell shell)
|
||||
{
|
||||
bool isDark = Application.Current?.UserAppTheme == AppTheme.Dark;
|
||||
Console.WriteLine($"[ApplyShellColors] Theme is: {(isDark ? "Dark" : "Light")}");
|
||||
|
||||
// Flyout background color
|
||||
if (shell.FlyoutBackgroundColor != null && shell.FlyoutBackgroundColor != Colors.Transparent)
|
||||
{
|
||||
var color = shell.FlyoutBackgroundColor;
|
||||
skiaShell.FlyoutBackgroundColor = new SKColor(
|
||||
(byte)(color.Red * 255f),
|
||||
(byte)(color.Green * 255f),
|
||||
(byte)(color.Blue * 255f),
|
||||
(byte)(color.Alpha * 255f));
|
||||
Console.WriteLine($"[ApplyShellColors] FlyoutBackgroundColor from MAUI: {skiaShell.FlyoutBackgroundColor}");
|
||||
}
|
||||
else
|
||||
{
|
||||
skiaShell.FlyoutBackgroundColor = isDark
|
||||
? new SKColor(30, 30, 30)
|
||||
: new SKColor(255, 255, 255);
|
||||
Console.WriteLine($"[ApplyShellColors] Using default FlyoutBackgroundColor: {skiaShell.FlyoutBackgroundColor}");
|
||||
}
|
||||
|
||||
// Flyout text color
|
||||
skiaShell.FlyoutTextColor = isDark
|
||||
? new SKColor(224, 224, 224)
|
||||
: new SKColor(33, 33, 33);
|
||||
Console.WriteLine($"[ApplyShellColors] FlyoutTextColor: {skiaShell.FlyoutTextColor}");
|
||||
|
||||
// Content background color
|
||||
skiaShell.ContentBackgroundColor = isDark
|
||||
? new SKColor(18, 18, 18)
|
||||
: new SKColor(250, 250, 250);
|
||||
Console.WriteLine($"[ApplyShellColors] ContentBackgroundColor: {skiaShell.ContentBackgroundColor}");
|
||||
|
||||
// NavBar background color
|
||||
if (shell.BackgroundColor != null && shell.BackgroundColor != Colors.Transparent)
|
||||
{
|
||||
var color = shell.BackgroundColor;
|
||||
skiaShell.NavBarBackgroundColor = new SKColor(
|
||||
(byte)(color.Red * 255f),
|
||||
(byte)(color.Green * 255f),
|
||||
(byte)(color.Blue * 255f),
|
||||
(byte)(color.Alpha * 255f));
|
||||
}
|
||||
else
|
||||
{
|
||||
skiaShell.NavBarBackgroundColor = new SKColor(33, 150, 243); // Material blue
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles MAUI Shell navigation events and updates SkiaShell accordingly.
|
||||
/// </summary>
|
||||
@@ -370,8 +290,7 @@ public class LinuxViewRenderer
|
||||
var shellContent = new ShellContent
|
||||
{
|
||||
Title = content.Title ?? shellSection.Title ?? flyoutItem.Title ?? "",
|
||||
Route = content.Route ?? "",
|
||||
MauiShellContent = content
|
||||
Route = content.Route ?? ""
|
||||
};
|
||||
|
||||
// Create the page content
|
||||
@@ -409,8 +328,7 @@ public class LinuxViewRenderer
|
||||
var shellContent = new ShellContent
|
||||
{
|
||||
Title = content.Title ?? tab.Title ?? "",
|
||||
Route = content.Route ?? "",
|
||||
MauiShellContent = content
|
||||
Route = content.Route ?? ""
|
||||
};
|
||||
|
||||
var pageContent = CreateShellContentPage(content);
|
||||
@@ -441,8 +359,7 @@ public class LinuxViewRenderer
|
||||
var shellContent = new ShellContent
|
||||
{
|
||||
Title = content.Title ?? "",
|
||||
Route = content.Route ?? "",
|
||||
MauiShellContent = content
|
||||
Route = content.Route ?? ""
|
||||
};
|
||||
|
||||
var pageContent = CreateShellContentPage(content);
|
||||
@@ -485,38 +402,17 @@ public class LinuxViewRenderer
|
||||
var contentView = RenderView(cp.Content);
|
||||
if (contentView != null)
|
||||
{
|
||||
// Get page background color if set
|
||||
SKColor? bgColor = null;
|
||||
if (cp.BackgroundColor != null && cp.BackgroundColor != Colors.Transparent)
|
||||
if (contentView is SkiaScrollView)
|
||||
{
|
||||
var color = cp.BackgroundColor;
|
||||
bgColor = new SKColor(
|
||||
(byte)(color.Red * 255f),
|
||||
(byte)(color.Green * 255f),
|
||||
(byte)(color.Blue * 255f),
|
||||
(byte)(color.Alpha * 255f));
|
||||
Console.WriteLine($"[CreateShellContentPage] Page BackgroundColor: {bgColor}");
|
||||
}
|
||||
|
||||
if (contentView is SkiaScrollView scrollView)
|
||||
{
|
||||
if (bgColor.HasValue)
|
||||
{
|
||||
scrollView.BackgroundColor = bgColor.Value;
|
||||
}
|
||||
return scrollView;
|
||||
return contentView;
|
||||
}
|
||||
else
|
||||
{
|
||||
var newScrollView = new SkiaScrollView
|
||||
var scrollView = new SkiaScrollView
|
||||
{
|
||||
Content = contentView
|
||||
};
|
||||
if (bgColor.HasValue)
|
||||
{
|
||||
newScrollView.BackgroundColor = bgColor.Value;
|
||||
}
|
||||
return newScrollView;
|
||||
return scrollView;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -580,3 +476,22 @@ public class LinuxViewRenderer
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
}
|
||||
226
Interop/X11.cs
226
Interop/X11.cs
@@ -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 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;
|
||||
|
||||
[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 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);
|
||||
|
||||
[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,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,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;
|
||||
}
|
||||
@@ -1,24 +1,12 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Maui.Controls;
|
||||
using Microsoft.Maui.Dispatching;
|
||||
using Microsoft.Maui.Hosting;
|
||||
using Microsoft.Maui.Platform.Linux.Dispatching;
|
||||
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||
using Microsoft.Maui.Platform.Linux.Native;
|
||||
using Microsoft.Maui.Platform.Linux.Rendering;
|
||||
using Microsoft.Maui.Platform.Linux.Services;
|
||||
using Microsoft.Maui.Platform.Linux.Window;
|
||||
using Microsoft.Maui.Platform.Linux.Services;
|
||||
using Microsoft.Maui.Platform;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux;
|
||||
|
||||
@@ -27,114 +15,19 @@ namespace Microsoft.Maui.Platform.Linux;
|
||||
/// </summary>
|
||||
public class LinuxApplication : IDisposable
|
||||
{
|
||||
private static int _invalidateCount;
|
||||
private static int _requestRedrawCount;
|
||||
private static int _drawCount;
|
||||
private static int _gtkThreadId;
|
||||
private static DateTime _lastCounterReset = DateTime.Now;
|
||||
private static bool _isRedrawing;
|
||||
private static int _loopCounter = 0;
|
||||
|
||||
private X11Window? _mainWindow;
|
||||
private GtkHostWindow? _gtkWindow;
|
||||
private SkiaRenderingEngine? _renderingEngine;
|
||||
private SkiaView? _rootView;
|
||||
private SkiaView? _focusedView;
|
||||
private SkiaView? _hoveredView;
|
||||
private SkiaView? _capturedView; // View that has captured pointer events during drag
|
||||
private bool _disposed;
|
||||
private bool _useGtk;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current application instance.
|
||||
/// </summary>
|
||||
public static LinuxApplication? Current { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the application is running in GTK mode.
|
||||
/// </summary>
|
||||
public static bool IsGtkMode => Current?._useGtk ?? false;
|
||||
|
||||
/// <summary>
|
||||
/// Logs an invalidate call for diagnostics.
|
||||
/// </summary>
|
||||
public static void LogInvalidate(string source)
|
||||
{
|
||||
int currentThread = Environment.CurrentManagedThreadId;
|
||||
Interlocked.Increment(ref _invalidateCount);
|
||||
if (currentThread != _gtkThreadId && _gtkThreadId != 0)
|
||||
{
|
||||
Console.WriteLine($"[DIAG] ⚠️ Invalidate from WRONG THREAD! GTK={_gtkThreadId}, Current={currentThread}, Source={source}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a request redraw call for diagnostics.
|
||||
/// </summary>
|
||||
public static void LogRequestRedraw()
|
||||
{
|
||||
int currentThread = Environment.CurrentManagedThreadId;
|
||||
Interlocked.Increment(ref _requestRedrawCount);
|
||||
if (currentThread != _gtkThreadId && _gtkThreadId != 0)
|
||||
{
|
||||
Console.WriteLine($"[DIAG] ⚠️ RequestRedraw from WRONG THREAD! GTK={_gtkThreadId}, Current={currentThread}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void StartHeartbeat()
|
||||
{
|
||||
_gtkThreadId = Environment.CurrentManagedThreadId;
|
||||
Console.WriteLine($"[DIAG] GTK thread ID: {_gtkThreadId}");
|
||||
GLibNative.TimeoutAdd(250, () =>
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
if ((now - _lastCounterReset).TotalSeconds >= 1.0)
|
||||
{
|
||||
int invalidates = Interlocked.Exchange(ref _invalidateCount, 0);
|
||||
int redraws = Interlocked.Exchange(ref _requestRedrawCount, 0);
|
||||
int draws = Interlocked.Exchange(ref _drawCount, 0);
|
||||
Console.WriteLine($"[DIAG] ❤️ Heartbeat | Invalidate={invalidates}/s, RequestRedraw={redraws}/s, Draw={draws}/s");
|
||||
_lastCounterReset = now;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a draw call for diagnostics.
|
||||
/// </summary>
|
||||
public static void LogDraw()
|
||||
{
|
||||
Interlocked.Increment(ref _drawCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests a redraw of the application.
|
||||
/// </summary>
|
||||
public static void RequestRedraw()
|
||||
{
|
||||
LogRequestRedraw();
|
||||
if (_isRedrawing)
|
||||
return;
|
||||
|
||||
_isRedrawing = true;
|
||||
try
|
||||
{
|
||||
if (Current != null && Current._useGtk)
|
||||
{
|
||||
Current._gtkWindow?.RequestRedraw();
|
||||
}
|
||||
else
|
||||
{
|
||||
Current?._renderingEngine?.InvalidateAll();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isRedrawing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the main window.
|
||||
/// </summary>
|
||||
@@ -219,99 +112,84 @@ public class LinuxApplication : IDisposable
|
||||
/// <param name="configure">Optional configuration action.</param>
|
||||
public static void Run(MauiApp app, string[] args, Action<LinuxApplicationOptions>? configure)
|
||||
{
|
||||
// Initialize dispatcher
|
||||
LinuxDispatcher.Initialize();
|
||||
DispatcherProvider.SetCurrent(LinuxDispatcherProvider.Instance);
|
||||
Console.WriteLine("[LinuxApplication] Dispatcher initialized");
|
||||
|
||||
var options = app.Services.GetService<LinuxApplicationOptions>()
|
||||
?? new LinuxApplicationOptions();
|
||||
configure?.Invoke(options);
|
||||
ParseCommandLineOptions(args, options);
|
||||
|
||||
var linuxApp = new LinuxApplication();
|
||||
try
|
||||
using var linuxApp = new LinuxApplication();
|
||||
linuxApp.Initialize(options);
|
||||
|
||||
// Create MAUI context
|
||||
var mauiContext = new Hosting.LinuxMauiContext(app.Services, linuxApp);
|
||||
|
||||
// Get the application and render it
|
||||
var application = app.Services.GetService<IApplication>();
|
||||
SkiaView? rootView = null;
|
||||
|
||||
if (application is Microsoft.Maui.Controls.Application mauiApplication)
|
||||
{
|
||||
linuxApp.Initialize(options);
|
||||
|
||||
// Create MAUI context
|
||||
var mauiContext = new LinuxMauiContext(app.Services, linuxApp);
|
||||
|
||||
// Get the application and render it
|
||||
var application = app.Services.GetService<IApplication>();
|
||||
SkiaView? rootView = null;
|
||||
|
||||
if (application is Application mauiApplication)
|
||||
// Force Application.Current to be this instance
|
||||
// The constructor sets Current = this, but we ensure it here
|
||||
var currentProperty = typeof(Microsoft.Maui.Controls.Application).GetProperty("Current");
|
||||
if (currentProperty != null && currentProperty.CanWrite)
|
||||
{
|
||||
// Force Application.Current to be this instance
|
||||
var currentProperty = typeof(Application).GetProperty("Current");
|
||||
if (currentProperty != null && currentProperty.CanWrite)
|
||||
{
|
||||
currentProperty.SetValue(null, mauiApplication);
|
||||
}
|
||||
|
||||
// Handle theme changes
|
||||
((BindableObject)mauiApplication).PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == "UserAppTheme")
|
||||
{
|
||||
Console.WriteLine($"[LinuxApplication] Theme changed to: {mauiApplication.UserAppTheme}");
|
||||
LinuxViewRenderer.CurrentSkiaShell?.RefreshTheme();
|
||||
linuxApp._renderingEngine?.InvalidateAll();
|
||||
}
|
||||
};
|
||||
|
||||
if (mauiApplication.MainPage != null)
|
||||
{
|
||||
var mainPage = mauiApplication.MainPage;
|
||||
|
||||
var windowsField = typeof(Application).GetField("_windows",
|
||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var windowsList = windowsField?.GetValue(mauiApplication) as List<Microsoft.Maui.Controls.Window>;
|
||||
|
||||
if (windowsList != null && windowsList.Count == 0)
|
||||
{
|
||||
var mauiWindow = new Microsoft.Maui.Controls.Window(mainPage);
|
||||
windowsList.Add(mauiWindow);
|
||||
mauiWindow.Parent = mauiApplication;
|
||||
}
|
||||
else if (windowsList != null && windowsList.Count > 0 && windowsList[0].Page == null)
|
||||
{
|
||||
windowsList[0].Page = mainPage;
|
||||
}
|
||||
|
||||
var renderer = new LinuxViewRenderer(mauiContext);
|
||||
rootView = renderer.RenderPage(mainPage);
|
||||
|
||||
string windowTitle = "OpenMaui App";
|
||||
if (mainPage is NavigationPage navPage)
|
||||
{
|
||||
windowTitle = navPage.Title ?? windowTitle;
|
||||
}
|
||||
else if (mainPage is Shell shell)
|
||||
{
|
||||
windowTitle = shell.Title ?? windowTitle;
|
||||
}
|
||||
else
|
||||
{
|
||||
windowTitle = mainPage.Title ?? windowTitle;
|
||||
}
|
||||
linuxApp.SetWindowTitle(windowTitle);
|
||||
}
|
||||
currentProperty.SetValue(null, mauiApplication);
|
||||
}
|
||||
|
||||
if (rootView == null)
|
||||
if (mauiApplication.MainPage != null)
|
||||
{
|
||||
rootView = LinuxProgramHost.CreateDemoView();
|
||||
}
|
||||
// Create a MAUI Window and add it to the application
|
||||
// This ensures Shell.Current works (it reads from Application.Current.Windows[0].Page)
|
||||
var mainPage = mauiApplication.MainPage;
|
||||
|
||||
linuxApp.RootView = rootView;
|
||||
linuxApp.Run();
|
||||
// Always ensure we have a window with the Shell/Page
|
||||
var windowsField = typeof(Microsoft.Maui.Controls.Application).GetField("_windows",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
var windowsList = windowsField?.GetValue(mauiApplication) as System.Collections.Generic.List<Microsoft.Maui.Controls.Window>;
|
||||
|
||||
if (windowsList != null && windowsList.Count == 0)
|
||||
{
|
||||
var mauiWindow = new Microsoft.Maui.Controls.Window(mainPage);
|
||||
windowsList.Add(mauiWindow);
|
||||
mauiWindow.Parent = mauiApplication;
|
||||
}
|
||||
else if (windowsList != null && windowsList.Count > 0 && windowsList[0].Page == null)
|
||||
{
|
||||
// Window exists but has no page - set it
|
||||
windowsList[0].Page = mainPage;
|
||||
}
|
||||
|
||||
var renderer = new Hosting.LinuxViewRenderer(mauiContext);
|
||||
rootView = renderer.RenderPage(mainPage);
|
||||
|
||||
// Update window title based on app name (NavigationPage.Title takes precedence)
|
||||
string windowTitle = "OpenMaui App";
|
||||
if (mainPage is Microsoft.Maui.Controls.NavigationPage navPage)
|
||||
{
|
||||
// Prefer NavigationPage.Title (app name) over CurrentPage.Title (page name) for window title
|
||||
windowTitle = navPage.Title ?? windowTitle;
|
||||
}
|
||||
else if (mainPage is Microsoft.Maui.Controls.Shell shell)
|
||||
{
|
||||
windowTitle = shell.Title ?? windowTitle;
|
||||
}
|
||||
else
|
||||
{
|
||||
windowTitle = mainPage.Title ?? windowTitle;
|
||||
}
|
||||
linuxApp.SetWindowTitle(windowTitle);
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
||||
// Fallback to demo if no view
|
||||
if (rootView == null)
|
||||
{
|
||||
linuxApp?.Dispose();
|
||||
rootView = Hosting.LinuxProgramHost.CreateDemoView();
|
||||
}
|
||||
|
||||
linuxApp.RootView = rootView;
|
||||
linuxApp.Run();
|
||||
}
|
||||
|
||||
private static void ParseCommandLineOptions(string[] args, LinuxApplicationOptions options)
|
||||
@@ -340,37 +218,16 @@ public class LinuxApplication : IDisposable
|
||||
/// </summary>
|
||||
public void Initialize(LinuxApplicationOptions options)
|
||||
{
|
||||
_useGtk = options.UseGtk;
|
||||
if (_useGtk)
|
||||
{
|
||||
InitializeGtk(options);
|
||||
}
|
||||
else
|
||||
{
|
||||
InitializeX11(options);
|
||||
}
|
||||
RegisterServices();
|
||||
}
|
||||
|
||||
private void InitializeX11(LinuxApplicationOptions options)
|
||||
{
|
||||
// Create the main window
|
||||
_mainWindow = new X11Window(
|
||||
options.Title ?? "MAUI Application",
|
||||
options.Width,
|
||||
options.Height);
|
||||
|
||||
// Set up WebView main window
|
||||
SkiaWebView.SetMainWindow(_mainWindow.Display, _mainWindow.Handle);
|
||||
|
||||
// Set window icon
|
||||
string? iconPath = ResolveIconPath(options.IconPath);
|
||||
if (!string.IsNullOrEmpty(iconPath))
|
||||
{
|
||||
_mainWindow.SetIcon(iconPath);
|
||||
}
|
||||
|
||||
// Create the rendering engine
|
||||
_renderingEngine = new SkiaRenderingEngine(_mainWindow);
|
||||
|
||||
// Wire up events
|
||||
_mainWindow.Resized += OnWindowResized;
|
||||
_mainWindow.Exposed += OnWindowExposed;
|
||||
_mainWindow.KeyDown += OnKeyDown;
|
||||
@@ -381,69 +238,9 @@ public class LinuxApplication : IDisposable
|
||||
_mainWindow.PointerReleased += OnPointerReleased;
|
||||
_mainWindow.Scroll += OnScroll;
|
||||
_mainWindow.CloseRequested += OnCloseRequested;
|
||||
}
|
||||
|
||||
private void InitializeGtk(LinuxApplicationOptions options)
|
||||
{
|
||||
_gtkWindow = GtkHostService.Instance.GetOrCreateHostWindow(
|
||||
options.Title ?? "MAUI Application",
|
||||
options.Width,
|
||||
options.Height);
|
||||
|
||||
string? iconPath = ResolveIconPath(options.IconPath);
|
||||
if (!string.IsNullOrEmpty(iconPath))
|
||||
{
|
||||
GtkHostService.Instance.SetWindowIcon(iconPath);
|
||||
}
|
||||
|
||||
if (_gtkWindow.SkiaSurface != null)
|
||||
{
|
||||
_gtkWindow.SkiaSurface.DrawRequested += OnGtkDrawRequested;
|
||||
_gtkWindow.SkiaSurface.PointerPressed += OnGtkPointerPressed;
|
||||
_gtkWindow.SkiaSurface.PointerReleased += OnGtkPointerReleased;
|
||||
_gtkWindow.SkiaSurface.PointerMoved += OnGtkPointerMoved;
|
||||
_gtkWindow.SkiaSurface.KeyPressed += OnGtkKeyPressed;
|
||||
_gtkWindow.SkiaSurface.KeyReleased += OnGtkKeyReleased;
|
||||
_gtkWindow.SkiaSurface.Scrolled += OnGtkScrolled;
|
||||
_gtkWindow.SkiaSurface.TextInput += OnGtkTextInput;
|
||||
}
|
||||
_gtkWindow.Resized += OnGtkResized;
|
||||
}
|
||||
|
||||
private static string? ResolveIconPath(string? explicitPath)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(explicitPath))
|
||||
{
|
||||
if (Path.IsPathRooted(explicitPath))
|
||||
{
|
||||
return File.Exists(explicitPath) ? explicitPath : null;
|
||||
}
|
||||
string resolved = Path.Combine(AppContext.BaseDirectory, explicitPath);
|
||||
return File.Exists(resolved) ? resolved : null;
|
||||
}
|
||||
|
||||
string baseDir = AppContext.BaseDirectory;
|
||||
|
||||
// Check for appicon.meta (generated icon)
|
||||
string metaPath = Path.Combine(baseDir, "appicon.meta");
|
||||
if (File.Exists(metaPath))
|
||||
{
|
||||
string? generated = MauiIconGenerator.GenerateIcon(metaPath);
|
||||
if (!string.IsNullOrEmpty(generated) && File.Exists(generated))
|
||||
{
|
||||
return generated;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for appicon.png
|
||||
string pngPath = Path.Combine(baseDir, "appicon.png");
|
||||
if (File.Exists(pngPath)) return pngPath;
|
||||
|
||||
// Check for appicon.svg
|
||||
string svgPath = Path.Combine(baseDir, "appicon.svg");
|
||||
if (File.Exists(svgPath)) return svgPath;
|
||||
|
||||
return null;
|
||||
// Register platform services
|
||||
RegisterServices();
|
||||
}
|
||||
|
||||
private void RegisterServices()
|
||||
@@ -464,62 +261,27 @@ public class LinuxApplication : IDisposable
|
||||
/// Shows the main window and runs the event loop.
|
||||
/// </summary>
|
||||
public void Run()
|
||||
{
|
||||
if (_useGtk)
|
||||
{
|
||||
RunGtk();
|
||||
}
|
||||
else
|
||||
{
|
||||
RunX11();
|
||||
}
|
||||
}
|
||||
|
||||
private void RunX11()
|
||||
{
|
||||
if (_mainWindow == null)
|
||||
throw new InvalidOperationException("Application not initialized");
|
||||
|
||||
_mainWindow.Show();
|
||||
|
||||
// Initial render
|
||||
Render();
|
||||
|
||||
Console.WriteLine("[LinuxApplication] Starting event loop");
|
||||
// Run the event loop
|
||||
while (_mainWindow.IsRunning)
|
||||
{
|
||||
_loopCounter++;
|
||||
if (_loopCounter % 1000 == 0)
|
||||
{
|
||||
Console.WriteLine($"[LinuxApplication] Loop iteration {_loopCounter}");
|
||||
}
|
||||
|
||||
_mainWindow.ProcessEvents();
|
||||
SkiaWebView.ProcessGtkEvents();
|
||||
|
||||
// Update animations and render
|
||||
UpdateAnimations();
|
||||
Render();
|
||||
|
||||
// Small delay to prevent 100% CPU usage
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
Console.WriteLine("[LinuxApplication] Event loop ended");
|
||||
}
|
||||
|
||||
private void RunGtk()
|
||||
{
|
||||
if (_gtkWindow == null)
|
||||
throw new InvalidOperationException("Application not initialized");
|
||||
|
||||
StartHeartbeat();
|
||||
PerformGtkLayout(_gtkWindow.Width, _gtkWindow.Height);
|
||||
_gtkWindow.RequestRedraw();
|
||||
_gtkWindow.Run();
|
||||
GtkHostService.Instance.Shutdown();
|
||||
}
|
||||
|
||||
private void PerformGtkLayout(int width, int height)
|
||||
{
|
||||
if (_rootView != null)
|
||||
{
|
||||
_rootView.Measure(new SKSize(width, height));
|
||||
_rootView.Arrange(new SKRect(0, 0, width, height));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAnimations()
|
||||
@@ -596,13 +358,6 @@ public class LinuxApplication : IDisposable
|
||||
|
||||
private void OnPointerMoved(object? sender, PointerEventArgs e)
|
||||
{
|
||||
// Route to context menu if one is active
|
||||
if (LinuxDialogService.HasContextMenu)
|
||||
{
|
||||
LinuxDialogService.ActiveContextMenu?.OnPointerMoved(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Route to dialog if one is active
|
||||
if (LinuxDialogService.HasActiveDialog)
|
||||
{
|
||||
@@ -629,10 +384,6 @@ public class LinuxApplication : IDisposable
|
||||
_hoveredView?.OnPointerExited(e);
|
||||
_hoveredView = hitView;
|
||||
_hoveredView?.OnPointerEntered(e);
|
||||
|
||||
// Update cursor based on view's cursor type
|
||||
CursorType cursor = hitView?.CursorType ?? CursorType.Arrow;
|
||||
_mainWindow?.SetCursor(cursor);
|
||||
}
|
||||
|
||||
hitView?.OnPointerMoved(e);
|
||||
@@ -643,13 +394,6 @@ public class LinuxApplication : IDisposable
|
||||
{
|
||||
Console.WriteLine($"[LinuxApplication] OnPointerPressed at ({e.X}, {e.Y})");
|
||||
|
||||
// Route to context menu if one is active
|
||||
if (LinuxDialogService.HasContextMenu)
|
||||
{
|
||||
LinuxDialogService.ActiveContextMenu?.OnPointerPressed(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Route to dialog if one is active
|
||||
if (LinuxDialogService.HasActiveDialog)
|
||||
{
|
||||
@@ -745,224 +489,6 @@ public class LinuxApplication : IDisposable
|
||||
_mainWindow?.Stop();
|
||||
}
|
||||
|
||||
// GTK Event Handlers
|
||||
private void OnGtkDrawRequested(object? sender, EventArgs e)
|
||||
{
|
||||
Console.WriteLine("[DIAG] >>> OnGtkDrawRequested ENTER");
|
||||
LogDraw();
|
||||
var surface = _gtkWindow?.SkiaSurface;
|
||||
if (surface?.Canvas != null && _rootView != null)
|
||||
{
|
||||
var bgColor = Application.Current?.UserAppTheme == AppTheme.Dark
|
||||
? new SKColor(32, 33, 36)
|
||||
: SKColors.White;
|
||||
surface.Canvas.Clear(bgColor);
|
||||
Console.WriteLine("[DIAG] Drawing rootView...");
|
||||
_rootView.Draw(surface.Canvas);
|
||||
Console.WriteLine("[DIAG] Drawing dialogs...");
|
||||
var bounds = new SKRect(0, 0, surface.Width, surface.Height);
|
||||
LinuxDialogService.DrawDialogs(surface.Canvas, bounds);
|
||||
Console.WriteLine("[DIAG] <<< OnGtkDrawRequested EXIT");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGtkResized(object? sender, (int Width, int Height) size)
|
||||
{
|
||||
PerformGtkLayout(size.Width, size.Height);
|
||||
_gtkWindow?.RequestRedraw();
|
||||
}
|
||||
|
||||
private void OnGtkPointerPressed(object? sender, (double X, double Y, int Button) e)
|
||||
{
|
||||
string buttonName = e.Button == 1 ? "Left" : e.Button == 2 ? "Middle" : e.Button == 3 ? "Right" : $"Unknown({e.Button})";
|
||||
Console.WriteLine($"[LinuxApplication.GTK] PointerPressed at ({e.X:F1}, {e.Y:F1}), Button={e.Button} ({buttonName})");
|
||||
|
||||
if (LinuxDialogService.HasContextMenu)
|
||||
{
|
||||
var button = e.Button == 1 ? PointerButton.Left : e.Button == 2 ? PointerButton.Middle : PointerButton.Right;
|
||||
var args = new PointerEventArgs((float)e.X, (float)e.Y, button);
|
||||
LinuxDialogService.ActiveContextMenu?.OnPointerPressed(args);
|
||||
_gtkWindow?.RequestRedraw();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_rootView == null)
|
||||
{
|
||||
Console.WriteLine("[LinuxApplication.GTK] _rootView is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
var hitView = _rootView.HitTest((float)e.X, (float)e.Y);
|
||||
Console.WriteLine($"[LinuxApplication.GTK] HitView: {hitView?.GetType().Name ?? "null"}");
|
||||
|
||||
if (hitView != null)
|
||||
{
|
||||
if (hitView.IsFocusable && _focusedView != hitView)
|
||||
{
|
||||
_focusedView?.OnFocusLost();
|
||||
_focusedView = hitView;
|
||||
_focusedView.OnFocusGained();
|
||||
}
|
||||
_capturedView = hitView;
|
||||
var button = e.Button == 1 ? PointerButton.Left : e.Button == 2 ? PointerButton.Middle : PointerButton.Right;
|
||||
var args = new PointerEventArgs((float)e.X, (float)e.Y, button);
|
||||
Console.WriteLine("[DIAG] >>> Before OnPointerPressed");
|
||||
hitView.OnPointerPressed(args);
|
||||
Console.WriteLine("[DIAG] <<< After OnPointerPressed, calling RequestRedraw");
|
||||
_gtkWindow?.RequestRedraw();
|
||||
Console.WriteLine("[DIAG] <<< After RequestRedraw, returning from handler");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGtkPointerReleased(object? sender, (double X, double Y, int Button) e)
|
||||
{
|
||||
Console.WriteLine("[DIAG] >>> OnGtkPointerReleased ENTER");
|
||||
if (_rootView == null) return;
|
||||
|
||||
if (_capturedView != null)
|
||||
{
|
||||
var button = e.Button == 1 ? PointerButton.Left : e.Button == 2 ? PointerButton.Middle : PointerButton.Right;
|
||||
var args = new PointerEventArgs((float)e.X, (float)e.Y, button);
|
||||
Console.WriteLine($"[DIAG] Calling OnPointerReleased on {_capturedView.GetType().Name}");
|
||||
_capturedView.OnPointerReleased(args);
|
||||
Console.WriteLine("[DIAG] OnPointerReleased returned");
|
||||
_capturedView = null;
|
||||
_gtkWindow?.RequestRedraw();
|
||||
Console.WriteLine("[DIAG] <<< OnGtkPointerReleased EXIT (captured path)");
|
||||
}
|
||||
else
|
||||
{
|
||||
var hitView = _rootView.HitTest((float)e.X, (float)e.Y);
|
||||
if (hitView != null)
|
||||
{
|
||||
var button = e.Button == 1 ? PointerButton.Left : e.Button == 2 ? PointerButton.Middle : PointerButton.Right;
|
||||
var args = new PointerEventArgs((float)e.X, (float)e.Y, button);
|
||||
hitView.OnPointerReleased(args);
|
||||
_gtkWindow?.RequestRedraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGtkPointerMoved(object? sender, (double X, double Y) e)
|
||||
{
|
||||
if (LinuxDialogService.HasContextMenu)
|
||||
{
|
||||
var args = new PointerEventArgs((float)e.X, (float)e.Y);
|
||||
LinuxDialogService.ActiveContextMenu?.OnPointerMoved(args);
|
||||
_gtkWindow?.RequestRedraw();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_rootView == null) return;
|
||||
|
||||
if (_capturedView != null)
|
||||
{
|
||||
var args = new PointerEventArgs((float)e.X, (float)e.Y);
|
||||
_capturedView.OnPointerMoved(args);
|
||||
_gtkWindow?.RequestRedraw();
|
||||
return;
|
||||
}
|
||||
|
||||
var hitView = _rootView.HitTest((float)e.X, (float)e.Y);
|
||||
if (hitView != _hoveredView)
|
||||
{
|
||||
var args = new PointerEventArgs((float)e.X, (float)e.Y);
|
||||
_hoveredView?.OnPointerExited(args);
|
||||
_hoveredView = hitView;
|
||||
_hoveredView?.OnPointerEntered(args);
|
||||
_gtkWindow?.RequestRedraw();
|
||||
}
|
||||
|
||||
if (hitView != null)
|
||||
{
|
||||
var args = new PointerEventArgs((float)e.X, (float)e.Y);
|
||||
hitView.OnPointerMoved(args);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGtkKeyPressed(object? sender, (uint KeyVal, uint KeyCode, uint State) e)
|
||||
{
|
||||
if (_focusedView != null)
|
||||
{
|
||||
var key = ConvertGdkKey(e.KeyVal);
|
||||
var modifiers = ConvertGdkModifiers(e.State);
|
||||
var args = new KeyEventArgs(key, modifiers);
|
||||
_focusedView.OnKeyDown(args);
|
||||
_gtkWindow?.RequestRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGtkKeyReleased(object? sender, (uint KeyVal, uint KeyCode, uint State) e)
|
||||
{
|
||||
if (_focusedView != null)
|
||||
{
|
||||
var key = ConvertGdkKey(e.KeyVal);
|
||||
var modifiers = ConvertGdkModifiers(e.State);
|
||||
var args = new KeyEventArgs(key, modifiers);
|
||||
_focusedView.OnKeyUp(args);
|
||||
_gtkWindow?.RequestRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGtkScrolled(object? sender, (double X, double Y, double DeltaX, double DeltaY) e)
|
||||
{
|
||||
if (_rootView == null) return;
|
||||
|
||||
var hitView = _rootView.HitTest((float)e.X, (float)e.Y);
|
||||
while (hitView != null)
|
||||
{
|
||||
if (hitView is SkiaScrollView scrollView)
|
||||
{
|
||||
var args = new ScrollEventArgs((float)e.X, (float)e.Y, (float)e.DeltaX, (float)e.DeltaY);
|
||||
scrollView.OnScroll(args);
|
||||
_gtkWindow?.RequestRedraw();
|
||||
break;
|
||||
}
|
||||
hitView = hitView.Parent;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGtkTextInput(object? sender, string text)
|
||||
{
|
||||
if (_focusedView != null)
|
||||
{
|
||||
var args = new TextInputEventArgs(text);
|
||||
_focusedView.OnTextInput(args);
|
||||
_gtkWindow?.RequestRedraw();
|
||||
}
|
||||
}
|
||||
|
||||
private static Key ConvertGdkKey(uint keyval)
|
||||
{
|
||||
return keyval switch
|
||||
{
|
||||
65288 => Key.Backspace,
|
||||
65289 => Key.Tab,
|
||||
65293 => Key.Enter,
|
||||
65307 => Key.Escape,
|
||||
65360 => Key.Home,
|
||||
65361 => Key.Left,
|
||||
65362 => Key.Up,
|
||||
65363 => Key.Right,
|
||||
65364 => Key.Down,
|
||||
65365 => Key.PageUp,
|
||||
65366 => Key.PageDown,
|
||||
65367 => Key.End,
|
||||
65535 => Key.Delete,
|
||||
>= 32 and <= 126 => (Key)keyval,
|
||||
_ => Key.Unknown
|
||||
};
|
||||
}
|
||||
|
||||
private static KeyModifiers ConvertGdkModifiers(uint state)
|
||||
{
|
||||
var modifiers = KeyModifiers.None;
|
||||
if ((state & 1) != 0) modifiers |= KeyModifiers.Shift;
|
||||
if ((state & 4) != 0) modifiers |= KeyModifiers.Control;
|
||||
if ((state & 8) != 0) modifiers |= KeyModifiers.Alt;
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
@@ -977,3 +503,60 @@ public class LinuxApplication : IDisposable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for Linux application initialization.
|
||||
/// </summary>
|
||||
public class LinuxApplicationOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the window title.
|
||||
/// </summary>
|
||||
public string? Title { get; set; } = "MAUI Application";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the initial window width.
|
||||
/// </summary>
|
||||
public int Width { get; set; } = 800;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the initial window height.
|
||||
/// </summary>
|
||||
public int Height { get; set; } = 600;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use hardware acceleration.
|
||||
/// </summary>
|
||||
public bool UseHardwareAcceleration { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the display server type.
|
||||
/// </summary>
|
||||
public DisplayServerType DisplayServer { get; set; } = DisplayServerType.Auto;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to force demo mode instead of loading the application's pages.
|
||||
/// </summary>
|
||||
public bool ForceDemo { get; set; } = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Display server type options.
|
||||
/// </summary>
|
||||
public enum DisplayServerType
|
||||
{
|
||||
/// <summary>
|
||||
/// Automatically detect the display server.
|
||||
/// </summary>
|
||||
Auto,
|
||||
|
||||
/// <summary>
|
||||
/// Use X11 (Xorg).
|
||||
/// </summary>
|
||||
X11,
|
||||
|
||||
/// <summary>
|
||||
/// Use Wayland.
|
||||
/// </summary>
|
||||
Wayland
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
public class LinuxApplicationOptions
|
||||
{
|
||||
public string? Title { get; set; } = "MAUI Application";
|
||||
|
||||
public int Width { get; set; } = 800;
|
||||
|
||||
public int Height { get; set; } = 600;
|
||||
|
||||
public bool UseHardwareAcceleration { get; set; } = true;
|
||||
|
||||
public DisplayServerType DisplayServer { get; set; }
|
||||
|
||||
public bool ForceDemo { get; set; }
|
||||
|
||||
public string? IconPath { get; set; }
|
||||
|
||||
public bool UseGtk { get; set; }
|
||||
}
|
||||
@@ -1,354 +0,0 @@
|
||||
# OpenMaui Linux - Recovery Merge Tracking
|
||||
|
||||
**Branch:** `final`
|
||||
**Last Updated:** 2026-01-01
|
||||
**Build Status:** SUCCEEDS
|
||||
|
||||
---
|
||||
|
||||
## HANDLERS
|
||||
|
||||
**CRITICAL**: All handlers must use namespace `Microsoft.Maui.Platform.Linux.Handlers` and follow decompiled EXACTLY.
|
||||
|
||||
| File | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| ActivityIndicatorHandler.Linux.cs | [x] | **FIXED 2026-01-01** - Removed IsEnabled/BackgroundColor (not in production), fixed namespace |
|
||||
| ApplicationHandler.cs | [x] | **VERIFIED 2026-01-01** - Matches production |
|
||||
| BorderHandler.cs | [x] | **FIXED 2026-01-01** - Added ConnectHandler/DisconnectHandler with MauiView and Tapped event, OnPlatformViewTapped calls GestureManager.ProcessTap |
|
||||
| BoxViewHandler.cs | [x] | **VERIFIED 2026-01-01** - Matches production, Color/CornerRadius/Background/BackgroundColor |
|
||||
| ButtonHandler.Linux.cs | [x] | **FIXED 2026-01-01** - Removed MapText/TextColor/Font (not in production), fixed namespace, added null checks |
|
||||
| CheckBoxHandler.Linux.cs | [x] | **FIXED 2026-01-01** - Added VerticalLayoutAlignment/HorizontalLayoutAlignment, fixed namespace |
|
||||
| CollectionViewHandler.cs | [x] | **VERIFIED 2026-01-01** - Matches production |
|
||||
| DatePickerHandler.cs | [x] | **VERIFIED 2026-01-01** - Matches production, dark theme support |
|
||||
| EditorHandler.Linux.cs | [x] | **CREATED 2026-01-01** - Was missing, created from decompiled |
|
||||
| EntryHandler.Linux.cs | [x] | **FIXED 2026-01-01** - Added CharacterSpacing/ClearButtonVisibility/VerticalTextAlignment, fixed namespace, null checks |
|
||||
| FlexLayoutHandler.cs | [x] | **VERIFIED 2026-01-01** - Matches production |
|
||||
| FlyoutPageHandler.cs | [x] | **VERIFIED 2026-01-01** - Matches production |
|
||||
| FrameHandler.cs | [x] | **FIXED 2026-01-01** - Added ConnectHandler/DisconnectHandler with MauiView and Tapped event, OnPlatformViewTapped calls GestureManager.ProcessTap |
|
||||
| GestureManager.cs | [x] | **VERIFIED 2026-01-01** - Matches production |
|
||||
| GraphicsViewHandler.cs | [x] | **VERIFIED 2026-01-01** - Matches production |
|
||||
| GtkWebViewHandler.cs | [x] | Added new file from decompiled |
|
||||
| GtkWebViewManager.cs | [x] | **VERIFIED 2026-01-01** - Matches production |
|
||||
| GtkWebViewPlatformView.cs | [x] | **VERIFIED 2026-01-01** - Matches production |
|
||||
| GtkWebViewProxy.cs | [x] | Added new file from decompiled |
|
||||
| ImageButtonHandler.cs | [x] | **VERIFIED 2026-01-01** - Matches production, has ImageSourceServiceResultManager |
|
||||
| ImageHandler.Linux.cs | [x] | **VERIFIED 2026-01-01** - Matches production, FontImageSource rendering |
|
||||
| ItemsViewHandler.cs | [x] | **VERIFIED 2026-01-01** - Matches production |
|
||||
| LabelHandler.Linux.cs | [x] | **FIXED 2026-01-01** - Added CharacterSpacing/LayoutAlignment/FormattedText, ConnectHandler gesture logic, fixed namespace |
|
||||
| LayoutHandler.cs | [x] | **VERIFIED 2026-01-01** - Matches production, includes StackLayoutHandler/GridHandler |
|
||||
| NavigationPageHandler.cs | [x] | **VERIFIED 2026-01-01** - Matches production, toolbar items, SVG/PNG icons |
|
||||
| PageHandler.cs | [x] | **VERIFIED 2026-01-01** - Matches production, includes ContentPageHandler |
|
||||
| PickerHandler.Linux.cs | [x] | **CREATED 2026-01-01** - Was missing, created from decompiled with collection changed tracking |
|
||||
| ProgressBarHandler.Linux.cs | [x] | **FIXED 2026-01-01** - Added ConnectHandler/DisconnectHandler IsVisible tracking, fixed namespace |
|
||||
| RadioButtonHandler.Linux.cs | [x] | **VERIFIED 2026-01-01** - Matches production, Content/GroupName/Value in ConnectHandler |
|
||||
| ScrollViewHandler.Linux.cs | [x] | **VERIFIED 2026-01-01** - Matches production, CommandMapper with RequestScrollTo |
|
||||
| SearchBarHandler.Linux.cs | [x] | **FIXED 2026-01-01** - Fixed namespace, added CancelButtonColor, SolidPaint, null checks |
|
||||
| ShellHandler.cs | [x] | **VERIFIED 2026-01-01** - Matches production, navigation event handling |
|
||||
| SliderHandler.Linux.cs | [x] | **FIXED 2026-01-01** - Removed BackgroundColor (use base), fixed namespace, added ConnectHandler init calls |
|
||||
| StepperHandler.Linux.cs | [x] | **VERIFIED 2026-01-01** - Matches production, dark theme support |
|
||||
| SwitchHandler.Linux.cs | [x] | **FIXED 2026-01-01** - Added OffTrackColor logic, fixed namespace, removed extra BackgroundColor |
|
||||
| TabbedPageHandler.cs | [x] | **VERIFIED 2026-01-01** - Matches production, SelectedIndexChanged event |
|
||||
| TimePickerHandler.cs | [x] | **VERIFIED 2026-01-01** - Matches production, dark theme support |
|
||||
| WebViewHandler.cs | [x] | **VERIFIED 2026-01-01** - Matches production |
|
||||
| WindowHandler.cs | [x] | **VERIFIED 2026-01-01** - Matches production, includes SkiaWindow class |
|
||||
|
||||
---
|
||||
|
||||
## VIEWS
|
||||
|
||||
| File | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| SkiaActivityIndicator.cs | [x] | Verified - all TwoWay, logic matches |
|
||||
| SkiaAlertDialog.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, modal dialog rendering |
|
||||
| SkiaBorder.cs | [x] | **FIXED 2026-01-01** - Logic matches, removed embedded SkiaFrame (now separate file) |
|
||||
| SkiaFrame.cs | [x] | **ADDED 2026-01-01** - Created as separate file matching decompiled pattern |
|
||||
| SkiaBoxView.cs | [x] | Verified - all TwoWay, logic matches |
|
||||
| SkiaButton.cs | [x] | Verified - all TwoWay, logic matches |
|
||||
| SkiaCarouselView.cs | [x] | **FIXED 2026-01-01** - Logic matches, removed embedded PositionChangedEventArgs |
|
||||
| PositionChangedEventArgs.cs | [x] | **ADDED 2026-01-01** - Created as separate file matching decompiled |
|
||||
| SkiaCheckBox.cs | [x] | Verified - IsChecked=OneWay, rest TwoWay, logic matches |
|
||||
| SkiaCollectionView.cs | [x] | **FIXED 2026-01-01** - Removed embedded SkiaSelectionMode, ItemsLayoutOrientation |
|
||||
| SkiaSelectionMode.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| ItemsLayoutOrientation.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| SkiaContentPresenter.cs | [x] | **FIXED 2026-01-01** - Removed embedded LayoutAlignment (now separate file) |
|
||||
| LayoutAlignment.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| SkiaContextMenu.cs | [x] | **VERIFIED 2026-01-01** - Logic matches decompiled |
|
||||
| SkiaDatePicker.cs | [x] | **VERIFIED 2026-01-01** - Date=OneWay, all others=TwoWay |
|
||||
| SkiaEditor.cs | [x] | **FIXED 2026-01-01** - All BindingModes corrected (Text=OneWay, others=TwoWay) |
|
||||
| SkiaEntry.cs | [x] | **FIXED 2026-01-01** - TextProperty BindingMode.OneWay, others TwoWay |
|
||||
| SkiaFlexLayout.cs | [x] | **VERIFIED 2026-01-01** - Logic matches decompiled |
|
||||
| SkiaFlyoutPage.cs | [x] | **FIXED 2026-01-01** - Removed embedded FlyoutLayoutBehavior (now separate file) |
|
||||
| FlyoutLayoutBehavior.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| SkiaGraphicsView.cs | [x] | **VERIFIED 2026-01-01** - Logic matches decompiled |
|
||||
| SkiaImage.cs | [x] | **VERIFIED 2026-01-01** - No BindableProperties, logic matches |
|
||||
| SkiaImageButton.cs | [x] | **FIXED 2026-01-01** - Added SVG support, multi-path search matching decompiled |
|
||||
| SkiaIndicatorView.cs | [x] | **FIXED 2026-01-01** - Removed embedded IndicatorShape (now separate file) |
|
||||
| IndicatorShape.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| SkiaItemsView.cs | [x] | Added GetItemView() method |
|
||||
| SkiaLabel.cs | [x] | **FIXED 2026-01-01** - All BindingModes TwoWay |
|
||||
| SkiaLayoutView.cs | [x] | **FIXED 2026-01-01** - All BindingModes TwoWay (Spacing, Padding, ClipToBounds, Orientation, RowSpacing, ColumnSpacing) |
|
||||
| SkiaMenuBar.cs | [x] | **FIXED 2026-01-01** - Removed embedded MenuBarItem, MenuItem, SkiaMenuFlyout, MenuItemClickedEventArgs |
|
||||
| MenuBarItem.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| MenuItem.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| SkiaMenuFlyout.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| MenuItemClickedEventArgs.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| SkiaNavigationPage.cs | [x] | **FIXED 2026-01-01** - Added LinuxApplication.IsGtkMode check, removed embedded NavigationEventArgs |
|
||||
| NavigationEventArgs.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| SkiaPage.cs | [x] | Added SkiaToolbarItem.Icon property |
|
||||
| SkiaPicker.cs | [x] | FIXED - SelectedIndex=OneWay, all others=TwoWay (was missing) |
|
||||
| SkiaProgressBar.cs | [x] | Verified - Progress=OneWay, rest TwoWay, logic matches |
|
||||
| SkiaRadioButton.cs | [x] | **FIXED 2026-01-01** - IsChecked=OneWay, all others=TwoWay |
|
||||
| SkiaRefreshView.cs | [x] | **FIXED 2026-01-01** - Added ICommand support (Command, CommandParameter) matching decompiled |
|
||||
| SkiaScrollView.cs | [x] | **FIXED 2026-01-01** - All BindingModes TwoWay |
|
||||
| SkiaSearchBar.cs | [x] | **VERIFIED 2026-01-01** - No BindableProperties, logic matches |
|
||||
| SkiaShell.cs | [x] | **FIXED 2026-01-01** - Added FlyoutTextColor, ContentBackgroundColor, route registration, query parameters, OnScroll |
|
||||
| SkiaSlider.cs | [x] | FIXED - Value=OneWay, rest TwoWay (agent had inverted all) |
|
||||
| SkiaStepper.cs | [x] | **FIXED 2026-01-01** - Value=OneWay, all others=TwoWay |
|
||||
| SkiaSwipeView.cs | [x] | **FIXED 2026-01-01** - Removed embedded SwipeItem, SwipeDirection, SwipeMode, SwipeStartedEventArgs, SwipeEndedEventArgs |
|
||||
| SwipeItem.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| SwipeDirection.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| SwipeMode.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| SwipeStartedEventArgs.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| SwipeEndedEventArgs.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| SkiaSwitch.cs | [x] | FIXED - IsOn=OneWay (agent had TwoWay) |
|
||||
| SkiaTabbedPage.cs | [x] | **FIXED 2026-01-01** - Removed embedded TabItem (now separate file) |
|
||||
| TabItem.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| SkiaTemplatedView.cs | [x] | **FIXED 2026-01-01** - Added missing using statements (Shapes, Graphics) |
|
||||
| SkiaTimePicker.cs | [x] | **FIXED 2026-01-01** - Time=OneWay, all others=TwoWay |
|
||||
| SkiaView.cs | [x] | Made Arrange() virtual |
|
||||
| SkiaVisualStateManager.cs | [x] | **FIXED 2026-01-01** - Removed embedded SkiaVisualStateGroupList, SkiaVisualStateGroup, SkiaVisualState, SkiaVisualStateSetter |
|
||||
| SkiaVisualStateGroupList.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| SkiaVisualStateGroup.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| SkiaVisualState.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| SkiaVisualStateSetter.cs | [x] | **ADDED 2026-01-01** - Created as separate file |
|
||||
| SkiaWebView.cs | [x] | **FIXED 2026-01-01** - Full X11 embedding, position tracking, hardware accel, load callbacks |
|
||||
|
||||
---
|
||||
|
||||
## SERVICES
|
||||
|
||||
| File | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| AccessibilityServiceFactory.cs | [x] | **FIXED 2026-01-01** - Fixed CreateService() to call Initialize(), added Reset() |
|
||||
| AccessibleAction.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| AccessibleProperty.cs | [x] | **FIXED 2026-01-01** - Fixed to match decompiled (Name,Description,Role,Value,Parent,Children) |
|
||||
| AccessibleRect.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| AccessibleRole.cs | [x] | **FIXED 2026-01-01** - Fixed to match decompiled (simplified list with Button,Tab,TabPanel) |
|
||||
| AccessibleState.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| AccessibleStates.cs | [x] | **FIXED 2026-01-01** - Fixed to match decompiled (MultiSelectable capital S) |
|
||||
| AnnouncementPriority.cs | [x] | **FIXED 2026-01-01** - Fixed to match decompiled (Polite,Assertive) |
|
||||
| AppActionsService.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, main has clean string interpolation |
|
||||
| AppInfoService.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, main has clean enum names |
|
||||
| AtSpi2AccessibilityService.cs | [x] | **FIXED 2026-01-01** - Removed embedded AccessibilityServiceFactory, NullAccessibilityService |
|
||||
| BrowserService.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, main has clean nameof/interpolation/enums |
|
||||
| ClipboardService.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, xclip/xsel fallback |
|
||||
| ColorDialogResult.cs | [x] | **FIXED 2026-01-01** - Fixed to match decompiled |
|
||||
| ConnectivityService.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, main has clean enum names |
|
||||
| DesktopEnvironment.cs | [x] | **FIXED 2026-01-01** - Fixed to match decompiled (GNOME uppercase) |
|
||||
| DeviceDisplayService.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, main has clean enum names |
|
||||
| DeviceInfoService.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, main has clean enum names |
|
||||
| DisplayServerFactory.cs | [x] | **FIXED 2026-01-01** - Removed embedded DisplayServerType, IDisplayWindow, X11DisplayWindow, WaylandDisplayWindow |
|
||||
| DisplayServerType.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| DragAction.cs | [x] | **FIXED 2026-01-01** - Fixed to match decompiled |
|
||||
| DragData.cs | [x] | **FIXED 2026-01-01** - Fixed to match decompiled |
|
||||
| DragDropService.cs | [x] | **FIXED 2026-01-01** - Removed embedded DragData, DragEventArgs, DropEventArgs, DragAction |
|
||||
| DragEventArgs.cs | [x] | **FIXED 2026-01-01** - Fixed to match decompiled |
|
||||
| DropEventArgs.cs | [x] | **FIXED 2026-01-01** - Fixed to match decompiled |
|
||||
| EmailService.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, main has clean nameof/interpolation |
|
||||
| Fcitx5InputMethodService.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, D-Bus interface |
|
||||
| FileDialogResult.cs | [x] | **FIXED 2026-01-01** - Fixed to match decompiled |
|
||||
| FilePickerService.cs | [x] | **FIXED 2026-01-01** - Removed embedded LinuxFileResult |
|
||||
| FolderPickerOptions.cs | [x] | **FIXED 2026-01-01** - Fixed to match decompiled |
|
||||
| FolderPickerResult.cs | [x] | **FIXED 2026-01-01** - Fixed to match decompiled |
|
||||
| FolderPickerService.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, zenity/kdialog fallback |
|
||||
| FolderResult.cs | [x] | **FIXED 2026-01-01** - Fixed to match decompiled |
|
||||
| FontFallbackManager.cs | [x] | **FIXED 2026-01-01** - Removed embedded TextRun |
|
||||
| GlobalHotkeyService.cs | [x] | **FIXED 2026-01-01** - Removed embedded HotkeyEventArgs, HotkeyModifiers, HotkeyKey |
|
||||
| Gtk4InteropService.cs | [x] | **FIXED 2026-01-01** - Removed embedded GtkResponseType, GtkMessageType, GtkButtonsType, GtkFileChooserAction, FileDialogResult, ColorDialogResult |
|
||||
| GtkButtonsType.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| GtkContextMenuService.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, GTK P/Invoke |
|
||||
| GtkFileChooserAction.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| GtkHostService.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, main has clean ??= syntax |
|
||||
| GtkMenuItem.cs | [x] | **VERIFIED 2026-01-01** - Identical logic |
|
||||
| GtkMessageType.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| GtkResponseType.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| HardwareVideoService.cs | [x] | **FIXED 2026-01-01** - Removed embedded VideoAccelerationApi, VideoProfile, VideoFrame |
|
||||
| HiDpiService.cs | [x] | **FIXED 2026-01-01** - Removed embedded ScaleChangedEventArgs |
|
||||
| HighContrastChangedEventArgs.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| HighContrastColors.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| HighContrastService.cs | [x] | **FIXED 2026-01-01** - Removed embedded HighContrastTheme, HighContrastColors, HighContrastChangedEventArgs |
|
||||
| HighContrastTheme.cs | [x] | **FIXED 2026-01-01** - Fixed to match decompiled (None,WhiteOnBlack,BlackOnWhite) |
|
||||
| HotkeyEventArgs.cs | [x] | **FIXED 2026-01-01** - Fixed constructor order (int id, HotkeyKey key, HotkeyModifiers modifiers) |
|
||||
| HotkeyKey.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| HotkeyModifiers.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| IAccessibilityService.cs | [x] | **FIXED 2026-01-01** - Removed many embedded types |
|
||||
| IAccessible.cs | [x] | **FIXED 2026-01-01** - Fixed to match decompiled exactly |
|
||||
| IAccessibleEditableText.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| IAccessibleText.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| IBusInputMethodService.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, IBus D-Bus interface |
|
||||
| IDisplayWindow.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| IInputContext.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| IInputMethodService.cs | [x] | **FIXED 2026-01-01** - Removed embedded IInputContext, TextCommittedEventArgs, PreEditChangedEventArgs, PreEditAttribute, PreEditAttributeType, KeyModifiers |
|
||||
| InputMethodServiceFactory.cs | [x] | **FIXED 2026-01-01** - Removed embedded NullInputMethodService |
|
||||
| KeyModifiers.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| LauncherService.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, xdg-open |
|
||||
| LinuxFileResult.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| LinuxResourcesProvider.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, system styles |
|
||||
| MauiIconGenerator.cs | [x] | **FIXED 2026-01-01** - Added Svg.Skia, SVG foreground, Scale metadata |
|
||||
| NotificationAction.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| NotificationActionEventArgs.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| NotificationClosedEventArgs.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| NotificationCloseReason.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| NotificationContext.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| NotificationOptions.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| NotificationService.cs | [x] | **FIXED 2026-01-01** - Removed embedded NotificationOptions, NotificationUrgency, NotificationCloseReason, NotificationContext, NotificationActionEventArgs, NotificationClosedEventArgs, NotificationAction |
|
||||
| NotificationUrgency.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| NullAccessibilityService.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| NullInputMethodService.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| PortalFilePickerService.cs | [x] | **FIXED 2026-01-01** - Removed embedded FolderResult, FolderPickerResult, FolderPickerOptions, PortalFolderPickerService |
|
||||
| PortalFolderPickerService.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| PreEditAttribute.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| PreEditAttributeType.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| PreEditChangedEventArgs.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| PreferencesService.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, JSON file storage with XDG |
|
||||
| ScaleChangedEventArgs.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| SecureStorageService.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, secret-tool with AES fallback |
|
||||
| ShareService.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, xdg-open with portal fallback |
|
||||
| SystemColors.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| SystemTheme.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| SystemThemeService.cs | [x] | **FIXED 2026-01-01** - Removed embedded SystemTheme, DesktopEnvironment, ThemeChangedEventArgs, SystemColors |
|
||||
| SystemTrayService.cs | [x] | **FIXED 2026-01-01** - Removed embedded TrayMenuItem |
|
||||
| TextCommittedEventArgs.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| TextRun.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| ThemeChangedEventArgs.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| TrayMenuItem.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| VersionTrackingService.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, JSON tracking file |
|
||||
| VideoAccelerationApi.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| VideoFrame.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| VideoProfile.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| VirtualizationExtensions.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled |
|
||||
| VirtualizationManager.cs | [x] | **FIXED 2026-01-01** - Removed embedded VirtualizationExtensions |
|
||||
| WaylandDisplayWindow.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| X11DisplayWindow.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| X11InputMethodService.cs | [x] | **VERIFIED 2026-01-01** - Logic matches, X11 XIM interface |
|
||||
|
||||
---
|
||||
|
||||
## HOSTING
|
||||
|
||||
| File | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| GtkMauiContext.cs | [x] | **ADDED 2026-01-01** - Was missing, created from decompiled |
|
||||
| HandlerMappingExtensions.cs | [x] | **ADDED 2026-01-01** - Was missing, created from decompiled |
|
||||
| LinuxAnimationManager.cs | [x] | **ADDED 2026-01-01** - Was missing, created from decompiled |
|
||||
| LinuxMauiAppBuilderExtensions.cs | [x] | **FIXED 2026-01-01** - Added IDispatcherProvider, IDeviceInfo, IDeviceDisplay, IAppInfo, IConnectivity, GtkHostService registrations; fixed WebView to use GtkWebViewHandler |
|
||||
| LinuxMauiContext.cs | [x] | **FIXED 2026-01-01** - Removed embedded classes (now separate files), added LinuxDispatcher using |
|
||||
| LinuxProgramHost.cs | [x] | **FIXED 2026-01-01** - Added GtkHostService.Initialize call for WebView support |
|
||||
| LinuxTicker.cs | [x] | **ADDED 2026-01-01** - Was missing, created from decompiled |
|
||||
| LinuxViewRenderer.cs | [x] | **FIXED 2026-01-01** - Added ApplyShellColors(), FlyoutHeader rendering, FlyoutFooterText, MauiShellContent, ContentRenderer/ColorRefresher delegates, page BackgroundColor handling |
|
||||
| MauiAppBuilderExtensions.cs | [x] | **DELETED 2026-01-01** - Not in decompiled, was outdated duplicate with wrong namespace |
|
||||
| MauiHandlerExtensions.cs | [x] | **FIXED 2026-01-01** - Fixed WebView to use GtkWebViewHandler, FlexLayout to use LayoutHandler |
|
||||
| ScopedLinuxMauiContext.cs | [x] | **ADDED 2026-01-01** - Was missing, created from decompiled |
|
||||
|
||||
---
|
||||
|
||||
## DISPATCHING
|
||||
|
||||
| File | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| LinuxDispatcher.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled, clean syntax |
|
||||
| LinuxDispatcherProvider.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled |
|
||||
| LinuxDispatcherTimer.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled, clean syntax |
|
||||
|
||||
---
|
||||
|
||||
## NATIVE
|
||||
|
||||
| File | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| CairoNative.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled |
|
||||
| GdkNative.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled |
|
||||
| GLibNative.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled |
|
||||
| GtkNative.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled |
|
||||
| WebKitNative.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled |
|
||||
|
||||
---
|
||||
|
||||
## WINDOW
|
||||
|
||||
| File | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| CursorType.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled |
|
||||
| GtkHostWindow.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled, main has clean comments |
|
||||
| WaylandWindow.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled, main has clean comments |
|
||||
| X11Window.cs | [x] | **FIXED 2026-01-01** - Added SVG icon support, event counter logging from decompiled |
|
||||
|
||||
---
|
||||
|
||||
## RENDERING
|
||||
|
||||
| File | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| GpuRenderingEngine.cs | [x] | **FIXED 2026-01-01** - Removed embedded GpuStats |
|
||||
| GpuStats.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled |
|
||||
| GtkSkiaSurfaceWidget.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled, same public API |
|
||||
| LayeredRenderer.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled |
|
||||
| RenderCache.cs | [x] | **FIXED 2026-01-01** - Removed embedded LayeredRenderer, RenderLayer, TextRenderCache |
|
||||
| RenderLayer.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled |
|
||||
| ResourceCache.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled |
|
||||
| SkiaRenderingEngine.cs | [x] | **FIXED 2026-01-01** - Removed embedded ResourceCache |
|
||||
| TextRenderCache.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled |
|
||||
|
||||
---
|
||||
|
||||
## INTEROP
|
||||
|
||||
| File | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| ClientMessageData.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| WebKitGtk.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled, main has cleaner formatting with regions |
|
||||
| X11.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled, X11Interop.cs DELETED (was duplicate) |
|
||||
| XButtonEvent.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| XClientMessageEvent.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| XConfigureEvent.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| XCrossingEvent.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| XCursorShape.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| XEvent.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| XEventMask.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| XEventType.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| XExposeEvent.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| XFocusChangeEvent.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| XKeyEvent.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| XMotionEvent.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| XSetWindowAttributes.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| XWindowClass.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
|
||||
---
|
||||
|
||||
## CONVERTERS
|
||||
|
||||
| File | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| ColorExtensions.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| SKColorTypeConverter.cs | [x] | **FIXED 2026-01-01** - Removed embedded ColorExtensions |
|
||||
| SKPointTypeConverter.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| SKRectTypeConverter.cs | [x] | **FIXED 2026-01-01** - Removed embedded SKSizeTypeConverter, SKPointTypeConverter, SKTypeExtensions |
|
||||
| SKSizeTypeConverter.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
| SKTypeExtensions.cs | [x] | **VERIFIED 2026-01-01** - Separate file, matches decompiled |
|
||||
|
||||
---
|
||||
|
||||
## CORE
|
||||
|
||||
| File | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| LinuxApplication.cs | [x] | **FIXED 2026-01-01** - Removed embedded DisplayServerType, LinuxApplicationOptions |
|
||||
| LinuxApplicationOptions.cs | [x] | **VERIFIED 2026-01-01** - Matches decompiled (separate file) |
|
||||
|
||||
---
|
||||
|
||||
## TYPES
|
||||
|
||||
| File | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| ToggledEventArgs.cs | [x] | ADDED - was missing, required by SkiaSwitch |
|
||||
@@ -1,80 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Native;
|
||||
|
||||
internal static class CairoNative
|
||||
{
|
||||
public enum cairo_format_t
|
||||
{
|
||||
CAIRO_FORMAT_INVALID = -1,
|
||||
CAIRO_FORMAT_ARGB32,
|
||||
CAIRO_FORMAT_RGB24,
|
||||
CAIRO_FORMAT_A8,
|
||||
CAIRO_FORMAT_A1,
|
||||
CAIRO_FORMAT_RGB16_565,
|
||||
CAIRO_FORMAT_RGB30
|
||||
}
|
||||
|
||||
private const string Lib = "libcairo.so.2";
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern IntPtr cairo_image_surface_create_for_data(IntPtr data, cairo_format_t format, int width, int height, int stride);
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern IntPtr cairo_image_surface_create(cairo_format_t format, int width, int height);
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern IntPtr cairo_image_surface_get_data(IntPtr surface);
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern int cairo_image_surface_get_width(IntPtr surface);
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern int cairo_image_surface_get_height(IntPtr surface);
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern int cairo_image_surface_get_stride(IntPtr surface);
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern void cairo_surface_destroy(IntPtr surface);
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern void cairo_surface_flush(IntPtr surface);
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern void cairo_surface_mark_dirty(IntPtr surface);
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern void cairo_surface_mark_dirty_rectangle(IntPtr surface, int x, int y, int width, int height);
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern void cairo_set_source_surface(IntPtr cr, IntPtr surface, double x, double y);
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern void cairo_set_source_rgb(IntPtr cr, double red, double green, double blue);
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern void cairo_set_source_rgba(IntPtr cr, double red, double green, double blue, double alpha);
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern void cairo_paint(IntPtr cr);
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern void cairo_paint_with_alpha(IntPtr cr, double alpha);
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern void cairo_fill(IntPtr cr);
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern void cairo_rectangle(IntPtr cr, double x, double y, double width, double height);
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern void cairo_clip(IntPtr cr);
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern void cairo_save(IntPtr cr);
|
||||
|
||||
[DllImport("libcairo.so.2")]
|
||||
public static extern void cairo_restore(IntPtr cr);
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Native;
|
||||
|
||||
public static class GLibNative
|
||||
{
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate bool GSourceFunc(IntPtr userData);
|
||||
|
||||
private const string Lib = "libglib-2.0.so.0";
|
||||
|
||||
private static readonly List<GSourceFunc> _callbacks = new List<GSourceFunc>();
|
||||
private static readonly object _callbackLock = new object();
|
||||
|
||||
[DllImport("libglib-2.0.so.0", EntryPoint = "g_idle_add")]
|
||||
private static extern uint g_idle_add_native(GSourceFunc function, IntPtr data);
|
||||
|
||||
[DllImport("libglib-2.0.so.0", EntryPoint = "g_timeout_add")]
|
||||
private static extern uint g_timeout_add_native(uint interval, GSourceFunc function, IntPtr data);
|
||||
|
||||
[DllImport("libglib-2.0.so.0", EntryPoint = "g_source_remove")]
|
||||
public static extern bool SourceRemove(uint sourceId);
|
||||
|
||||
[DllImport("libglib-2.0.so.0", EntryPoint = "g_get_monotonic_time")]
|
||||
public static extern long GetMonotonicTime();
|
||||
|
||||
public static uint IdleAdd(Func<bool> callback)
|
||||
{
|
||||
GSourceFunc wrapper = null!;
|
||||
wrapper = delegate
|
||||
{
|
||||
bool flag = false;
|
||||
try
|
||||
{
|
||||
flag = callback();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("[GLibNative] Error in idle callback: " + ex.Message);
|
||||
}
|
||||
if (!flag)
|
||||
{
|
||||
lock (_callbackLock)
|
||||
{
|
||||
_callbacks.Remove(wrapper);
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
};
|
||||
lock (_callbackLock)
|
||||
{
|
||||
_callbacks.Add(wrapper);
|
||||
}
|
||||
return g_idle_add_native(wrapper, IntPtr.Zero);
|
||||
}
|
||||
|
||||
public static uint TimeoutAdd(uint intervalMs, Func<bool> callback)
|
||||
{
|
||||
GSourceFunc wrapper = null!;
|
||||
wrapper = delegate
|
||||
{
|
||||
bool flag = false;
|
||||
try
|
||||
{
|
||||
flag = callback();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("[GLibNative] Error in timeout callback: " + ex.Message);
|
||||
}
|
||||
if (!flag)
|
||||
{
|
||||
lock (_callbackLock)
|
||||
{
|
||||
_callbacks.Remove(wrapper);
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
};
|
||||
lock (_callbackLock)
|
||||
{
|
||||
_callbacks.Add(wrapper);
|
||||
}
|
||||
return g_timeout_add_native(intervalMs, wrapper, IntPtr.Zero);
|
||||
}
|
||||
|
||||
public static void ClearCallbacks()
|
||||
{
|
||||
lock (_callbackLock)
|
||||
{
|
||||
_callbacks.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static uint g_idle_add(GSourceFunc func, IntPtr data)
|
||||
{
|
||||
return g_idle_add_native(func, data);
|
||||
}
|
||||
|
||||
public static uint g_timeout_add(uint intervalMs, GSourceFunc func, IntPtr data)
|
||||
{
|
||||
return g_timeout_add_native(intervalMs, func, data);
|
||||
}
|
||||
|
||||
public static bool g_source_remove(uint tag)
|
||||
{
|
||||
return SourceRemove(tag);
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Native;
|
||||
|
||||
internal static class GdkNative
|
||||
{
|
||||
[Flags]
|
||||
public enum GdkEventMask
|
||||
{
|
||||
ExposureMask = 2,
|
||||
PointerMotionMask = 4,
|
||||
PointerMotionHintMask = 8,
|
||||
ButtonMotionMask = 0x10,
|
||||
Button1MotionMask = 0x20,
|
||||
Button2MotionMask = 0x40,
|
||||
Button3MotionMask = 0x80,
|
||||
ButtonPressMask = 0x100,
|
||||
ButtonReleaseMask = 0x200,
|
||||
KeyPressMask = 0x400,
|
||||
KeyReleaseMask = 0x800,
|
||||
EnterNotifyMask = 0x1000,
|
||||
LeaveNotifyMask = 0x2000,
|
||||
FocusChangeMask = 0x4000,
|
||||
StructureMask = 0x8000,
|
||||
PropertyChangeMask = 0x10000,
|
||||
VisibilityNotifyMask = 0x20000,
|
||||
ProximityInMask = 0x40000,
|
||||
ProximityOutMask = 0x80000,
|
||||
SubstructureMask = 0x100000,
|
||||
ScrollMask = 0x200000,
|
||||
TouchMask = 0x400000,
|
||||
SmoothScrollMask = 0x800000,
|
||||
AllEventsMask = 0xFFFFFE
|
||||
}
|
||||
|
||||
public enum GdkScrollDirection
|
||||
{
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
Smooth
|
||||
}
|
||||
|
||||
public struct GdkEventButton
|
||||
{
|
||||
public int Type;
|
||||
public IntPtr Window;
|
||||
public sbyte SendEvent;
|
||||
public uint Time;
|
||||
public double X;
|
||||
public double Y;
|
||||
public IntPtr Axes;
|
||||
public uint State;
|
||||
public uint Button;
|
||||
public IntPtr Device;
|
||||
public double XRoot;
|
||||
public double YRoot;
|
||||
}
|
||||
|
||||
public struct GdkEventMotion
|
||||
{
|
||||
public int Type;
|
||||
public IntPtr Window;
|
||||
public sbyte SendEvent;
|
||||
public uint Time;
|
||||
public double X;
|
||||
public double Y;
|
||||
public IntPtr Axes;
|
||||
public uint State;
|
||||
public short IsHint;
|
||||
public IntPtr Device;
|
||||
public double XRoot;
|
||||
public double YRoot;
|
||||
}
|
||||
|
||||
public struct GdkEventKey
|
||||
{
|
||||
public int Type;
|
||||
public IntPtr Window;
|
||||
public sbyte SendEvent;
|
||||
public uint Time;
|
||||
public uint State;
|
||||
public uint Keyval;
|
||||
public int Length;
|
||||
public IntPtr String;
|
||||
public ushort HardwareKeycode;
|
||||
public byte Group;
|
||||
public uint IsModifier;
|
||||
}
|
||||
|
||||
public struct GdkEventScroll
|
||||
{
|
||||
public int Type;
|
||||
public IntPtr Window;
|
||||
public sbyte SendEvent;
|
||||
public uint Time;
|
||||
public double X;
|
||||
public double Y;
|
||||
public uint State;
|
||||
public GdkScrollDirection Direction;
|
||||
public IntPtr Device;
|
||||
public double XRoot;
|
||||
public double YRoot;
|
||||
public double DeltaX;
|
||||
public double DeltaY;
|
||||
}
|
||||
|
||||
private const string Lib = "libgdk-3.so.0";
|
||||
|
||||
[DllImport("libgdk-3.so.0")]
|
||||
public static extern IntPtr gdk_display_get_default();
|
||||
|
||||
[DllImport("libgdk-3.so.0")]
|
||||
public static extern IntPtr gdk_display_get_name(IntPtr display);
|
||||
|
||||
[DllImport("libgdk-3.so.0")]
|
||||
public static extern IntPtr gdk_screen_get_default();
|
||||
|
||||
[DllImport("libgdk-3.so.0")]
|
||||
public static extern int gdk_screen_get_width(IntPtr screen);
|
||||
|
||||
[DllImport("libgdk-3.so.0")]
|
||||
public static extern int gdk_screen_get_height(IntPtr screen);
|
||||
|
||||
[DllImport("libgdk-3.so.0")]
|
||||
public static extern void gdk_window_invalidate_rect(IntPtr window, IntPtr rect, bool invalidateChildren);
|
||||
|
||||
[DllImport("libgdk-3.so.0")]
|
||||
public static extern uint gdk_keyval_to_unicode(uint keyval);
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Native;
|
||||
|
||||
internal static class GtkNative
|
||||
{
|
||||
public struct GtkAllocation
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
public int Width;
|
||||
public int Height;
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate bool GSourceFunc(IntPtr userData);
|
||||
|
||||
private const string Lib = "libgtk-3.so.0";
|
||||
|
||||
public const int GTK_WINDOW_TOPLEVEL = 0;
|
||||
public const int GTK_WINDOW_POPUP = 1;
|
||||
|
||||
private const string LibGdkPixbuf = "libgdk_pixbuf-2.0.so.0";
|
||||
|
||||
public const int GDK_COLORSPACE_RGB = 0;
|
||||
|
||||
private const string GLib = "libglib-2.0.so.0";
|
||||
|
||||
private static readonly List<GSourceFunc> _idleCallbacks = new List<GSourceFunc>();
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_init(ref int argc, ref IntPtr argv);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern bool gtk_init_check(ref int argc, ref IntPtr argv);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern IntPtr gtk_window_new(int windowType);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_window_set_title(IntPtr window, string title);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_window_set_default_size(IntPtr window, int width, int height);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_window_resize(IntPtr window, int width, int height);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_window_move(IntPtr window, int x, int y);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_window_get_size(IntPtr window, out int width, out int height);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_window_get_position(IntPtr window, out int x, out int y);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_window_set_icon(IntPtr window, IntPtr pixbuf);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_window_set_icon_from_file(IntPtr window, string filename, IntPtr error);
|
||||
|
||||
[DllImport("libgdk_pixbuf-2.0.so.0")]
|
||||
public static extern IntPtr gdk_pixbuf_new_from_file(string filename, IntPtr error);
|
||||
|
||||
[DllImport("libgdk_pixbuf-2.0.so.0")]
|
||||
public static extern IntPtr gdk_pixbuf_new_from_data(IntPtr data, int colorspace, bool hasAlpha, int bitsPerSample, int width, int height, int rowstride, IntPtr destroyFn, IntPtr destroyFnData);
|
||||
|
||||
[DllImport("libgdk_pixbuf-2.0.so.0")]
|
||||
public static extern void g_object_unref(IntPtr obj);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_widget_show_all(IntPtr widget);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_widget_show(IntPtr widget);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_widget_hide(IntPtr widget);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_widget_destroy(IntPtr widget);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_widget_queue_draw(IntPtr widget);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_widget_set_size_request(IntPtr widget, int width, int height);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_widget_get_allocation(IntPtr widget, out GtkAllocation allocation);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_main();
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_main_quit();
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern bool gtk_events_pending();
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern bool gtk_main_iteration_do(bool blocking);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern IntPtr gtk_overlay_new();
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_container_add(IntPtr container, IntPtr widget);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_container_remove(IntPtr container, IntPtr widget);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_overlay_add_overlay(IntPtr overlay, IntPtr widget);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_overlay_set_overlay_pass_through(IntPtr overlay, IntPtr widget, bool passThrough);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern IntPtr gtk_fixed_new();
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_fixed_put(IntPtr fixedWidget, IntPtr widget, int x, int y);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_fixed_move(IntPtr fixedWidget, IntPtr widget, int x, int y);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern IntPtr gtk_drawing_area_new();
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_widget_set_can_focus(IntPtr widget, bool canFocus);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern bool gtk_widget_grab_focus(IntPtr widget);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern bool gtk_widget_has_focus(IntPtr widget);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_widget_add_events(IntPtr widget, int events);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern ulong g_signal_connect_data(IntPtr instance, string detailedSignal, IntPtr cHandler, IntPtr data, IntPtr destroyData, int connectFlags);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void g_signal_handler_disconnect(IntPtr instance, ulong handlerId);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern IntPtr gtk_widget_get_window(IntPtr widget);
|
||||
|
||||
[DllImport("libglib-2.0.so.0", EntryPoint = "g_idle_add")]
|
||||
public static extern uint IdleAdd(GSourceFunc function, IntPtr data);
|
||||
|
||||
[DllImport("libglib-2.0.so.0", EntryPoint = "g_source_remove")]
|
||||
public static extern bool SourceRemove(uint sourceId);
|
||||
|
||||
public static uint IdleAdd(Func<bool> callback)
|
||||
{
|
||||
GSourceFunc gSourceFunc = (IntPtr _) => callback();
|
||||
_idleCallbacks.Add(gSourceFunc);
|
||||
return IdleAdd(gSourceFunc, IntPtr.Zero);
|
||||
}
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern IntPtr gtk_menu_new();
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern IntPtr gtk_menu_item_new_with_label(string label);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern IntPtr gtk_separator_menu_item_new();
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_menu_shell_append(IntPtr menuShell, IntPtr child);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_menu_popup_at_pointer(IntPtr menu, IntPtr triggerEvent);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern void gtk_widget_set_sensitive(IntPtr widget, bool sensitive);
|
||||
|
||||
[DllImport("libgtk-3.so.0")]
|
||||
public static extern IntPtr gtk_get_current_event();
|
||||
|
||||
[DllImport("libgdk-3.so.0")]
|
||||
public static extern void gdk_event_free(IntPtr eventPtr);
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Native;
|
||||
|
||||
internal static class WebKitNative
|
||||
{
|
||||
private delegate IntPtr WebKitWebViewNewDelegate();
|
||||
private delegate void WebKitWebViewLoadUriDelegate(IntPtr webView, string uri);
|
||||
private delegate void WebKitWebViewLoadHtmlDelegate(IntPtr webView, string content, string? baseUri);
|
||||
private delegate IntPtr WebKitWebViewGetUriDelegate(IntPtr webView);
|
||||
private delegate IntPtr WebKitWebViewGetTitleDelegate(IntPtr webView);
|
||||
private delegate void WebKitWebViewGoBackDelegate(IntPtr webView);
|
||||
private delegate void WebKitWebViewGoForwardDelegate(IntPtr webView);
|
||||
private delegate bool WebKitWebViewCanGoBackDelegate(IntPtr webView);
|
||||
private delegate bool WebKitWebViewCanGoForwardDelegate(IntPtr webView);
|
||||
private delegate void WebKitWebViewReloadDelegate(IntPtr webView);
|
||||
private delegate void WebKitWebViewStopLoadingDelegate(IntPtr webView);
|
||||
private delegate IntPtr WebKitWebViewGetSettingsDelegate(IntPtr webView);
|
||||
private delegate void WebKitSettingsSetHardwareAccelerationPolicyDelegate(IntPtr settings, int policy);
|
||||
private delegate void WebKitSettingsSetEnableJavascriptDelegate(IntPtr settings, bool enabled);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public delegate void LoadChangedCallback(IntPtr webView, int loadEvent, IntPtr userData);
|
||||
|
||||
private delegate ulong GSignalConnectDataDelegate(IntPtr instance, string signalName, LoadChangedCallback callback, IntPtr userData, IntPtr destroyNotify, int connectFlags);
|
||||
|
||||
public enum WebKitLoadEvent
|
||||
{
|
||||
Started,
|
||||
Redirected,
|
||||
Committed,
|
||||
Finished
|
||||
}
|
||||
|
||||
private static IntPtr _handle;
|
||||
private static bool _initialized;
|
||||
|
||||
private static readonly string[] LibraryNames = new string[4]
|
||||
{
|
||||
"libwebkit2gtk-4.1.so.0",
|
||||
"libwebkit2gtk-4.0.so.37",
|
||||
"libwebkit2gtk-4.0.so",
|
||||
"libwebkit2gtk-4.1.so"
|
||||
};
|
||||
|
||||
private static WebKitWebViewNewDelegate? _webkitWebViewNew;
|
||||
private static WebKitWebViewLoadUriDelegate? _webkitLoadUri;
|
||||
private static WebKitWebViewLoadHtmlDelegate? _webkitLoadHtml;
|
||||
private static WebKitWebViewGetUriDelegate? _webkitGetUri;
|
||||
private static WebKitWebViewGetTitleDelegate? _webkitGetTitle;
|
||||
private static WebKitWebViewGoBackDelegate? _webkitGoBack;
|
||||
private static WebKitWebViewGoForwardDelegate? _webkitGoForward;
|
||||
private static WebKitWebViewCanGoBackDelegate? _webkitCanGoBack;
|
||||
private static WebKitWebViewCanGoForwardDelegate? _webkitCanGoForward;
|
||||
private static WebKitWebViewReloadDelegate? _webkitReload;
|
||||
private static WebKitWebViewStopLoadingDelegate? _webkitStopLoading;
|
||||
private static WebKitWebViewGetSettingsDelegate? _webkitGetSettings;
|
||||
private static WebKitSettingsSetHardwareAccelerationPolicyDelegate? _webkitSetHardwareAccel;
|
||||
private static WebKitSettingsSetEnableJavascriptDelegate? _webkitSetJavascript;
|
||||
private static GSignalConnectDataDelegate? _gSignalConnectData;
|
||||
|
||||
private static readonly Dictionary<IntPtr, LoadChangedCallback> _loadChangedCallbacks = new Dictionary<IntPtr, LoadChangedCallback>();
|
||||
|
||||
private const int RTLD_NOW = 2;
|
||||
private const int RTLD_GLOBAL = 256;
|
||||
|
||||
private static IntPtr _gobjectHandle;
|
||||
|
||||
[DllImport("libdl.so.2")]
|
||||
private static extern IntPtr dlopen(string? filename, int flags);
|
||||
|
||||
[DllImport("libdl.so.2")]
|
||||
private static extern IntPtr dlsym(IntPtr handle, string symbol);
|
||||
|
||||
[DllImport("libdl.so.2")]
|
||||
private static extern IntPtr dlerror();
|
||||
|
||||
public static bool Initialize()
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
return _handle != IntPtr.Zero;
|
||||
}
|
||||
_initialized = true;
|
||||
|
||||
string[] libraryNames = LibraryNames;
|
||||
foreach (string text in libraryNames)
|
||||
{
|
||||
_handle = dlopen(text, 258);
|
||||
if (_handle != IntPtr.Zero)
|
||||
{
|
||||
Console.WriteLine("[WebKitNative] Loaded " + text);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_handle == IntPtr.Zero)
|
||||
{
|
||||
Console.WriteLine("[WebKitNative] Failed to load WebKitGTK library");
|
||||
return false;
|
||||
}
|
||||
|
||||
_webkitWebViewNew = LoadFunction<WebKitWebViewNewDelegate>("webkit_web_view_new");
|
||||
_webkitLoadUri = LoadFunction<WebKitWebViewLoadUriDelegate>("webkit_web_view_load_uri");
|
||||
_webkitLoadHtml = LoadFunction<WebKitWebViewLoadHtmlDelegate>("webkit_web_view_load_html");
|
||||
_webkitGetUri = LoadFunction<WebKitWebViewGetUriDelegate>("webkit_web_view_get_uri");
|
||||
_webkitGetTitle = LoadFunction<WebKitWebViewGetTitleDelegate>("webkit_web_view_get_title");
|
||||
_webkitGoBack = LoadFunction<WebKitWebViewGoBackDelegate>("webkit_web_view_go_back");
|
||||
_webkitGoForward = LoadFunction<WebKitWebViewGoForwardDelegate>("webkit_web_view_go_forward");
|
||||
_webkitCanGoBack = LoadFunction<WebKitWebViewCanGoBackDelegate>("webkit_web_view_can_go_back");
|
||||
_webkitCanGoForward = LoadFunction<WebKitWebViewCanGoForwardDelegate>("webkit_web_view_can_go_forward");
|
||||
_webkitReload = LoadFunction<WebKitWebViewReloadDelegate>("webkit_web_view_reload");
|
||||
_webkitStopLoading = LoadFunction<WebKitWebViewStopLoadingDelegate>("webkit_web_view_stop_loading");
|
||||
_webkitGetSettings = LoadFunction<WebKitWebViewGetSettingsDelegate>("webkit_web_view_get_settings");
|
||||
_webkitSetHardwareAccel = LoadFunction<WebKitSettingsSetHardwareAccelerationPolicyDelegate>("webkit_settings_set_hardware_acceleration_policy");
|
||||
_webkitSetJavascript = LoadFunction<WebKitSettingsSetEnableJavascriptDelegate>("webkit_settings_set_enable_javascript");
|
||||
|
||||
_gobjectHandle = dlopen("libgobject-2.0.so.0", 258);
|
||||
if (_gobjectHandle != IntPtr.Zero)
|
||||
{
|
||||
IntPtr intPtr = dlsym(_gobjectHandle, "g_signal_connect_data");
|
||||
if (intPtr != IntPtr.Zero)
|
||||
{
|
||||
_gSignalConnectData = Marshal.GetDelegateForFunctionPointer<GSignalConnectDataDelegate>(intPtr);
|
||||
Console.WriteLine("[WebKitNative] Loaded g_signal_connect_data");
|
||||
}
|
||||
}
|
||||
|
||||
return _webkitWebViewNew != null;
|
||||
}
|
||||
|
||||
private static T? LoadFunction<T>(string name) where T : Delegate
|
||||
{
|
||||
if (_handle == IntPtr.Zero)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
IntPtr intPtr = dlsym(_handle, name);
|
||||
if (intPtr == IntPtr.Zero)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return Marshal.GetDelegateForFunctionPointer<T>(intPtr);
|
||||
}
|
||||
|
||||
public static IntPtr WebViewNew()
|
||||
{
|
||||
if (!Initialize() || _webkitWebViewNew == null)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
return _webkitWebViewNew();
|
||||
}
|
||||
|
||||
public static void LoadUri(IntPtr webView, string uri)
|
||||
{
|
||||
_webkitLoadUri?.Invoke(webView, uri);
|
||||
}
|
||||
|
||||
public static void LoadHtml(IntPtr webView, string content, string? baseUri = null)
|
||||
{
|
||||
_webkitLoadHtml?.Invoke(webView, content, baseUri);
|
||||
}
|
||||
|
||||
public static string? GetUri(IntPtr webView)
|
||||
{
|
||||
IntPtr intPtr = _webkitGetUri?.Invoke(webView) ?? IntPtr.Zero;
|
||||
if (intPtr == IntPtr.Zero)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return Marshal.PtrToStringUTF8(intPtr);
|
||||
}
|
||||
|
||||
public static string? GetTitle(IntPtr webView)
|
||||
{
|
||||
IntPtr intPtr = _webkitGetTitle?.Invoke(webView) ?? IntPtr.Zero;
|
||||
if (intPtr == IntPtr.Zero)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return Marshal.PtrToStringUTF8(intPtr);
|
||||
}
|
||||
|
||||
public static void GoBack(IntPtr webView)
|
||||
{
|
||||
_webkitGoBack?.Invoke(webView);
|
||||
}
|
||||
|
||||
public static void GoForward(IntPtr webView)
|
||||
{
|
||||
_webkitGoForward?.Invoke(webView);
|
||||
}
|
||||
|
||||
public static bool CanGoBack(IntPtr webView)
|
||||
{
|
||||
return _webkitCanGoBack?.Invoke(webView) ?? false;
|
||||
}
|
||||
|
||||
public static bool CanGoForward(IntPtr webView)
|
||||
{
|
||||
return _webkitCanGoForward?.Invoke(webView) ?? false;
|
||||
}
|
||||
|
||||
public static void Reload(IntPtr webView)
|
||||
{
|
||||
_webkitReload?.Invoke(webView);
|
||||
}
|
||||
|
||||
public static void StopLoading(IntPtr webView)
|
||||
{
|
||||
_webkitStopLoading?.Invoke(webView);
|
||||
}
|
||||
|
||||
public static void ConfigureSettings(IntPtr webView, bool disableHardwareAccel = true)
|
||||
{
|
||||
if (_webkitGetSettings != null)
|
||||
{
|
||||
IntPtr intPtr = _webkitGetSettings(webView);
|
||||
if (intPtr != IntPtr.Zero && disableHardwareAccel && _webkitSetHardwareAccel != null)
|
||||
{
|
||||
_webkitSetHardwareAccel(intPtr, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetJavascriptEnabled(IntPtr webView, bool enabled)
|
||||
{
|
||||
if (_webkitGetSettings != null && _webkitSetJavascript != null)
|
||||
{
|
||||
IntPtr intPtr = _webkitGetSettings(webView);
|
||||
if (intPtr != IntPtr.Zero)
|
||||
{
|
||||
_webkitSetJavascript(intPtr, enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ulong ConnectLoadChanged(IntPtr webView, LoadChangedCallback callback)
|
||||
{
|
||||
if (_gSignalConnectData == null || webView == IntPtr.Zero)
|
||||
{
|
||||
Console.WriteLine("[WebKitNative] Cannot connect load-changed: signal connect not available");
|
||||
return 0uL;
|
||||
}
|
||||
_loadChangedCallbacks[webView] = callback;
|
||||
return _gSignalConnectData(webView, "load-changed", callback, IntPtr.Zero, IntPtr.Zero, 0);
|
||||
}
|
||||
|
||||
public static void DisconnectLoadChanged(IntPtr webView)
|
||||
{
|
||||
_loadChangedCallbacks.Remove(webView);
|
||||
}
|
||||
}
|
||||
@@ -42,11 +42,13 @@
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.9" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.9" />
|
||||
<PackageReference Include="SkiaSharp.Views.Desktop.Common" Version="2.88.9" />
|
||||
<PackageReference Include="Svg.Skia" Version="1.0.0" />
|
||||
|
||||
<!-- HarfBuzz for advanced text shaping -->
|
||||
<PackageReference Include="HarfBuzzSharp" Version="7.3.0.3" />
|
||||
<PackageReference Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0.3" />
|
||||
|
||||
<!-- SVG support for icon loading -->
|
||||
<PackageReference Include="Svg.Skia" Version="2.0.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Include README and icon in package -->
|
||||
|
||||
@@ -1,335 +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 SkiaSharp;
|
||||
using Microsoft.Maui.Platform.Linux.Window;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// GPU-accelerated rendering engine using OpenGL.
|
||||
/// Falls back to software rendering if GPU initialization fails.
|
||||
/// </summary>
|
||||
public class GpuRenderingEngine : IDisposable
|
||||
{
|
||||
private readonly X11Window _window;
|
||||
private GRContext? _grContext;
|
||||
private GRBackendRenderTarget? _renderTarget;
|
||||
private SKSurface? _surface;
|
||||
private SKCanvas? _canvas;
|
||||
private bool _disposed;
|
||||
private bool _gpuAvailable;
|
||||
private int _width;
|
||||
private int _height;
|
||||
|
||||
// Fallback to software rendering
|
||||
private SKBitmap? _softwareBitmap;
|
||||
private SKCanvas? _softwareCanvas;
|
||||
|
||||
// Dirty region tracking
|
||||
private readonly List<SKRect> _dirtyRegions = new();
|
||||
private readonly object _dirtyLock = new();
|
||||
private bool _fullRedrawNeeded = true;
|
||||
private const int MaxDirtyRegions = 32;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether GPU acceleration is available and active.
|
||||
/// </summary>
|
||||
public bool IsGpuAccelerated => _gpuAvailable && _grContext != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current rendering backend name.
|
||||
/// </summary>
|
||||
public string BackendName => IsGpuAccelerated ? "OpenGL" : "Software";
|
||||
|
||||
public int Width => _width;
|
||||
public int Height => _height;
|
||||
|
||||
public GpuRenderingEngine(X11Window window)
|
||||
{
|
||||
_window = window;
|
||||
_width = window.Width;
|
||||
_height = window.Height;
|
||||
|
||||
// Try to initialize GPU rendering
|
||||
_gpuAvailable = TryInitializeGpu();
|
||||
|
||||
if (!_gpuAvailable)
|
||||
{
|
||||
Console.WriteLine("[GpuRenderingEngine] GPU not available, using software rendering");
|
||||
InitializeSoftwareRendering();
|
||||
}
|
||||
|
||||
_window.Resized += OnWindowResized;
|
||||
_window.Exposed += OnWindowExposed;
|
||||
}
|
||||
|
||||
private bool TryInitializeGpu()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check if we can create an OpenGL context
|
||||
var glInterface = GRGlInterface.Create();
|
||||
if (glInterface == null)
|
||||
{
|
||||
Console.WriteLine("[GpuRenderingEngine] Failed to create GL interface");
|
||||
return false;
|
||||
}
|
||||
|
||||
_grContext = GRContext.CreateGl(glInterface);
|
||||
if (_grContext == null)
|
||||
{
|
||||
Console.WriteLine("[GpuRenderingEngine] Failed to create GR context");
|
||||
glInterface.Dispose();
|
||||
return false;
|
||||
}
|
||||
|
||||
CreateGpuSurface();
|
||||
Console.WriteLine("[GpuRenderingEngine] GPU acceleration enabled");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[GpuRenderingEngine] GPU initialization failed: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateGpuSurface()
|
||||
{
|
||||
if (_grContext == null) return;
|
||||
|
||||
_renderTarget?.Dispose();
|
||||
_surface?.Dispose();
|
||||
|
||||
var width = Math.Max(1, _width);
|
||||
var height = Math.Max(1, _height);
|
||||
|
||||
// Create framebuffer info (assuming default framebuffer 0)
|
||||
var framebufferInfo = new GRGlFramebufferInfo(0, SKColorType.Rgba8888.ToGlSizedFormat());
|
||||
|
||||
_renderTarget = new GRBackendRenderTarget(
|
||||
width, height,
|
||||
0, // sample count
|
||||
8, // stencil bits
|
||||
framebufferInfo);
|
||||
|
||||
_surface = SKSurface.Create(
|
||||
_grContext,
|
||||
_renderTarget,
|
||||
GRSurfaceOrigin.BottomLeft,
|
||||
SKColorType.Rgba8888);
|
||||
|
||||
if (_surface == null)
|
||||
{
|
||||
Console.WriteLine("[GpuRenderingEngine] Failed to create GPU surface, falling back to software");
|
||||
_gpuAvailable = false;
|
||||
InitializeSoftwareRendering();
|
||||
return;
|
||||
}
|
||||
|
||||
_canvas = _surface.Canvas;
|
||||
}
|
||||
|
||||
private void InitializeSoftwareRendering()
|
||||
{
|
||||
var width = Math.Max(1, _width);
|
||||
var height = Math.Max(1, _height);
|
||||
|
||||
_softwareBitmap?.Dispose();
|
||||
_softwareCanvas?.Dispose();
|
||||
|
||||
var imageInfo = new SKImageInfo(width, height, SKColorType.Bgra8888, SKAlphaType.Premul);
|
||||
_softwareBitmap = new SKBitmap(imageInfo);
|
||||
_softwareCanvas = new SKCanvas(_softwareBitmap);
|
||||
_canvas = _softwareCanvas;
|
||||
}
|
||||
|
||||
private void OnWindowResized(object? sender, (int Width, int Height) size)
|
||||
{
|
||||
_width = size.Width;
|
||||
_height = size.Height;
|
||||
|
||||
if (_gpuAvailable && _grContext != null)
|
||||
{
|
||||
CreateGpuSurface();
|
||||
}
|
||||
else
|
||||
{
|
||||
InitializeSoftwareRendering();
|
||||
}
|
||||
|
||||
_fullRedrawNeeded = true;
|
||||
}
|
||||
|
||||
private void OnWindowExposed(object? sender, EventArgs e)
|
||||
{
|
||||
_fullRedrawNeeded = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks a region as needing redraw.
|
||||
/// </summary>
|
||||
public void InvalidateRegion(SKRect region)
|
||||
{
|
||||
if (region.IsEmpty || region.Width <= 0 || region.Height <= 0)
|
||||
return;
|
||||
|
||||
region = SKRect.Intersect(region, new SKRect(0, 0, Width, Height));
|
||||
if (region.IsEmpty) return;
|
||||
|
||||
lock (_dirtyLock)
|
||||
{
|
||||
if (_dirtyRegions.Count >= MaxDirtyRegions)
|
||||
{
|
||||
_fullRedrawNeeded = true;
|
||||
_dirtyRegions.Clear();
|
||||
return;
|
||||
}
|
||||
_dirtyRegions.Add(region);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the entire surface as needing redraw.
|
||||
/// </summary>
|
||||
public void InvalidateAll()
|
||||
{
|
||||
_fullRedrawNeeded = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the view tree with dirty region optimization.
|
||||
/// </summary>
|
||||
public void Render(SkiaView rootView)
|
||||
{
|
||||
if (_canvas == null) return;
|
||||
|
||||
// Measure and arrange
|
||||
var availableSize = new SKSize(Width, Height);
|
||||
rootView.Measure(availableSize);
|
||||
rootView.Arrange(new SKRect(0, 0, Width, Height));
|
||||
|
||||
// Determine regions to redraw
|
||||
List<SKRect> regionsToRedraw;
|
||||
bool isFullRedraw;
|
||||
|
||||
lock (_dirtyLock)
|
||||
{
|
||||
isFullRedraw = _fullRedrawNeeded || _dirtyRegions.Count == 0;
|
||||
if (isFullRedraw)
|
||||
{
|
||||
regionsToRedraw = new List<SKRect> { new SKRect(0, 0, Width, Height) };
|
||||
_dirtyRegions.Clear();
|
||||
_fullRedrawNeeded = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
regionsToRedraw = new List<SKRect>(_dirtyRegions);
|
||||
_dirtyRegions.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Render each dirty region
|
||||
foreach (var region in regionsToRedraw)
|
||||
{
|
||||
_canvas.Save();
|
||||
if (!isFullRedraw)
|
||||
{
|
||||
_canvas.ClipRect(region);
|
||||
}
|
||||
|
||||
// Clear region
|
||||
_canvas.Clear(SKColors.White);
|
||||
|
||||
// Draw view tree
|
||||
rootView.Draw(_canvas);
|
||||
|
||||
_canvas.Restore();
|
||||
}
|
||||
|
||||
// Draw popup overlays
|
||||
SkiaView.DrawPopupOverlays(_canvas);
|
||||
|
||||
// Draw modal dialogs
|
||||
if (LinuxDialogService.HasActiveDialog)
|
||||
{
|
||||
LinuxDialogService.DrawDialogs(_canvas, new SKRect(0, 0, Width, Height));
|
||||
}
|
||||
|
||||
_canvas.Flush();
|
||||
|
||||
// Present to window
|
||||
if (_gpuAvailable && _grContext != null)
|
||||
{
|
||||
_grContext.Submit();
|
||||
// Swap buffers would happen here via GLX/EGL
|
||||
}
|
||||
else if (_softwareBitmap != null)
|
||||
{
|
||||
var pixels = _softwareBitmap.GetPixels();
|
||||
if (pixels != IntPtr.Zero)
|
||||
{
|
||||
_window.DrawPixels(pixels, Width, Height, _softwareBitmap.RowBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets performance statistics for the GPU context.
|
||||
/// </summary>
|
||||
public GpuStats GetStats()
|
||||
{
|
||||
if (_grContext == null)
|
||||
{
|
||||
return new GpuStats { IsGpuAccelerated = false };
|
||||
}
|
||||
|
||||
// Get resource cache limits from GRContext
|
||||
_grContext.GetResourceCacheLimits(out var maxResources, out var maxBytes);
|
||||
|
||||
return new GpuStats
|
||||
{
|
||||
IsGpuAccelerated = true,
|
||||
MaxTextureSize = 4096, // Common default, SkiaSharp doesn't expose this directly
|
||||
ResourceCacheUsedBytes = 0, // Would need to track manually
|
||||
ResourceCacheLimitBytes = maxBytes
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Purges unused GPU resources to free memory.
|
||||
/// </summary>
|
||||
public void PurgeResources()
|
||||
{
|
||||
_grContext?.PurgeResources();
|
||||
}
|
||||
|
||||
public SKCanvas? GetCanvas() => _canvas;
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_window.Resized -= OnWindowResized;
|
||||
_window.Exposed -= OnWindowExposed;
|
||||
|
||||
_surface?.Dispose();
|
||||
_renderTarget?.Dispose();
|
||||
_grContext?.Dispose();
|
||||
_softwareBitmap?.Dispose();
|
||||
_softwareCanvas?.Dispose();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +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.Rendering;
|
||||
|
||||
public class GpuStats
|
||||
{
|
||||
public bool IsGpuAccelerated { get; init; }
|
||||
|
||||
public int MaxTextureSize { get; init; }
|
||||
|
||||
public long ResourceCacheUsedBytes { get; init; }
|
||||
|
||||
public long ResourceCacheLimitBytes { get; init; }
|
||||
|
||||
public double ResourceCacheUsedMB => ResourceCacheUsedBytes / 1048576.0;
|
||||
|
||||
public double ResourceCacheLimitMB => ResourceCacheLimitBytes / 1048576.0;
|
||||
}
|
||||
@@ -1,391 +0,0 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Maui.Platform.Linux.Native;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// GTK drawing area widget that renders Skia content via Cairo.
|
||||
/// Provides hardware-accelerated 2D rendering for MAUI views.
|
||||
/// </summary>
|
||||
public sealed class GtkSkiaSurfaceWidget : IDisposable
|
||||
{
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate bool DrawCallback(IntPtr widget, IntPtr cairoContext, IntPtr userData);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate bool ConfigureCallback(IntPtr widget, IntPtr eventData, IntPtr userData);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate bool ButtonEventCallback(IntPtr widget, IntPtr eventData, IntPtr userData);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate bool MotionEventCallback(IntPtr widget, IntPtr eventData, IntPtr userData);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate bool KeyEventCallback(IntPtr widget, IntPtr eventData, IntPtr userData);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate bool ScrollEventCallback(IntPtr widget, IntPtr eventData, IntPtr userData);
|
||||
|
||||
private struct GdkEventButton
|
||||
{
|
||||
public int type;
|
||||
public IntPtr window;
|
||||
public sbyte send_event;
|
||||
public uint time;
|
||||
public double x;
|
||||
public double y;
|
||||
public IntPtr axes;
|
||||
public uint state;
|
||||
public uint button;
|
||||
}
|
||||
|
||||
private struct GdkEventMotion
|
||||
{
|
||||
public int type;
|
||||
public IntPtr window;
|
||||
public sbyte send_event;
|
||||
public uint time;
|
||||
public double x;
|
||||
public double y;
|
||||
}
|
||||
|
||||
private struct GdkEventKey
|
||||
{
|
||||
public int type;
|
||||
public IntPtr window;
|
||||
public sbyte send_event;
|
||||
public uint time;
|
||||
public uint state;
|
||||
public uint keyval;
|
||||
public int length;
|
||||
public IntPtr str;
|
||||
public ushort hardware_keycode;
|
||||
}
|
||||
|
||||
private struct GdkEventScroll
|
||||
{
|
||||
public int type;
|
||||
public IntPtr window;
|
||||
public sbyte send_event;
|
||||
public uint time;
|
||||
public double x;
|
||||
public double y;
|
||||
public uint state;
|
||||
public int direction;
|
||||
public IntPtr device;
|
||||
public double x_root;
|
||||
public double y_root;
|
||||
public double delta_x;
|
||||
public double delta_y;
|
||||
}
|
||||
|
||||
private IntPtr _widget;
|
||||
private SKImageInfo _imageInfo;
|
||||
private SKBitmap? _bitmap;
|
||||
private SKCanvas? _canvas;
|
||||
private IntPtr _cairoSurface;
|
||||
private readonly DrawCallback _drawCallback;
|
||||
private readonly ConfigureCallback _configureCallback;
|
||||
private ulong _drawSignalId;
|
||||
private ulong _configureSignalId;
|
||||
private bool _isTransparent;
|
||||
private readonly ButtonEventCallback _buttonPressCallback;
|
||||
private readonly ButtonEventCallback _buttonReleaseCallback;
|
||||
private readonly MotionEventCallback _motionCallback;
|
||||
private readonly KeyEventCallback _keyPressCallback;
|
||||
private readonly KeyEventCallback _keyReleaseCallback;
|
||||
private readonly ScrollEventCallback _scrollCallback;
|
||||
|
||||
public IntPtr Widget => _widget;
|
||||
public SKCanvas? Canvas => _canvas;
|
||||
public SKImageInfo ImageInfo => _imageInfo;
|
||||
public int Width => _imageInfo.Width;
|
||||
public int Height => _imageInfo.Height;
|
||||
public bool IsTransparent => _isTransparent;
|
||||
|
||||
public event EventHandler? DrawRequested;
|
||||
public event EventHandler<(int Width, int Height)>? Resized;
|
||||
public event EventHandler<(double X, double Y, int Button)>? PointerPressed;
|
||||
public event EventHandler<(double X, double Y, int Button)>? PointerReleased;
|
||||
public event EventHandler<(double X, double Y)>? PointerMoved;
|
||||
public event EventHandler<(uint KeyVal, uint KeyCode, uint State)>? KeyPressed;
|
||||
public event EventHandler<(uint KeyVal, uint KeyCode, uint State)>? KeyReleased;
|
||||
public event EventHandler<(double X, double Y, double DeltaX, double DeltaY)>? Scrolled;
|
||||
public event EventHandler<string>? TextInput;
|
||||
|
||||
public GtkSkiaSurfaceWidget(int width, int height)
|
||||
{
|
||||
_widget = GtkNative.gtk_drawing_area_new();
|
||||
if (_widget == IntPtr.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create GTK drawing area");
|
||||
}
|
||||
|
||||
GtkNative.gtk_widget_set_size_request(_widget, width, height);
|
||||
GtkNative.gtk_widget_add_events(_widget, 10551046);
|
||||
GtkNative.gtk_widget_set_can_focus(_widget, canFocus: true);
|
||||
|
||||
CreateBuffer(width, height);
|
||||
|
||||
// Store delegates to prevent garbage collection
|
||||
_drawCallback = OnDraw;
|
||||
_configureCallback = OnConfigure;
|
||||
_buttonPressCallback = OnButtonPress;
|
||||
_buttonReleaseCallback = OnButtonRelease;
|
||||
_motionCallback = OnMotion;
|
||||
_keyPressCallback = OnKeyPress;
|
||||
_keyReleaseCallback = OnKeyRelease;
|
||||
_scrollCallback = OnScroll;
|
||||
|
||||
// Connect signals
|
||||
_drawSignalId = GtkNative.g_signal_connect_data(_widget, "draw", Marshal.GetFunctionPointerForDelegate(_drawCallback), IntPtr.Zero, IntPtr.Zero, 0);
|
||||
_configureSignalId = GtkNative.g_signal_connect_data(_widget, "configure-event", Marshal.GetFunctionPointerForDelegate(_configureCallback), IntPtr.Zero, IntPtr.Zero, 0);
|
||||
GtkNative.g_signal_connect_data(_widget, "button-press-event", Marshal.GetFunctionPointerForDelegate(_buttonPressCallback), IntPtr.Zero, IntPtr.Zero, 0);
|
||||
GtkNative.g_signal_connect_data(_widget, "button-release-event", Marshal.GetFunctionPointerForDelegate(_buttonReleaseCallback), IntPtr.Zero, IntPtr.Zero, 0);
|
||||
GtkNative.g_signal_connect_data(_widget, "motion-notify-event", Marshal.GetFunctionPointerForDelegate(_motionCallback), IntPtr.Zero, IntPtr.Zero, 0);
|
||||
GtkNative.g_signal_connect_data(_widget, "key-press-event", Marshal.GetFunctionPointerForDelegate(_keyPressCallback), IntPtr.Zero, IntPtr.Zero, 0);
|
||||
GtkNative.g_signal_connect_data(_widget, "key-release-event", Marshal.GetFunctionPointerForDelegate(_keyReleaseCallback), IntPtr.Zero, IntPtr.Zero, 0);
|
||||
GtkNative.g_signal_connect_data(_widget, "scroll-event", Marshal.GetFunctionPointerForDelegate(_scrollCallback), IntPtr.Zero, IntPtr.Zero, 0);
|
||||
|
||||
Console.WriteLine($"[GtkSkiaSurfaceWidget] Created with size {width}x{height}");
|
||||
}
|
||||
|
||||
private void CreateBuffer(int width, int height)
|
||||
{
|
||||
width = Math.Max(1, width);
|
||||
height = Math.Max(1, height);
|
||||
|
||||
_canvas?.Dispose();
|
||||
_bitmap?.Dispose();
|
||||
|
||||
if (_cairoSurface != IntPtr.Zero)
|
||||
{
|
||||
CairoNative.cairo_surface_destroy(_cairoSurface);
|
||||
_cairoSurface = IntPtr.Zero;
|
||||
}
|
||||
|
||||
_imageInfo = new SKImageInfo(width, height, SKColorType.Bgra8888, SKAlphaType.Premul);
|
||||
_bitmap = new SKBitmap(_imageInfo);
|
||||
_canvas = new SKCanvas(_bitmap);
|
||||
|
||||
IntPtr pixels = _bitmap.GetPixels();
|
||||
_cairoSurface = CairoNative.cairo_image_surface_create_for_data(
|
||||
pixels,
|
||||
CairoNative.cairo_format_t.CAIRO_FORMAT_ARGB32,
|
||||
_imageInfo.Width,
|
||||
_imageInfo.Height,
|
||||
_imageInfo.RowBytes);
|
||||
|
||||
Console.WriteLine($"[GtkSkiaSurfaceWidget] Created buffer {width}x{height}, stride={_imageInfo.RowBytes}");
|
||||
}
|
||||
|
||||
public void Resize(int width, int height)
|
||||
{
|
||||
if (width != _imageInfo.Width || height != _imageInfo.Height)
|
||||
{
|
||||
CreateBuffer(width, height);
|
||||
Resized?.Invoke(this, (width, height));
|
||||
}
|
||||
}
|
||||
|
||||
public void RenderFrame(Action<SKCanvas, SKImageInfo> render)
|
||||
{
|
||||
if (_canvas != null && _bitmap != null)
|
||||
{
|
||||
render(_canvas, _imageInfo);
|
||||
_canvas.Flush();
|
||||
CairoNative.cairo_surface_flush(_cairoSurface);
|
||||
CairoNative.cairo_surface_mark_dirty(_cairoSurface);
|
||||
GtkNative.gtk_widget_queue_draw(_widget);
|
||||
}
|
||||
}
|
||||
|
||||
public void Invalidate()
|
||||
{
|
||||
GtkNative.gtk_widget_queue_draw(_widget);
|
||||
}
|
||||
|
||||
public void SetTransparent(bool transparent)
|
||||
{
|
||||
_isTransparent = transparent;
|
||||
}
|
||||
|
||||
private bool OnDraw(IntPtr widget, IntPtr cairoContext, IntPtr userData)
|
||||
{
|
||||
if (_cairoSurface == IntPtr.Zero || cairoContext == IntPtr.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_isTransparent)
|
||||
{
|
||||
_canvas?.Clear(SKColors.Transparent);
|
||||
}
|
||||
|
||||
DrawRequested?.Invoke(this, EventArgs.Empty);
|
||||
_canvas?.Flush();
|
||||
|
||||
CairoNative.cairo_surface_flush(_cairoSurface);
|
||||
CairoNative.cairo_surface_mark_dirty(_cairoSurface);
|
||||
CairoNative.cairo_set_source_surface(cairoContext, _cairoSurface, 0.0, 0.0);
|
||||
CairoNative.cairo_paint(cairoContext);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool OnConfigure(IntPtr widget, IntPtr eventData, IntPtr userData)
|
||||
{
|
||||
GtkNative.gtk_widget_get_allocation(widget, out var allocation);
|
||||
if (allocation.Width > 0 && allocation.Height > 0 &&
|
||||
(allocation.Width != _imageInfo.Width || allocation.Height != _imageInfo.Height))
|
||||
{
|
||||
Resize(allocation.Width, allocation.Height);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool OnButtonPress(IntPtr widget, IntPtr eventData, IntPtr userData)
|
||||
{
|
||||
GtkNative.gtk_widget_grab_focus(_widget);
|
||||
var (x, y, button) = ParseButtonEvent(eventData);
|
||||
Console.WriteLine($"[GtkSkiaSurfaceWidget] ButtonPress at ({x}, {y}), button={button}");
|
||||
PointerPressed?.Invoke(this, (x, y, button));
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool OnButtonRelease(IntPtr widget, IntPtr eventData, IntPtr userData)
|
||||
{
|
||||
var (x, y, button) = ParseButtonEvent(eventData);
|
||||
PointerReleased?.Invoke(this, (x, y, button));
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool OnMotion(IntPtr widget, IntPtr eventData, IntPtr userData)
|
||||
{
|
||||
var (x, y) = ParseMotionEvent(eventData);
|
||||
PointerMoved?.Invoke(this, (x, y));
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RaisePointerPressed(double x, double y, int button)
|
||||
{
|
||||
Console.WriteLine($"[GtkSkiaSurfaceWidget] RaisePointerPressed at ({x}, {y}), button={button}");
|
||||
PointerPressed?.Invoke(this, (x, y, button));
|
||||
}
|
||||
|
||||
public void RaisePointerReleased(double x, double y, int button)
|
||||
{
|
||||
PointerReleased?.Invoke(this, (x, y, button));
|
||||
}
|
||||
|
||||
public void RaisePointerMoved(double x, double y)
|
||||
{
|
||||
PointerMoved?.Invoke(this, (x, y));
|
||||
}
|
||||
|
||||
private bool OnKeyPress(IntPtr widget, IntPtr eventData, IntPtr userData)
|
||||
{
|
||||
var (keyval, keycode, state) = ParseKeyEvent(eventData);
|
||||
KeyPressed?.Invoke(this, (keyval, keycode, state));
|
||||
|
||||
uint unicode = GdkNative.gdk_keyval_to_unicode(keyval);
|
||||
if (unicode != 0 && unicode < 65536)
|
||||
{
|
||||
char c = (char)unicode;
|
||||
if (!char.IsControl(c) || c == '\r' || c == '\n' || c == '\t')
|
||||
{
|
||||
string text = c.ToString();
|
||||
Console.WriteLine($"[GtkSkiaSurfaceWidget] TextInput: '{text}' (keyval={keyval}, unicode={unicode})");
|
||||
TextInput?.Invoke(this, text);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool OnKeyRelease(IntPtr widget, IntPtr eventData, IntPtr userData)
|
||||
{
|
||||
var (keyval, keycode, state) = ParseKeyEvent(eventData);
|
||||
KeyReleased?.Invoke(this, (keyval, keycode, state));
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool OnScroll(IntPtr widget, IntPtr eventData, IntPtr userData)
|
||||
{
|
||||
var (x, y, deltaX, deltaY) = ParseScrollEvent(eventData);
|
||||
Scrolled?.Invoke(this, (x, y, deltaX, deltaY));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static (double x, double y, int button) ParseButtonEvent(IntPtr eventData)
|
||||
{
|
||||
var evt = Marshal.PtrToStructure<GdkEventButton>(eventData);
|
||||
return (evt.x, evt.y, (int)evt.button);
|
||||
}
|
||||
|
||||
private static (double x, double y) ParseMotionEvent(IntPtr eventData)
|
||||
{
|
||||
var evt = Marshal.PtrToStructure<GdkEventMotion>(eventData);
|
||||
return (evt.x, evt.y);
|
||||
}
|
||||
|
||||
private static (uint keyval, uint keycode, uint state) ParseKeyEvent(IntPtr eventData)
|
||||
{
|
||||
var evt = Marshal.PtrToStructure<GdkEventKey>(eventData);
|
||||
return (evt.keyval, evt.hardware_keycode, evt.state);
|
||||
}
|
||||
|
||||
private static (double x, double y, double deltaX, double deltaY) ParseScrollEvent(IntPtr eventData)
|
||||
{
|
||||
var evt = Marshal.PtrToStructure<GdkEventScroll>(eventData);
|
||||
double deltaX = 0.0;
|
||||
double deltaY = 0.0;
|
||||
|
||||
if (evt.direction == 4) // GDK_SCROLL_SMOOTH
|
||||
{
|
||||
deltaX = evt.delta_x;
|
||||
deltaY = evt.delta_y;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (evt.direction)
|
||||
{
|
||||
case 0: // GDK_SCROLL_UP
|
||||
deltaY = -1.0;
|
||||
break;
|
||||
case 1: // GDK_SCROLL_DOWN
|
||||
deltaY = 1.0;
|
||||
break;
|
||||
case 2: // GDK_SCROLL_LEFT
|
||||
deltaX = -1.0;
|
||||
break;
|
||||
case 3: // GDK_SCROLL_RIGHT
|
||||
deltaX = 1.0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (evt.x, evt.y, deltaX, deltaY);
|
||||
}
|
||||
|
||||
public void GrabFocus()
|
||||
{
|
||||
GtkNative.gtk_widget_grab_focus(_widget);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_canvas?.Dispose();
|
||||
_canvas = null;
|
||||
|
||||
_bitmap?.Dispose();
|
||||
_bitmap = null;
|
||||
|
||||
if (_cairoSurface != IntPtr.Zero)
|
||||
{
|
||||
CairoNative.cairo_surface_destroy(_cairoSurface);
|
||||
_cairoSurface = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Rendering;
|
||||
|
||||
public class LayeredRenderer : IDisposable
|
||||
{
|
||||
private readonly Dictionary<int, RenderLayer> _layers = new();
|
||||
private readonly object _lock = new();
|
||||
private bool _disposed;
|
||||
|
||||
public RenderLayer GetLayer(int zIndex)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_layers.TryGetValue(zIndex, out var layer))
|
||||
{
|
||||
layer = new RenderLayer(zIndex);
|
||||
_layers[zIndex] = layer;
|
||||
}
|
||||
return layer;
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveLayer(int zIndex)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_layers.TryGetValue(zIndex, out var layer))
|
||||
{
|
||||
layer.Dispose();
|
||||
_layers.Remove(zIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Composite(SKCanvas canvas, SKRect bounds)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (var layer in _layers.Values.OrderBy(l => l.ZIndex))
|
||||
{
|
||||
layer.DrawTo(canvas, bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void InvalidateAll()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (var layer in _layers.Values)
|
||||
{
|
||||
layer.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (var layer in _layers.Values)
|
||||
{
|
||||
layer.Dispose();
|
||||
}
|
||||
_layers.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -236,3 +236,291 @@ public class RenderCache : IDisposable
|
||||
public int AccessCount { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides layered rendering for separating static and dynamic content.
|
||||
/// </summary>
|
||||
public class LayeredRenderer : IDisposable
|
||||
{
|
||||
private readonly Dictionary<int, RenderLayer> _layers = new();
|
||||
private readonly object _lock = new();
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or creates a render layer.
|
||||
/// </summary>
|
||||
public RenderLayer GetLayer(int zIndex)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (!_layers.TryGetValue(zIndex, out var layer))
|
||||
{
|
||||
layer = new RenderLayer(zIndex);
|
||||
_layers[zIndex] = layer;
|
||||
}
|
||||
return layer;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a render layer.
|
||||
/// </summary>
|
||||
public void RemoveLayer(int zIndex)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_layers.TryGetValue(zIndex, out var layer))
|
||||
{
|
||||
layer.Dispose();
|
||||
_layers.Remove(zIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Composites all layers onto the target canvas.
|
||||
/// </summary>
|
||||
public void Composite(SKCanvas canvas, SKRect bounds)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (var layer in _layers.Values.OrderBy(l => l.ZIndex))
|
||||
{
|
||||
layer.DrawTo(canvas, bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalidates all layers.
|
||||
/// </summary>
|
||||
public void InvalidateAll()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (var layer in _layers.Values)
|
||||
{
|
||||
layer.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (var layer in _layers.Values)
|
||||
{
|
||||
layer.Dispose();
|
||||
}
|
||||
_layers.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single render layer with its own bitmap buffer.
|
||||
/// </summary>
|
||||
public class RenderLayer : IDisposable
|
||||
{
|
||||
private SKBitmap? _bitmap;
|
||||
private SKCanvas? _canvas;
|
||||
private bool _isDirty = true;
|
||||
private SKRect _bounds;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Z-index of this layer.
|
||||
/// </summary>
|
||||
public int ZIndex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this layer needs to be redrawn.
|
||||
/// </summary>
|
||||
public bool IsDirty => _isDirty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether this layer is visible.
|
||||
/// </summary>
|
||||
public bool IsVisible { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the layer opacity (0-1).
|
||||
/// </summary>
|
||||
public float Opacity { get; set; } = 1f;
|
||||
|
||||
public RenderLayer(int zIndex)
|
||||
{
|
||||
ZIndex = zIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prepares the layer for rendering.
|
||||
/// </summary>
|
||||
public SKCanvas BeginDraw(SKRect bounds)
|
||||
{
|
||||
if (_bitmap == null || _bounds != bounds)
|
||||
{
|
||||
_bitmap?.Dispose();
|
||||
_canvas?.Dispose();
|
||||
|
||||
int width = Math.Max(1, (int)bounds.Width);
|
||||
int height = Math.Max(1, (int)bounds.Height);
|
||||
|
||||
_bitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
_canvas = new SKCanvas(_bitmap);
|
||||
_bounds = bounds;
|
||||
}
|
||||
|
||||
_canvas!.Clear(SKColors.Transparent);
|
||||
_isDirty = false;
|
||||
return _canvas;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks the layer as needing redraw.
|
||||
/// </summary>
|
||||
public void Invalidate()
|
||||
{
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws this layer to the target canvas.
|
||||
/// </summary>
|
||||
public void DrawTo(SKCanvas canvas, SKRect bounds)
|
||||
{
|
||||
if (!IsVisible || _bitmap == null) return;
|
||||
|
||||
using var paint = new SKPaint
|
||||
{
|
||||
Color = SKColors.White.WithAlpha((byte)(Opacity * 255))
|
||||
};
|
||||
|
||||
canvas.DrawBitmap(_bitmap, bounds.Left, bounds.Top, paint);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
_canvas?.Dispose();
|
||||
_bitmap?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides text rendering optimization with glyph caching.
|
||||
/// </summary>
|
||||
public class TextRenderCache : IDisposable
|
||||
{
|
||||
private readonly Dictionary<TextCacheKey, SKBitmap> _cache = new();
|
||||
private readonly object _lock = new();
|
||||
private int _maxEntries = 500;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of cached text entries.
|
||||
/// </summary>
|
||||
public int MaxEntries
|
||||
{
|
||||
get => _maxEntries;
|
||||
set => _maxEntries = Math.Max(10, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a cached text bitmap or creates one.
|
||||
/// </summary>
|
||||
public SKBitmap GetOrCreate(string text, SKPaint paint)
|
||||
{
|
||||
var key = new TextCacheKey(text, paint);
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_cache.TryGetValue(key, out var cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
// Create text bitmap
|
||||
var bounds = new SKRect();
|
||||
paint.MeasureText(text, ref bounds);
|
||||
|
||||
int width = Math.Max(1, (int)Math.Ceiling(bounds.Width) + 2);
|
||||
int height = Math.Max(1, (int)Math.Ceiling(bounds.Height) + 2);
|
||||
|
||||
var bitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
|
||||
using (var canvas = new SKCanvas(bitmap))
|
||||
{
|
||||
canvas.Clear(SKColors.Transparent);
|
||||
canvas.DrawText(text, -bounds.Left + 1, -bounds.Top + 1, paint);
|
||||
}
|
||||
|
||||
// Trim cache if needed
|
||||
if (_cache.Count >= _maxEntries)
|
||||
{
|
||||
var oldest = _cache.First();
|
||||
oldest.Value.Dispose();
|
||||
_cache.Remove(oldest.Key);
|
||||
}
|
||||
|
||||
_cache[key] = bitmap;
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all cached text.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (var entry in _cache.Values)
|
||||
{
|
||||
entry.Dispose();
|
||||
}
|
||||
_cache.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
Clear();
|
||||
}
|
||||
|
||||
private readonly struct TextCacheKey : IEquatable<TextCacheKey>
|
||||
{
|
||||
private readonly string _text;
|
||||
private readonly float _textSize;
|
||||
private readonly SKColor _color;
|
||||
private readonly int _weight;
|
||||
private readonly int _hashCode;
|
||||
|
||||
public TextCacheKey(string text, SKPaint paint)
|
||||
{
|
||||
_text = text;
|
||||
_textSize = paint.TextSize;
|
||||
_color = paint.Color;
|
||||
_weight = paint.Typeface?.FontWeight ?? (int)SKFontStyleWeight.Normal;
|
||||
_hashCode = HashCode.Combine(_text, _textSize, _color, _weight);
|
||||
}
|
||||
|
||||
public bool Equals(TextCacheKey other)
|
||||
{
|
||||
return _text == other._text &&
|
||||
Math.Abs(_textSize - other._textSize) < 0.001f &&
|
||||
_color == other._color &&
|
||||
_weight == other._weight;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj) => obj is TextCacheKey other && Equals(other);
|
||||
public override int GetHashCode() => _hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +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 SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Rendering;
|
||||
|
||||
public class RenderLayer : IDisposable
|
||||
{
|
||||
private SKBitmap? _bitmap;
|
||||
private SKCanvas? _canvas;
|
||||
private bool _isDirty = true;
|
||||
private SKRect _bounds;
|
||||
private bool _disposed;
|
||||
|
||||
public int ZIndex { get; }
|
||||
|
||||
public bool IsDirty => _isDirty;
|
||||
|
||||
public bool IsVisible { get; set; } = true;
|
||||
|
||||
public float Opacity { get; set; } = 1f;
|
||||
|
||||
public RenderLayer(int zIndex)
|
||||
{
|
||||
ZIndex = zIndex;
|
||||
}
|
||||
|
||||
public SKCanvas BeginDraw(SKRect bounds)
|
||||
{
|
||||
if (_bitmap == null || _bounds != bounds)
|
||||
{
|
||||
_bitmap?.Dispose();
|
||||
_canvas?.Dispose();
|
||||
|
||||
var width = Math.Max(1, (int)bounds.Width);
|
||||
var height = Math.Max(1, (int)bounds.Height);
|
||||
_bitmap = new SKBitmap(width, height, SKColorType.Bgra8888, SKAlphaType.Premul);
|
||||
_canvas = new SKCanvas(_bitmap);
|
||||
_bounds = bounds;
|
||||
}
|
||||
|
||||
_canvas!.Clear(SKColors.Transparent);
|
||||
_isDirty = false;
|
||||
return _canvas;
|
||||
}
|
||||
|
||||
public void Invalidate()
|
||||
{
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
public void DrawTo(SKCanvas canvas, SKRect bounds)
|
||||
{
|
||||
if (!IsVisible || _bitmap == null) return;
|
||||
|
||||
using var paint = new SKPaint
|
||||
{
|
||||
Color = SKColors.White.WithAlpha((byte)(Opacity * 255f))
|
||||
};
|
||||
canvas.DrawBitmap(_bitmap, bounds.Left, bounds.Top, paint);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
_canvas?.Dispose();
|
||||
_bitmap?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,43 +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 SkiaSharp;
|
||||
|
||||
namespace Microsoft.Maui.Platform.Linux.Rendering;
|
||||
|
||||
public class ResourceCache : IDisposable
|
||||
{
|
||||
private readonly Dictionary<string, SKTypeface> _typefaces = new();
|
||||
private bool _disposed;
|
||||
|
||||
public SKTypeface GetTypeface(string fontFamily, SKFontStyle style)
|
||||
{
|
||||
var key = $"{fontFamily}_{style.Weight}_{style.Width}_{style.Slant}";
|
||||
|
||||
if (!_typefaces.TryGetValue(key, out var typeface))
|
||||
{
|
||||
typeface = SKTypeface.FromFamilyName(fontFamily, style) ?? SKTypeface.Default;
|
||||
_typefaces[key] = typeface;
|
||||
}
|
||||
|
||||
return typeface;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var typeface in _typefaces.Values)
|
||||
{
|
||||
typeface.Dispose();
|
||||
}
|
||||
_typefaces.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
Clear();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user