Files

14 KiB
Raw Permalink Blame History

AtomUI.Native — macOS 平台 API 文档

概述

macOS 平台通过 Objective-C Runtime(libobjc.A.dylib)直接调用 AppKit 框架的 NSWindow / NSView API,实现窗口鼠标事件穿透、标题栏红绿灯按钮位置控制及窗口关闭按钮状态管理。所有 API 均标注 [SupportedOSPlatform("macos")]。


1. 公共扩展方法

1.1 SetWindowIgnoreMouseEvents

设置窗口是否忽略(穿透)鼠标输入事件。

internal static void SetWindowIgnoreMouseEvents(this WindowBase window, bool flag)

参数:

参数 类型 说明
window WindowBase Avalonia 窗口实例
flag bool true 启用穿透,false 禁用穿透

异常:

异常类型 条件
ArgumentException 窗口句柄无效(IntPtr.Zero)

macOS 实现原理:

调用 NSWindow 的 setIgnoresMouseEvents: 方法:

[nsWindow setIgnoresMouseEvents:flag];

通过 ObjC Runtime 消息发送实现:

objc_msgSend_void_bool(handle, SetIgnoresMouseEventsSelector, flag);

1.2 IsWindowIgnoreMouseEvents

查询窗口当前是否处于鼠标事件穿透状态。

internal static bool IsWindowIgnoreMouseEvents(this WindowBase window)

参数:

参数 类型 说明
window WindowBase Avalonia 窗口实例

返回值:

类型 说明
bool true 表示窗口正在穿透鼠标事件

macOS 实现原理:

调用 NSWindow 的 ignoresMouseEvents 属性 getter:

objc_msgSend_bool(handle, IgnoresMouseEventsSelector);

1.3 SetMacOSOptionButtonsPosition ⟨macOS 专属⟩

自定义窗口标题栏红绿灯按钮(关闭、最小化、缩放)的位置。

[SupportedOSPlatform("macos")]
public static void SetMacOSOptionButtonsPosition(
    this WindowBase window,
    double x,
    double y,
    double spacing = 10.0)

参数:

参数 类型 默认值 说明
window WindowBase — Avalonia 窗口实例
x double — 第一个按钮(关闭按钮)的 X 坐标偏移
y double — 按钮的 Y 坐标偏移(Cocoa 坐标系,取负值向下移动)
spacing double 10.0 按钮之间的水平间距

异常:

异常类型 条件
ArgumentException 窗口句柄无效

行为说明:

  • 在设计模式(Design.IsDesignMode)下直接返回,不执行任何操作
  • 按钮按 Close → Minimize → Zoom 的顺序从左到右排列
  • 每个按钮的 Y 坐标使用 -y(Cocoa 坐标系 Y 轴向上,取负实现向下偏移)
  • 修改完成后强制重绘标题栏(setNeedsDisplay: on superview)

实现流程:

1. GetStandardWindowButton(CloseButton)      → 获取关闭按钮 NSView
2. GetStandardWindowButton(MiniaturizeButton) → 获取最小化按钮 NSView
3. GetStandardWindowButton(ZoomButton)        → 获取缩放按钮 NSView
4. 逐个设置 frame.Origin = (offset, -y)
5. offset += spacing + buttonWidth
6. SetNeedsDisplay(superview, true)           → 强制重绘标题栏

1.4 GetMacOSOptionsSize ⟨macOS 专属⟩

获取窗口标题栏红绿灯按钮组的总尺寸。

[SupportedOSPlatform("macos")]
public static Size GetMacOSOptionsSize(this WindowBase window, double spacing = 10.0)

参数:

参数 类型 默认值 说明
window WindowBase — Avalonia 窗口实例
spacing double 10.0 按钮之间的间距(用于计算总宽度)

返回值:

类型 说明
Size 三个按钮组的总宽度和最大高度。设计模式下返回 default

计算逻辑:

totalWidth = closeBtn.Width + spacing
           + minimizeBtn.Width + spacing
           + zoomBtn.Width
maxHeight  = max(closeBtn.Height, minimizeBtn.Height, zoomBtn.Height)

1.5 SetMacOSWindowClosable ⟨macOS 专属⟩

启用或禁用窗口的关闭按钮。

[SupportedOSPlatform("macos")]
public static void SetMacOSWindowClosable(this WindowBase window, bool flag)

参数:

参数 类型 说明
window WindowBase Avalonia 窗口实例
flag bool true 启用关闭按钮,false 禁用关闭按钮

实现流程:

1. GetStandardWindowButton(CloseButton)   → 获取关闭按钮 NSView
2. GetStyleMask(window)                   → 读取当前样式掩码
3. SetStyleMask(window, styleMask)        → 重新设置样式掩码以刷新按钮状态
4. SetEnabled(closeButton, flag)          → 设置按钮启用/禁用

2. P/Invoke 声明

命名空间:AtomUI.Native.MacOS
类:internal static class WindowUtilsInterop
平台:[SupportedOSPlatform("macos")]

