Preview 3: Complete control implementation with XAML data binding
Major milestone adding full control functionality: Controls Enhanced: - Entry/Editor: Full keyboard input, cursor navigation, selection, clipboard - CollectionView: Data binding, selection highlighting, scrolling - CheckBox/Switch/Slider: Interactive state management - Picker/DatePicker/TimePicker: Dropdown selection with popup overlays - ProgressBar/ActivityIndicator: Animated progress display - Button: Press/release visual states - Border/Frame: Rounded corners, stroke styling - Label: Text wrapping, alignment, decorations - Grid/StackLayout: Margin and padding support Features Added: - DisplayAlert dialogs with button actions - NavigationPage with toolbar and back navigation - Shell with flyout menu navigation - XAML value converters for data binding - Margin support in all layout containers - Popup overlay system for pickers New Samples: - TodoApp: Full CRUD task manager with NavigationPage - ShellDemo: Comprehensive control showcase Removed: - ControlGallery (replaced by ShellDemo) - LinuxDemo (replaced by TodoApp) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using SkiaSharp;
|
||||
using Microsoft.Maui.Platform.Linux.Window;
|
||||
using Microsoft.Maui.Platform.Linux.Rendering;
|
||||
|
||||
@@ -216,113 +215,25 @@ public class X11DisplayWindow : IDisplayWindow
|
||||
|
||||
/// <summary>
|
||||
/// Wayland display window wrapper implementing IDisplayWindow.
|
||||
/// Uses wl_shm for software rendering with SkiaSharp.
|
||||
/// Uses the full WaylandWindow implementation with xdg-shell protocol.
|
||||
/// </summary>
|
||||
public class WaylandDisplayWindow : IDisplayWindow
|
||||
{
|
||||
#region Native Interop
|
||||
private readonly WaylandWindow _window;
|
||||
|
||||
private const string LibWaylandClient = "libwayland-client.so.0";
|
||||
public int Width => _window.Width;
|
||||
public int Height => _window.Height;
|
||||
public bool IsRunning => _window.IsRunning;
|
||||
|
||||
[DllImport(LibWaylandClient)]
|
||||
private static extern IntPtr wl_display_connect(string? name);
|
||||
/// <summary>
|
||||
/// Gets the pixel data pointer for rendering.
|
||||
/// </summary>
|
||||
public IntPtr PixelData => _window.PixelData;
|
||||
|
||||
[DllImport(LibWaylandClient)]
|
||||
private static extern void wl_display_disconnect(IntPtr display);
|
||||
|
||||
[DllImport(LibWaylandClient)]
|
||||
private static extern int wl_display_dispatch(IntPtr display);
|
||||
|
||||
[DllImport(LibWaylandClient)]
|
||||
private static extern int wl_display_dispatch_pending(IntPtr display);
|
||||
|
||||
[DllImport(LibWaylandClient)]
|
||||
private static extern int wl_display_roundtrip(IntPtr display);
|
||||
|
||||
[DllImport(LibWaylandClient)]
|
||||
private static extern int wl_display_flush(IntPtr display);
|
||||
|
||||
[DllImport(LibWaylandClient)]
|
||||
private static extern IntPtr wl_display_get_registry(IntPtr display);
|
||||
|
||||
[DllImport(LibWaylandClient)]
|
||||
private static extern IntPtr wl_compositor_create_surface(IntPtr compositor);
|
||||
|
||||
[DllImport(LibWaylandClient)]
|
||||
private static extern void wl_surface_attach(IntPtr surface, IntPtr buffer, int x, int y);
|
||||
|
||||
[DllImport(LibWaylandClient)]
|
||||
private static extern void wl_surface_damage(IntPtr surface, int x, int y, int width, int height);
|
||||
|
||||
[DllImport(LibWaylandClient)]
|
||||
private static extern void wl_surface_commit(IntPtr surface);
|
||||
|
||||
[DllImport(LibWaylandClient)]
|
||||
private static extern void wl_surface_destroy(IntPtr surface);
|
||||
|
||||
[DllImport(LibWaylandClient)]
|
||||
private static extern IntPtr wl_shm_create_pool(IntPtr shm, int fd, int size);
|
||||
|
||||
[DllImport(LibWaylandClient)]
|
||||
private static extern void wl_shm_pool_destroy(IntPtr pool);
|
||||
|
||||
[DllImport(LibWaylandClient)]
|
||||
private static extern IntPtr wl_shm_pool_create_buffer(IntPtr pool, int offset, int width, int height, int stride, uint format);
|
||||
|
||||
[DllImport(LibWaylandClient)]
|
||||
private static extern void wl_buffer_destroy(IntPtr buffer);
|
||||
|
||||
[DllImport("libc", EntryPoint = "shm_open")]
|
||||
private static extern int shm_open([MarshalAs(UnmanagedType.LPStr)] string name, int oflag, int mode);
|
||||
|
||||
[DllImport("libc", EntryPoint = "shm_unlink")]
|
||||
private static extern int shm_unlink([MarshalAs(UnmanagedType.LPStr)] string name);
|
||||
|
||||
[DllImport("libc", EntryPoint = "ftruncate")]
|
||||
private static extern int ftruncate(int fd, long length);
|
||||
|
||||
[DllImport("libc", EntryPoint = "mmap")]
|
||||
private static extern IntPtr mmap(IntPtr addr, nuint length, int prot, int flags, int fd, long offset);
|
||||
|
||||
[DllImport("libc", EntryPoint = "munmap")]
|
||||
private static extern int munmap(IntPtr addr, nuint length);
|
||||
|
||||
[DllImport("libc", EntryPoint = "close")]
|
||||
private static extern int close(int fd);
|
||||
|
||||
private const int O_RDWR = 2;
|
||||
private const int O_CREAT = 0x40;
|
||||
private const int O_EXCL = 0x80;
|
||||
private const int PROT_READ = 1;
|
||||
private const int PROT_WRITE = 2;
|
||||
private const int MAP_SHARED = 1;
|
||||
private const uint WL_SHM_FORMAT_XRGB8888 = 1;
|
||||
|
||||
#endregion
|
||||
|
||||
private IntPtr _display;
|
||||
private IntPtr _registry;
|
||||
private IntPtr _compositor;
|
||||
private IntPtr _shm;
|
||||
private IntPtr _surface;
|
||||
private IntPtr _shmPool;
|
||||
private IntPtr _buffer;
|
||||
private IntPtr _pixelData;
|
||||
private int _shmFd = -1;
|
||||
private int _bufferSize;
|
||||
|
||||
private int _width;
|
||||
private int _height;
|
||||
private string _title;
|
||||
private bool _isRunning;
|
||||
private bool _disposed;
|
||||
|
||||
private SKBitmap? _bitmap;
|
||||
private SKCanvas? _canvas;
|
||||
|
||||
public int Width => _width;
|
||||
public int Height => _height;
|
||||
public bool IsRunning => _isRunning;
|
||||
/// <summary>
|
||||
/// Gets the stride (bytes per row) of the pixel buffer.
|
||||
/// </summary>
|
||||
public int Stride => _window.Stride;
|
||||
|
||||
public event EventHandler<KeyEventArgs>? KeyDown;
|
||||
public event EventHandler<KeyEventArgs>? KeyUp;
|
||||
@@ -337,213 +248,27 @@ public class WaylandDisplayWindow : IDisplayWindow
|
||||
|
||||
public WaylandDisplayWindow(string title, int width, int height)
|
||||
{
|
||||
_title = title;
|
||||
_width = width;
|
||||
_height = height;
|
||||
_window = new WaylandWindow(title, width, height);
|
||||
|
||||
Initialize();
|
||||
// Wire up events
|
||||
_window.KeyDown += (s, e) => KeyDown?.Invoke(this, e);
|
||||
_window.KeyUp += (s, e) => KeyUp?.Invoke(this, e);
|
||||
_window.TextInput += (s, e) => TextInput?.Invoke(this, e);
|
||||
_window.PointerMoved += (s, e) => PointerMoved?.Invoke(this, e);
|
||||
_window.PointerPressed += (s, e) => PointerPressed?.Invoke(this, e);
|
||||
_window.PointerReleased += (s, e) => PointerReleased?.Invoke(this, e);
|
||||
_window.Scroll += (s, e) => Scroll?.Invoke(this, e);
|
||||
_window.Exposed += (s, e) => Exposed?.Invoke(this, e);
|
||||
_window.Resized += (s, e) => Resized?.Invoke(this, e);
|
||||
_window.CloseRequested += (s, e) => CloseRequested?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
_display = wl_display_connect(null);
|
||||
if (_display == IntPtr.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to connect to Wayland display. Is WAYLAND_DISPLAY set?");
|
||||
}
|
||||
|
||||
_registry = wl_display_get_registry(_display);
|
||||
if (_registry == IntPtr.Zero)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to get Wayland registry");
|
||||
}
|
||||
|
||||
// Note: A full implementation would set up registry listeners to get
|
||||
// compositor and shm handles. For now, we throw an informative error
|
||||
// and fall back to X11 via XWayland in DisplayServerFactory.
|
||||
|
||||
// This is a placeholder - proper Wayland support requires:
|
||||
// 1. Setting up wl_registry_listener with callbacks
|
||||
// 2. Binding to wl_compositor, wl_shm, wl_seat, xdg_wm_base
|
||||
// 3. Implementing the xdg-shell protocol for toplevel windows
|
||||
|
||||
wl_display_roundtrip(_display);
|
||||
|
||||
// For now, signal that native Wayland isn't fully implemented
|
||||
throw new NotSupportedException(
|
||||
"Native Wayland support is experimental. " +
|
||||
"Set MAUI_PREFER_X11=1 to use XWayland, or run with DISPLAY set.");
|
||||
}
|
||||
|
||||
private void CreateShmBuffer()
|
||||
{
|
||||
int stride = _width * 4;
|
||||
_bufferSize = stride * _height;
|
||||
|
||||
string shmName = $"/maui-shm-{Environment.ProcessId}-{DateTime.Now.Ticks}";
|
||||
_shmFd = shm_open(shmName, O_RDWR | O_CREAT | O_EXCL, 0600);
|
||||
|
||||
if (_shmFd < 0)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to create shared memory file");
|
||||
}
|
||||
|
||||
shm_unlink(shmName);
|
||||
|
||||
if (ftruncate(_shmFd, _bufferSize) < 0)
|
||||
{
|
||||
close(_shmFd);
|
||||
throw new InvalidOperationException("Failed to resize shared memory");
|
||||
}
|
||||
|
||||
_pixelData = mmap(IntPtr.Zero, (nuint)_bufferSize, PROT_READ | PROT_WRITE, MAP_SHARED, _shmFd, 0);
|
||||
if (_pixelData == IntPtr.Zero || _pixelData == new IntPtr(-1))
|
||||
{
|
||||
close(_shmFd);
|
||||
throw new InvalidOperationException("Failed to mmap shared memory");
|
||||
}
|
||||
|
||||
_shmPool = wl_shm_create_pool(_shm, _shmFd, _bufferSize);
|
||||
if (_shmPool == IntPtr.Zero)
|
||||
{
|
||||
munmap(_pixelData, (nuint)_bufferSize);
|
||||
close(_shmFd);
|
||||
throw new InvalidOperationException("Failed to create wl_shm_pool");
|
||||
}
|
||||
|
||||
_buffer = wl_shm_pool_create_buffer(_shmPool, 0, _width, _height, stride, WL_SHM_FORMAT_XRGB8888);
|
||||
if (_buffer == IntPtr.Zero)
|
||||
{
|
||||
wl_shm_pool_destroy(_shmPool);
|
||||
munmap(_pixelData, (nuint)_bufferSize);
|
||||
close(_shmFd);
|
||||
throw new InvalidOperationException("Failed to create wl_buffer");
|
||||
}
|
||||
|
||||
// Create Skia bitmap backed by shared memory
|
||||
var info = new SKImageInfo(_width, _height, SKColorType.Bgra8888, SKAlphaType.Opaque);
|
||||
_bitmap = new SKBitmap();
|
||||
_bitmap.InstallPixels(info, _pixelData, stride);
|
||||
_canvas = new SKCanvas(_bitmap);
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
if (_surface == IntPtr.Zero || _buffer == IntPtr.Zero) return;
|
||||
|
||||
wl_surface_attach(_surface, _buffer, 0, 0);
|
||||
wl_surface_damage(_surface, 0, 0, _width, _height);
|
||||
wl_surface_commit(_surface);
|
||||
wl_display_flush(_display);
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
if (_surface == IntPtr.Zero) return;
|
||||
|
||||
wl_surface_attach(_surface, IntPtr.Zero, 0, 0);
|
||||
wl_surface_commit(_surface);
|
||||
wl_display_flush(_display);
|
||||
}
|
||||
|
||||
public void SetTitle(string title)
|
||||
{
|
||||
_title = title;
|
||||
}
|
||||
|
||||
public void Resize(int width, int height)
|
||||
{
|
||||
if (width == _width && height == _height) return;
|
||||
|
||||
_canvas?.Dispose();
|
||||
_bitmap?.Dispose();
|
||||
|
||||
if (_buffer != IntPtr.Zero)
|
||||
wl_buffer_destroy(_buffer);
|
||||
if (_shmPool != IntPtr.Zero)
|
||||
wl_shm_pool_destroy(_shmPool);
|
||||
if (_pixelData != IntPtr.Zero)
|
||||
munmap(_pixelData, (nuint)_bufferSize);
|
||||
if (_shmFd >= 0)
|
||||
close(_shmFd);
|
||||
|
||||
_width = width;
|
||||
_height = height;
|
||||
|
||||
CreateShmBuffer();
|
||||
Resized?.Invoke(this, (width, height));
|
||||
}
|
||||
|
||||
public void ProcessEvents()
|
||||
{
|
||||
if (!_isRunning || _display == IntPtr.Zero) return;
|
||||
|
||||
wl_display_dispatch_pending(_display);
|
||||
wl_display_flush(_display);
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_isRunning = false;
|
||||
}
|
||||
|
||||
public SKCanvas? GetCanvas() => _canvas;
|
||||
|
||||
public void CommitFrame()
|
||||
{
|
||||
if (_surface != IntPtr.Zero && _buffer != IntPtr.Zero)
|
||||
{
|
||||
wl_surface_attach(_surface, _buffer, 0, 0);
|
||||
wl_surface_damage(_surface, 0, 0, _width, _height);
|
||||
wl_surface_commit(_surface);
|
||||
wl_display_flush(_display);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
_isRunning = false;
|
||||
|
||||
_canvas?.Dispose();
|
||||
_bitmap?.Dispose();
|
||||
|
||||
if (_buffer != IntPtr.Zero)
|
||||
{
|
||||
wl_buffer_destroy(_buffer);
|
||||
_buffer = IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (_shmPool != IntPtr.Zero)
|
||||
{
|
||||
wl_shm_pool_destroy(_shmPool);
|
||||
_shmPool = IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (_pixelData != IntPtr.Zero && _pixelData != new IntPtr(-1))
|
||||
{
|
||||
munmap(_pixelData, (nuint)_bufferSize);
|
||||
_pixelData = IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (_shmFd >= 0)
|
||||
{
|
||||
close(_shmFd);
|
||||
_shmFd = -1;
|
||||
}
|
||||
|
||||
if (_surface != IntPtr.Zero)
|
||||
{
|
||||
wl_surface_destroy(_surface);
|
||||
_surface = IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (_display != IntPtr.Zero)
|
||||
{
|
||||
wl_display_disconnect(_display);
|
||||
_display = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
public void Show() => _window.Show();
|
||||
public void Hide() => _window.Hide();
|
||||
public void SetTitle(string title) => _window.SetTitle(title);
|
||||
public void Resize(int width, int height) => _window.Resize(width, height);
|
||||
public void ProcessEvents() => _window.ProcessEvents();
|
||||
public void Stop() => _window.Stop();
|
||||
public void CommitFrame() => _window.CommitFrame();
|
||||
public void Dispose() => _window.Dispose();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user