2025-12-19 09:30:16 +00:00
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Maui.Handlers ;
using Microsoft.Maui.Graphics ;
using Microsoft.Maui.Controls ;
using Microsoft.Maui.Platform ;
using SkiaSharp ;
namespace Microsoft.Maui.Platform.Linux.Handlers ;
/// <summary>
/// Handler for Window on Linux.
/// Maps IWindow to the Linux display window system.
/// </summary>
public partial class WindowHandler : ElementHandler < IWindow , SkiaWindow >
{
public static IPropertyMapper < IWindow , WindowHandler > Mapper =
new PropertyMapper < IWindow , WindowHandler > ( ElementHandler . ElementMapper )
{
[nameof(IWindow.Title)] = MapTitle ,
[nameof(IWindow.Content)] = MapContent ,
[nameof(IWindow.X)] = MapX ,
[nameof(IWindow.Y)] = MapY ,
[nameof(IWindow.Width)] = MapWidth ,
[nameof(IWindow.Height)] = MapHeight ,
[nameof(IWindow.MinimumWidth)] = MapMinimumWidth ,
[nameof(IWindow.MinimumHeight)] = MapMinimumHeight ,
[nameof(IWindow.MaximumWidth)] = MapMaximumWidth ,
[nameof(IWindow.MaximumHeight)] = MapMaximumHeight ,
} ;
public static CommandMapper < IWindow , WindowHandler > CommandMapper =
new ( ElementHandler . ElementCommandMapper )
{
} ;
public WindowHandler ( ) : base ( Mapper , CommandMapper )
{
}
public WindowHandler ( IPropertyMapper ? mapper , CommandMapper ? commandMapper = null )
: base ( mapper ? ? Mapper , commandMapper ? ? CommandMapper )
{
}
protected override SkiaWindow CreatePlatformElement ( )
{
return new SkiaWindow ( ) ;
}
protected override void ConnectHandler ( SkiaWindow platformView )
{
base . ConnectHandler ( platformView ) ;
platformView . CloseRequested + = OnCloseRequested ;
platformView . SizeChanged + = OnSizeChanged ;
}
protected override void DisconnectHandler ( SkiaWindow platformView )
{
platformView . CloseRequested - = OnCloseRequested ;
platformView . SizeChanged - = OnSizeChanged ;
base . DisconnectHandler ( platformView ) ;
}
private void OnCloseRequested ( object? sender , EventArgs e )
{
VirtualView ? . Destroying ( ) ;
}
private void OnSizeChanged ( object? sender , SizeChangedEventArgs e )
{
VirtualView ? . FrameChanged ( new Rect ( 0 , 0 , e . Width , e . Height ) ) ;
}
public static void MapTitle ( WindowHandler handler , IWindow window )
{
if ( handler . PlatformView is null ) return ;
handler . PlatformView . Title = window . Title ? ? "MAUI Application" ;
}
public static void MapContent ( WindowHandler handler , IWindow window )
{
2026-01-17 08:06:22 +00:00
Console . Error . WriteLine ( $"[WindowHandler] MapContent - PlatformView={handler.PlatformView != null}" ) ;
2025-12-19 09:30:16 +00:00
if ( handler . PlatformView is null ) return ;
var content = window . Content ;
2026-01-17 08:06:22 +00:00
Console . Error . WriteLine ( $"[WindowHandler] MapContent - content type={content?.GetType().Name}, handler={content?.Handler?.GetType().Name}" ) ;
2025-12-19 09:30:16 +00:00
if ( content ? . Handler ? . PlatformView is SkiaView skiaContent )
{
2026-01-17 08:06:22 +00:00
Console . Error . WriteLine ( $"[WindowHandler] MapContent - setting SkiaView content: {skiaContent.GetType().Name}" ) ;
2025-12-19 09:30:16 +00:00
handler . PlatformView . Content = skiaContent ;
}
2026-01-17 08:06:22 +00:00
else
{
Console . Error . WriteLine ( $"[WindowHandler] MapContent - content has no SkiaView! Handler={content?.Handler}, PlatformView={content?.Handler?.PlatformView}" ) ;
}
2025-12-19 09:30:16 +00:00
}
public static void MapX ( WindowHandler handler , IWindow window )
{
if ( handler . PlatformView is null ) return ;
handler . PlatformView . X = ( int ) window . X ;
}
public static void MapY ( WindowHandler handler , IWindow window )
{
if ( handler . PlatformView is null ) return ;
handler . PlatformView . Y = ( int ) window . Y ;
}
public static void MapWidth ( WindowHandler handler , IWindow window )
{
if ( handler . PlatformView is null ) return ;
handler . PlatformView . Width = ( int ) window . Width ;
}
public static void MapHeight ( WindowHandler handler , IWindow window )
{
if ( handler . PlatformView is null ) return ;
handler . PlatformView . Height = ( int ) window . Height ;
}
public static void MapMinimumWidth ( WindowHandler handler , IWindow window )
{
if ( handler . PlatformView is null ) return ;
handler . PlatformView . MinWidth = ( int ) window . MinimumWidth ;
}
public static void MapMinimumHeight ( WindowHandler handler , IWindow window )
{
if ( handler . PlatformView is null ) return ;
handler . PlatformView . MinHeight = ( int ) window . MinimumHeight ;
}
public static void MapMaximumWidth ( WindowHandler handler , IWindow window )
{
if ( handler . PlatformView is null ) return ;
handler . PlatformView . MaxWidth = ( int ) window . MaximumWidth ;
}
public static void MapMaximumHeight ( WindowHandler handler , IWindow window )
{
if ( handler . PlatformView is null ) return ;
handler . PlatformView . MaxHeight = ( int ) window . MaximumHeight ;
}
}
/// <summary>
/// Skia window wrapper for Linux display servers.
2025-12-21 13:26:56 -05:00
/// Handles rendering of content and popup overlays automatically.
2025-12-19 09:30:16 +00:00
/// </summary>
public class SkiaWindow
{
private SkiaView ? _content ;
private string _title = "MAUI Application" ;
private int _x , _y ;
private int _width = 800 ;
private int _height = 600 ;
private int _minWidth = 100 ;
private int _minHeight = 100 ;
private int _maxWidth = int . MaxValue ;
private int _maxHeight = int . MaxValue ;
public SkiaView ? Content
{
get = > _content ;
set
{
_content = value ;
ContentChanged ? . Invoke ( this , EventArgs . Empty ) ;
}
}
2025-12-21 13:26:56 -05:00
/// <summary>
/// Renders the window content and popup overlays to the canvas.
/// This should be called by the platform rendering loop.
/// </summary>
public void Render ( SKCanvas canvas )
{
// Clear background
canvas . Clear ( SKColors . White ) ;
// Draw main content
if ( _content ! = null )
{
2026-01-17 05:22:37 +00:00
_content . Measure ( new Size ( _width , _height ) ) ;
_content . Arrange ( new Rect ( 0 , 0 , _width , _height ) ) ;
2025-12-21 13:26:56 -05:00
_content . Draw ( canvas ) ;
}
// Draw popup overlays on top (dropdowns, date pickers, etc.)
// This ensures popups always render above all other content
SkiaView . DrawPopupOverlays ( canvas ) ;
}
2025-12-19 09:30:16 +00:00
public string Title
{
get = > _title ;
set
{
_title = value ;
TitleChanged ? . Invoke ( this , EventArgs . Empty ) ;
}
}
public int X
{
get = > _x ;
set { _x = value ; PositionChanged ? . Invoke ( this , EventArgs . Empty ) ; }
}
public int Y
{
get = > _y ;
set { _y = value ; PositionChanged ? . Invoke ( this , EventArgs . Empty ) ; }
}
public int Width
{
get = > _width ;
set
{
_width = Math . Clamp ( value , _minWidth , _maxWidth ) ;
SizeChanged ? . Invoke ( this , new SizeChangedEventArgs ( _width , _height ) ) ;
}
}
public int Height
{
get = > _height ;
set
{
_height = Math . Clamp ( value , _minHeight , _maxHeight ) ;
SizeChanged ? . Invoke ( this , new SizeChangedEventArgs ( _width , _height ) ) ;
}
}
public int MinWidth
{
get = > _minWidth ;
set { _minWidth = value ; }
}
public int MinHeight
{
get = > _minHeight ;
set { _minHeight = value ; }
}
public int MaxWidth
{
get = > _maxWidth ;
set { _maxWidth = value ; }
}
public int MaxHeight
{
get = > _maxHeight ;
set { _maxHeight = value ; }
}
public event EventHandler ? ContentChanged ;
public event EventHandler ? TitleChanged ;
public event EventHandler ? PositionChanged ;
public event EventHandler < SizeChangedEventArgs > ? SizeChanged ;
public event EventHandler ? CloseRequested ;
public void Close ( )
{
CloseRequested ? . Invoke ( this , EventArgs . Empty ) ;
}
}
/// <summary>
/// Event args for window size changes.
/// </summary>
public class SizeChangedEventArgs : EventArgs
{
public int Width { get ; }
public int Height { get ; }
public SizeChangedEventArgs ( int width , int height )
{
Width = width ;
Height = height ;
}
}