2.1 Objective-C Runtime 核心函数

objc_getClass

获取指定名称的 Objective-C 类对象。

[DllImport("/usr/lib/libobjc.A.dylib")]
public static extern IntPtr objc_getClass(string className);
参数 类型 说明
className string ObjC 类名(如 "NSWindow")

返回值: 类对象指针(IntPtr)。


sel_registerName

注册或获取一个 Objective-C Selector。

[DllImport("/usr/lib/libobjc.A.dylib")]
public static extern IntPtr sel_registerName(string selectorName);
参数 类型 说明
selectorName string Selector 名称(如 "setIgnoresMouseEvents:")

返回值: Selector 指针(IntPtr)。


objc_msgSend 重载族

Objective-C 消息发送函数的不同签名重载:

函数签名 返回类型 参数 说明
objc_msgSend_intptr(IntPtr, IntPtr) IntPtr receiver, selector 无参消息,返回指针
objc_msgSend_intptr_long(IntPtr, IntPtr, long) IntPtr receiver, selector, long arg 单 long 参数消息
objc_msgSend_void(IntPtr, IntPtr) void receiver, selector 无参无返回值消息
objc_msgSend_void_bool(IntPtr, IntPtr, bool) void receiver, selector, bool arg 单 bool 参数消息
objc_msgSend_bool(IntPtr, IntPtr) bool receiver, selector 无参消息,返回 bool
objc_msgSend_void_rect(IntPtr, IntPtr, NSRectFull) void receiver, selector, rect 单 NSRect 参数消息
objc_msgSend_rect(IntPtr, IntPtr) NSRectFull receiver, selector 无参消息,返回 NSRect
objc_msgSend_ulong(IntPtr, IntPtr) ulong receiver, selector 无参消息,返回 ulong
objc_msgSend_void_ulong(IntPtr, IntPtr, ulong) void receiver, selector, ulong arg 单 ulong 参数消息

2.2 预注册 Selector 缓存

所有常用 Selector 在静态字段中预注册,避免运行时重复查找:

字段名 Selector 字符串 对应 ObjC 方法
SetIgnoresMouseEventsSelector "setIgnoresMouseEvents:" [NSWindow setIgnoresMouseEvents:]
IgnoresMouseEventsSelector "ignoresMouseEvents" [NSWindow ignoresMouseEvents]
StandardWindowButtonSelector "standardWindowButton:" [NSWindow standardWindowButton:]
FrameSelector "frame" [NSView frame]
SetFrameSelector "setFrame:" [NSView setFrame:]
SuperviewSelector "superview" [NSView superview]
SetNeedsDisplaySelector "setNeedsDisplay:" [NSView setNeedsDisplay:]
StyleMaskSelector "styleMask" [NSWindow styleMask]
SetStyleMaskSelector "setStyleMask:" [NSWindow setStyleMask:]
SetEnabledSelector "setEnabled:" [NSControl setEnabled:]

2.3 辅助方法

GetStandardWindowButton

获取窗口的标准按钮(关闭、最小化、缩放等)。

public static IntPtr GetStandardWindowButton(IntPtr window, long buttonType)
参数 类型 说明
window IntPtr NSWindow 指针
buttonType long 按钮类型(参见 NSWindowButton 枚举)

返回值: 按钮的 NSView 指针。


GetFrame / SetFrame

获取或设置 NSView 的 frame 矩形。

public static NSRectFull GetFrame(IntPtr view)
public static void SetFrame(IntPtr view, NSRectFull frame)

GetSuperview

获取 NSView 的父视图。

public static IntPtr GetSuperview(IntPtr view)

SetNeedsDisplay

标记 NSView 需要重绘。

public static void SetNeedsDisplay(IntPtr view, bool flag)

GetStyleMask

获取窗口的样式掩码。

public static ulong GetStyleMask(IntPtr window)

SetEnabled

设置控件(如按钮)的启用状态。

public static void SetEnabled(IntPtr control, bool enabled)

2.4 结构体定义

CGPoint

Core Graphics 点结构体。

[StructLayout(LayoutKind.Sequential)]
public struct CGPoint
{
    public double X;
    public double Y;
}

CGSize

Core Graphics 尺寸结构体。

[StructLayout(LayoutKind.Sequential)]
public struct CGSize
{
    public double Width;
    public double Height;
}

NSRect

AppKit 矩形结构体(CGPoint + CGSize 版本)。

[StructLayout(LayoutKind.Sequential)]
public struct NSRect
{
    public CGPoint Origin;
    public CGSize Size;
}

NSPoint / NSSize

AppKit 点和尺寸结构体。

[StructLayout(LayoutKind.Sequential)]
public struct NSPoint { public double X; public double Y; }

[StructLayout(LayoutKind.Sequential)]
public struct NSSize { public double Width; public double Height; }

NSRectFull

AppKit 矩形结构体(NSPoint + NSSize 版本),用于 frame 操作。

[StructLayout(LayoutKind.Sequential)]
public struct NSRectFull
{
    public NSPoint Origin;
    public NSSize Size;
}

