23 KiB
Cross-Platform UI Considerations
**Referenced Files in This Document** - [AvaloniaDispatcher.cs](file://ClassicAssist.Avalonia/AvaloniaDispatcher.cs) - [AvaloniaUIInvoker.cs](file://ClassicAssist.Avalonia/AvaloniaUIInvoker.cs) - [IDispatcher.cs](file://ClassicAssist.Shared/IDispatcher.cs) - [IUIInvoker.cs](file://ClassicAssist.Shared/IUIInvoker.cs) - [PlatformDetector.cs](file://ClassicAssist.Shared/Engine/PlatformDetector.cs) - [App.axaml.cs](file://ClassicAssist.Avalonia/App.axaml.cs) - [Program.cs](file://ClassicAssist.Avalonia/Program.cs) - [MainWindow.axaml.cs](file://ClassicAssist.Avalonia/Views/MainWindow.axaml.cs) - [WPFDispatcher.cs](file://ClassicAssist/WPFDispatcher.cs) - [WPFUIInvoker.cs](file://ClassicAssist/WPFUIInvoker.cs)Table of Contents
- Introduction
- Project Structure
- Core Components
- Architecture Overview
- Detailed Component Analysis
- Platform-Specific Behaviors
- Dispatcher System and Thread Marshaling
- Keyboard Handling Differences
- File System Access Patterns
- Native Integration Points
- Testing Strategies
- Debugging Techniques
- Best Practices for Platform-Agnostic Code
- Conclusion
Introduction
This document provides comprehensive guidance for developing cross-platform user interfaces in ClassicAssist.Avalonia, focusing on platform-specific behaviors, rendering differences, and compatibility considerations across Windows, macOS, and Linux platforms. It covers the dispatcher system for thread marshaling, UI invoker patterns, platform abstraction layers, keyboard handling differences, file system access patterns, and native integration points.
The ClassicAssist.Avalonia project implements a sophisticated cross-platform architecture that abstracts platform-specific details while maintaining optimal performance and native feel across different operating systems. Understanding these patterns is crucial for developers working on UI components that need to function consistently across all supported platforms.
Project Structure
The ClassicAssist.Avalonia project follows a modular architecture that separates platform-specific implementations from shared logic:
graph TB
subgraph "Shared Layer"
Shared["ClassicAssist.Shared"]
IDispatcher["IDispatcher Interface"]
IUIInvoker["IUIInvoker Interface"]
PlatformDetector["PlatformDetector"]
end
subgraph "Avalonia Implementation"
Avalonia["ClassicAssist.Avalonia"]
AvaloniaDispatcher["AvaloniaDispatcher"]
AvaloniaUIInvoker["AvaloniaUIInvoker"]
App["App.axaml.cs"]
Program["Program.cs"]
end
subgraph "WPF Legacy"
WPF["ClassicAssist"]
WPFDispatcher["WPFDispatcher"]
WPFUIInvoker["WPFUIInvoker"]
end
Shared --> Avalonia
Shared --> WPF
IDispatcher --> AvaloniaDispatcher
IUIInvoker --> AvaloniaUIInvoker
PlatformDetector --> Avalonia
App --> AvaloniaDispatcher
Program --> App
Diagram sources
Section sources
Core Components
The cross-platform UI architecture is built around several core components that provide abstraction over platform-specific implementations:
Dispatcher Abstraction
The IDispatcher interface defines a contract for thread marshaling operations, allowing UI updates to be safely executed on the appropriate thread regardless of the underlying platform.
UI Invoker Pattern
The IUIInvoker interface provides a consistent API for invoking UI operations across different platforms, abstracting away the complexities of platform-specific threading models.
Platform Detection
The PlatformDetector class provides utilities for detecting the current operating system and making platform-specific decisions at runtime.
Section sources
Architecture Overview
The ClassicAssist.Avalonia architecture implements a layered approach to cross-platform UI development:
sequenceDiagram
participant ViewModel as "ViewModel"
participant Dispatcher as "IDispatcher"
participant AvaloniaDisp as "AvaloniaDispatcher"
participant UIThread as "UI Thread"
ViewModel->>Dispatcher : InvokeOnUiThread(action)
Dispatcher->>AvaloniaDisp : Dispatch action
AvaloniaDisp->>UIThread : Marshal to UI thread
UIThread-->>AvaloniaDisp : Execute action
AvaloniaDisp-->>Dispatcher : Complete
Dispatcher-->>ViewModel : Action completed
Diagram sources
The architecture ensures that UI updates are always performed on the correct thread while providing a consistent API across different platforms. This design pattern prevents common threading issues and ensures smooth operation across Windows, macOS, and Linux environments.
Detailed Component Analysis
Dispatcher System Implementation
The dispatcher system provides thread-safe UI updates through a well-defined abstraction layer:
classDiagram
class IDispatcher {
<<interface>>
+InvokeOnUiThread(action) void
+BeginInvokeOnUiThread(action) void
+IsOnUIThread() bool
}
class AvaloniaDispatcher {
-_dispatcher Avalonia.Threading.Dispatcher
+InvokeOnUiThread(action) void
+BeginInvokeOnUiThread(action) void
+IsOnUIThread() bool
-EnsureDispatcherInitialized() void
}
class WPFDispatcher {
-_dispatcher System.Windows.Threading.Dispatcher
+InvokeOnUiThread(action) void
+BeginInvokeOnUiThread(action) void
+IsOnUIThread() bool
}
IDispatcher <|.. AvaloniaDispatcher
IDispatcher <|.. WPFDispatcher
AvaloniaDispatcher --> Avalonia.Threading.Dispatcher : "uses"
WPFDispatcher --> System.Windows.Threading.Dispatcher : "uses"
Diagram sources
The dispatcher implementation handles platform-specific threading concerns while providing a unified interface for UI thread marshaling. This approach allows the same business logic to work seamlessly across different UI frameworks.
UI Invoker Pattern
The UI invoker pattern provides a higher-level abstraction for common UI operations:
flowchart TD
Start([UI Operation Request]) --> CheckThread{"On UI Thread?"}
CheckThread --> |Yes| ExecuteDirect["Execute Directly"]
CheckThread --> |No| UseDispatcher["Use Dispatcher"]
UseDispatcher --> MarshalToUI["Marshal to UI Thread"]
MarshalToUI --> ExecuteOnUI["Execute on UI Thread"]
ExecuteDirect --> Complete([Operation Complete])
ExecuteOnUI --> Complete
Diagram sources
This pattern ensures that UI operations are always executed on the appropriate thread, preventing race conditions and ensuring thread safety across all platforms.
Section sources
Platform-Specific Behaviors
Window Management Differences
Each platform has distinct window management characteristics that affect how windows are created, displayed, and managed:
Windows Platform
- Uses Win32 API for advanced window features
- Supports hardware acceleration by default
- Handles DPI scaling automatically
- Provides native context menus and tooltips
macOS Platform
- Integrates with Cocoa framework
- Supports native menu bars and dock integration
- Implements proper window lifecycle management
- Handles Retina display scaling
Linux Platform
- Depends on X11 or Wayland backend
- May require additional configuration for hardware acceleration
- Varies significantly between distributions
- Limited support for some advanced features
Rendering Differences
Avalonia provides consistent rendering across platforms, but certain visual elements may render differently:
graph LR
subgraph "Rendering Pipeline"
XAML["XAML/UI Definition"]
AvaloniaRenderer["Avalonia Renderer"]
PlatformBackend["Platform Backend"]
end
subgraph "Windows"
WinRT["WinRT/DirectX"]
WGL["OpenGL"]
end
subgraph "macOS"
Metal["Metal"]
OpenGL["OpenGL"]
end
subgraph "Linux"
X11["X11"]
Wayland["Wayland"]
Vulkan["Vulkan"]
end
AvaloniaRenderer --> PlatformBackend
PlatformBackend --> WinRT
PlatformBackend --> Metal
PlatformBackend --> X11
Diagram sources
Font and Text Rendering
Font rendering varies across platforms due to different text measurement engines:
- Windows: Uses ClearType for enhanced text clarity
- macOS: Leverages Core Text for native font rendering
- Linux: Depends on FreeType and system font configuration
Section sources
Dispatcher System and Thread Marshaling
Thread Safety Considerations
Cross-platform applications must handle threading carefully, as each platform has different threading models and constraints:
stateDiagram-v2
[*] --> BackgroundThread
BackgroundThread --> UIUpdateRequired : "Need to update UI"
UIUpdateRequired --> DispatcherQueue : "Queue UI operation"
DispatcherQueue --> UIThread : "Dispatch to UI thread"
UIThread --> UIUpdate : "Execute UI update"
UIUpdate --> [*]
BackgroundThread --> ContinueProcessing : "Continue background work"
ContinueProcessing --> [*]
Diagram sources
Dispatcher Implementation Details
The Avalonia dispatcher implementation leverages Avalonia's built-in threading infrastructure:
- Automatic Thread Detection: Detects whether code is running on the UI thread
- Synchronous and Asynchronous Operations: Supports both immediate and deferred UI updates
- Exception Handling: Properly propagates exceptions from UI thread operations
- Performance Optimization: Minimizes overhead for frequent UI updates
Best Practices for Thread Marshaling
- Always use the dispatcher for UI updates from background threads
- Avoid blocking the UI thread with long-running operations
- Handle exceptions properly in dispatched operations
- Consider performance implications of frequent UI updates
- Test thoroughly on all target platforms
Section sources
Keyboard Handling Differences
Key Event Processing
Keyboard input handling varies significantly across platforms:
Windows Platform
- Uses Win32 key codes and virtual key codes
- Supports advanced input features like raw input
- Handles modifier keys consistently
- Provides detailed key state information
macOS Platform
- Uses Carbon/Cocoa key event system
- Different modifier key behavior (Command vs Control)
- International keyboard layout support
- Special handling for function keys
Linux Platform
- Depends on X11 or Wayland input system
- Variable support for special keys
- Distribution-dependent behavior
- May require additional configuration for full functionality
Platform-Specific Key Mappings
flowchart TD
KeyPress["Key Press Event"] --> PlatformCheck{"Platform Detection"}
PlatformCheck --> |Windows| WinKeys["Windows Key Codes"]
PlatformCheck --> |macOS| MacKeys["macOS Key Codes"]
PlatformCheck --> |Linux| LinKeys["Linux Key Codes"]
WinKeys --> Normalize["Normalize to Common Format"]
MacKeys --> Normalize
LinKeys --> Normalize
Normalize --> ApplicationLogic["Application Logic"]
ApplicationLogic --> Response["UI Response"]
Diagram sources
Best Practices for Keyboard Input
- Abstract key handling behind platform-independent interfaces
- Test keyboard shortcuts on all target platforms
- Handle international keyboards appropriately
- Provide fallback mechanisms for unsupported keys
- Document platform-specific behaviors clearly
File System Access Patterns
Cross-Platform File Operations
File system access requires careful consideration of platform differences:
graph TB
subgraph "Application Layer"
FileSystemAPI["FileSystem API"]
PathUtilities["Path Utilities"]
end
subgraph "Platform Abstraction"
PathSeparator["Path Separator Handler"]
PermissionHandler["Permission Handler"]
EncodingHandler["Encoding Handler"]
end
subgraph "Platform Implementations"
WinFS["Windows File System"]
MacFS["macOS File System"]
LinuxFS["Linux File System"]
end
FileSystemAPI --> PathUtilities
PathUtilities --> PathSeparator
PathUtilities --> PermissionHandler
PathUtilities --> EncodingHandler
PathSeparator --> WinFS
PathSeparator --> MacFS
PathSeparator --> LinuxFS
PermissionHandler --> WinFS
PermissionHandler --> MacFS
PermissionHandler --> LinuxFS
EncodingHandler --> WinFS
EncodingHandler --> MacFS
EncodingHandler --> LinuxFS
Diagram sources
Path Handling Differences
- Windows: Uses backslashes (
\) as path separators - macOS/Linux: Uses forward slashes (
/) as path separators - Case Sensitivity: Linux/macOS are case-sensitive; Windows is not
- Drive Letters: Only Windows uses drive letters (C:, D:)
File Permissions and Security
Each platform has different permission models:
- Windows: ACL-based permissions with user/group model
- macOS: Unix-style permissions with extended attributes
- Linux: Traditional Unix permissions with SELinux/AppArmor support
Section sources
Native Integration Points
Platform-Specific APIs
The application integrates with platform-specific APIs for enhanced functionality:
Windows Integration
- Registry access for settings storage
- Windows Forms interop for legacy components
- DirectX integration for graphics acceleration
- Windows notification system integration
macOS Integration
- Cocoa framework integration
- NSUserDefaults for preferences
- Spotlight indexing support
- Notification center integration
Linux Integration
- Desktop environment integration (GNOME, KDE)
- System tray integration
- MIME type handling
- File association management
Native Method Calls
sequenceDiagram
participant App as "Application"
participant NativeBridge as "Native Bridge"
participant OS as "Operating System"
App->>NativeBridge : Call Native Function
NativeBridge->>OS : Platform-Specific API Call
OS-->>NativeBridge : Result Data
NativeBridge-->>App : Processed Result
Note over App,OS : Platform abstraction layer handles<br/>different calling conventions and data types
Diagram sources
Memory Management Considerations
Different platforms have varying memory management requirements:
- Windows: COM objects require proper cleanup
- macOS: ARC-managed objects with manual bridging
- Linux: Manual memory management for native libraries
Section sources
Testing Strategies
Cross-Platform Testing Approach
Testing cross-platform UI components requires a multi-faceted approach:
flowchart TD
UnitTests["Unit Tests"] --> MockPlatform["Mock Platform Services"]
UnitTests --> BusinessLogic["Business Logic Validation"]
IntegrationTests["Integration Tests"] --> TestEnvironment["Test Environment Setup"]
IntegrationTests --> PlatformAbstractions["Platform Abstractions"]
UIAutomation["UI Automation"] --> HeadlessBrowser["Headless Browser Testing"]
UIAutomation --> ScreenCapture["Screen Capture Analysis"]
ManualTesting["Manual Testing"] --> AllPlatforms["All Target Platforms"]
ManualTesting --> UserScenarios["Real User Scenarios"]
AllPlatforms --> Windows["Windows Testing"]
AllPlatforms --> macOS["macOS Testing"]
AllPlatforms --> Linux["Linux Testing"]
Diagram sources
Platform-Specific Test Cases
Windows-Specific Tests
- Registry access and modification
- Windows Forms interoperability
- Hardware acceleration verification
- DPI scaling validation
macOS-Specific Tests
- Cocoa framework integration
- Menu bar functionality
- Dock integration
- Retina display rendering
Linux-Specific Tests
- Desktop environment compatibility
- File manager integration
- System theme adherence
- Package manager integration
Continuous Integration Setup
graph LR
subgraph "CI Pipeline"
Build["Build Stage"]
UnitTest["Unit Tests"]
IntegrationTest["Integration Tests"]
PlatformTest["Platform-Specific Tests"]
Deployment["Deployment"]
end
subgraph "Platforms"
Win["Windows VM"]
Mac["macOS VM"]
Lin["Linux VM"]
end
Build --> UnitTest
UnitTest --> IntegrationTest
IntegrationTest --> PlatformTest
PlatformTest --> Win
PlatformTest --> Mac
PlatformTest --> Lin
Win --> Deployment
Mac --> Deployment
Lin --> Deployment
Diagram sources
Debugging Techniques
Platform-Specific Debugging Tools
Windows Debugging
- Visual Studio debugger with .NET debugging
- WinDbg for low-level analysis
- Performance Profiler for resource usage
- Network traffic monitoring
macOS Debugging
- Xcode debugger with LLDB
- Instruments for performance analysis
- Console.app for system logs
- Activity Monitor for resource usage
Linux Debugging
- GDB for source-level debugging
- Valgrind for memory leak detection
- strace for system call tracing
- dmesg for kernel messages
Common Cross-Platform Issues
flowchart TD
IssueDetected["Issue Detected"] --> PlatformIdentify{"Identify Platform"}
PlatformIdentify --> WinIssues["Windows-Specific Issues"]
PlatformIdentify --> MacIssues["macOS-Specific Issues"]
PlatformIdentify --> LinIssues["Linux-Specific Issues"]
WinIssues --> WinTools["Windows Debug Tools"]
MacIssues --> MacTools["macOS Debug Tools"]
LinIssues --> LinTools["Linux Debug Tools"]
WinTools --> Resolution["Resolution Strategy"]
MacTools --> Resolution
LinTools --> Resolution
Resolution --> VerifyFix["Verify Fix on All Platforms"]
VerifyFix --> DeployFix["Deploy Fix"]
Diagram sources
Logging and Diagnostics
Implement comprehensive logging that works across all platforms:
- Structured Logging: Use JSON-formatted logs for easy parsing
- Platform Context: Include platform information in log entries
- Performance Metrics: Track performance indicators per platform
- Error Aggregation: Centralize error reporting across platforms
Section sources
Best Practices for Platform-Agnostic Code
Design Principles
- Interface-Based Design: Define clear interfaces for platform-specific functionality
- Dependency Injection: Use DI containers to inject platform-specific implementations
- Configuration-Driven Behavior: Make platform-specific behavior configurable
- Graceful Degradation: Provide fallbacks when platform features are unavailable
Code Organization
graph TB
subgraph "Shared Code"
Interfaces["Interfaces & Contracts"]
Models["Data Models"]
Algorithms["Platform-Independent Algorithms"]
end
subgraph "Platform Implementations"
WinImpl["Windows Implementation"]
MacImpl["macOS Implementation"]
LinImpl["Linux Implementation"]
end
subgraph "UI Layer"
Views["Platform-Agnostic Views"]
ViewModels["Shared ViewModels"]
end
Interfaces --> WinImpl
Interfaces --> MacImpl
Interfaces --> LinImpl
Models --> Views
Models --> ViewModels
Algorithms --> ViewModels
Algorithms --> Views
Diagram sources
Conditional Compilation vs Runtime Detection
Choose the appropriate approach based on complexity:
- Conditional Compilation: For significant architectural differences
- Runtime Detection: For minor behavioral variations
- Feature Flags: For optional platform-specific features
Error Handling Strategies
Implement robust error handling that accounts for platform differences:
- Platform-Specific Error Messages: Provide meaningful error messages for each platform
- Fallback Mechanisms: Gracefully handle missing platform features
- Diagnostic Information: Collect platform-specific diagnostic data
- User-Friendly Recovery: Guide users through platform-specific recovery steps
Conclusion
Developing cross-platform UI applications with ClassicAssist.Avalonia requires careful consideration of platform-specific behaviors, threading models, and system integrations. The architecture presented in this document provides a solid foundation for building maintainable and performant cross-platform applications.
Key takeaways include:
- Abstraction Layers: Use well-defined interfaces to abstract platform-specific details
- Thread Safety: Always use the dispatcher system for UI updates from background threads
- Platform Detection: Implement robust platform detection for conditional behavior
- Comprehensive Testing: Test thoroughly on all target platforms
- Graceful Degradation: Handle missing platform features gracefully
- Consistent User Experience: Maintain consistent behavior across platforms while respecting platform conventions
By following these guidelines and leveraging the provided abstractions, developers can create high-quality cross-platform applications that provide excellent user experiences on Windows, macOS, and Linux platforms.