2.5 枚举定义

NSWindowButton

标准窗口按钮类型。

public enum NSWindowButton : long
{
    CloseButton            = 0,  // 关闭按钮(红色)
    MiniaturizeButton      = 1,  // 最小化按钮(黄色)
    ZoomButton             = 2,  // 缩放按钮(绿色)
    ToolbarButton          = 3,  // 工具栏按钮
    DocumentIconButton     = 4,  // 文档图标按钮
    DocumentVersionsButton = 5,  // 文档版本按钮
    FullScreenButton       = 7   // 全屏按钮
}

WindowButtonType

窗口按钮类型(与 NSWindowButton 对应)。

public enum WindowButtonType : long
{
    CloseButton            = 0,
    MinimizeButton         = 1,
    ZoomButton             = 2,
    ToolbarButton          = 3,
    DocumentIconButton     = 4,
    DocumentVersionsButton = 5,
    FullScreenButton       = 7
}

NSWindowStyleMask

窗口样式掩码(Flags 枚举)。

[Flags]
public enum NSWindowStyleMask : ulong
{
    Borderless             = 0,         // 无边框
    Titled                 = 1 << 0,    // 带标题栏
    Closable               = 1 << 1,    // 可关闭
    Miniaturizable         = 1 << 2,    // 可最小化
    Resizable              = 1 << 3,    // 可调整大小
    TexturedBackground     = 1 << 8,    // 纹理背景
    UnifiedTitleAndToolbar = 1 << 12,   // 统一标题和工具栏
    FullScreen             = 1 << 14,   // 全屏
    FullSizeContentView    = 1 << 15,   // 全尺寸内容视图
    UtilityWindow          = 1 << 4,    // 工具窗口
    DocModalWindow         = 1 << 6,    // 文档模态窗口
    NonactivatingPanel     = 1 << 7,    // 非激活面板
    HUDWindow              = 1 << 13    // HUD 窗口
}

NSWindowCollectionBehavior

窗口集合行为(Flags 枚举)。

[Flags]
public enum NSWindowCollectionBehavior : ulong
{
    Default                    = 0,
    CanJoinAllSpaces           = 1 << 0,
    MoveToActiveSpace          = 1 << 1,
    Managed                    = 1 << 2,
    Transient                  = 1 << 3,
    Stationary                 = 1 << 4,
    ParticipatesInCycle        = 1 << 5,
    IgnoresCycle               = 1 << 6,
    FullScreenPrimary          = 1 << 7,
    FullScreenAuxiliary        = 1 << 8,
    FullScreenNone             = 1 << 9,
    FullScreenAllowsTiling     = 1 << 11,
    FullScreenDisallowsTiling  = 1 << 12
}

NSWindowLevel

窗口层级。

public enum NSWindowLevel : int
{
    Normal      = 0,
    Floating    = 3,
    ModalPanel  = 8,
    MainMenu    = 24,
    StatusBar   = 25,
    PopUpMenu   = 101,
    ScreenSaver = 1000
}

NSBackingStoreType

窗口后备存储类型。

public enum NSBackingStoreType : ulong
{
    Buffered = 2
}

3. 使用示例

// 启用窗口鼠标事件穿透
window.SetWindowIgnoreMouseEvents(true);

// 查询是否处于穿透状态
bool isPassThrough = window.IsWindowIgnoreMouseEvents();

// 自定义红绿灯按钮位置
if (OperatingSystem.IsMacOS())
{
    window.SetMacOSOptionButtonsPosition(x: 12, y: 8, spacing: 8);
}

// 获取红绿灯按钮组总尺寸
if (OperatingSystem.IsMacOS())
{
    Size buttonsSize = window.GetMacOSOptionsSize(spacing: 8);
    // buttonsSize.Width = 三个按钮宽度 + 2 × spacing
    // buttonsSize.Height = 最高按钮的高度
}

// 禁用窗口关闭按钮
if (OperatingSystem.IsMacOS())
{
    window.SetMacOSWindowClosable(false);
}

4. 注意事项

  1. 设计模式保护 — SetMacOSOptionButtonsPosition、GetMacOSOptionsSize、SetMacOSWindowClosable 在 Design.IsDesignMode 下直接返回,避免 XAML 预览器中的原生调用崩溃
  2. Cocoa 坐标系 — macOS 使用左下角原点坐标系,Y 轴向上。代码中 frame.Origin.Y = -y 将逻辑上的"向下偏移"转换为 Cocoa 坐标
  3. Selector 缓存 — 所有 Selector 通过 static readonly 预注册,避免每次调用 sel_registerName 的开销
  4. 按钮可能为空 — 如果窗口样式不包含某些按钮(如 Borderless 窗口),GetStandardWindowButton 可能返回 IntPtr.Zero,代码中已做非空检查
  5. 强制重绘 — 修改按钮位置后需要调用 SetNeedsDisplay(superview, true) 触发标题栏重绘,否则视觉位置不会更新