はじめに
Rust で native な Window を作成する3種です。
Windows
microsoft が提供している windows クレートを利用することで、伝統的な Windows API の作法でプログラミングできます。
[dependencies.windows] version = "0.45.0" features = [ "Win32_Foundation", "Win32_Graphics_Gdi", "Win32_System_LibraryLoader", "Win32_UI_WindowsAndMessaging", ]
昔ながらの Win32 で CreateWindowEx するのと同様なプログラミングモデルとなります。
use windows::{ core::*, Win32::Foundation::*, Win32::Graphics::Gdi::{PAINTSTRUCT, BeginPaint, EndPaint, TextOutA}, Win32::System::LibraryLoader::GetModuleHandleA, Win32::UI::WindowsAndMessaging::*, }; fn main() -> Result<()> { unsafe { let instance = GetModuleHandleA(None)?; let window_class = s!("window"); let wc = WNDCLASSA { hCursor: LoadCursorW(None, IDC_ARROW)?, hInstance: instance, lpszClassName: window_class, style: CS_HREDRAW | CS_VREDRAW, lpfnWndProc: Some(wndproc), ..Default::default() }; let atom = RegisterClassA(&wc); CreateWindowExA( WINDOW_EX_STYLE::default(), window_class, s!("hello world window"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, None, None, instance, None, ); let mut message = MSG::default(); while GetMessageA(&mut message, HWND(0), 0, 0).into() { DispatchMessageA(&message); } Ok(()) } } extern "system" fn wndproc(window: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { unsafe { match message { WM_PAINT => { let mut ps = PAINTSTRUCT::default(); let hdc = BeginPaint(window, &mut ps); TextOutA(hdc, 10, 10, "Hello World".as_bytes()); EndPaint(window, &mut ps); LRESULT(0) } WM_DESTROY => { PostQuitMessage(0); LRESULT(0) } _ => DefWindowProcA(window, message, wparam, lparam), } } }
以下のような Window が表示できます。
macOS(cocoa)
Firefox のブラウザエンジンである Servo のサブプロジェクトで管理されている cocoa クレートが利用できます。
[dependencies] cocoa = "0.24.1"
NSWindow で Window 作成の流れになります。
use cocoa::base::{ selector, nil, NO, id, }; use cocoa::foundation::{ NSAutoreleasePool, NSRect, NSPoint, NSSize, NSString, }; use cocoa::appkit::{ NSApp, NSWindow, NSApplication, NSWindowStyleMask, NSMenu, NSMenuItem, NSApplicationActivationPolicyRegular, NSBackingStoreBuffered, }; fn main() { unsafe { let _pool = NSAutoreleasePool::new(nil); let app = NSApp(); app.setActivationPolicy_(NSApplicationActivationPolicyRegular); add_menu(&app); let window = NSWindow::alloc(nil) .initWithContentRect_styleMask_backing_defer_( NSRect::new(NSPoint::new(0., 0.), NSSize::new(500., 300.)), NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSClosableWindowMask | NSWindowStyleMask::NSResizableWindowMask | NSWindowStyleMask::NSMiniaturizableWindowMask | NSWindowStyleMask::NSUnifiedTitleAndToolbarWindowMask, NSBackingStoreBuffered, NO ).autorelease(); window.cascadeTopLeftFromPoint_(NSPoint::new(20., 20.)); window.center(); let title = NSString::alloc(nil).init_str("Hello World!"); window.setTitle_(title); window.makeKeyAndOrderFront_(nil); app.run(); } } unsafe fn add_menu(app: &id) { // create Menu Bar let menubar = NSMenu::new(nil).autorelease(); let app_menu_item = NSMenuItem::new(nil).autorelease(); menubar.addItem_(app_menu_item); app.setMainMenu_(menubar); // create Application menu let app_menu = NSMenu::new(nil).autorelease(); let quit_title = NSString::alloc(nil).init_str("Quit"); let quit_action = selector("terminate:"); let quit_key = NSString::alloc(nil).init_str("q"); let quit_item = NSMenuItem::alloc(nil) .initWithTitle_action_keyEquivalent_(quit_title, quit_action, quit_key) .autorelease(); app_menu.addItem_(quit_item); app_menu_item.setSubmenu_(app_menu); }
以下のような Window が表示できます。
winit
winit クレートを使えば、前述のようなプラットフォームの違いを意識せずにプログラミングできます。
winit クレートを定義すれば、
[dependencies] winit = "0.2"
以下のようにしてウインドウが生成できます。
use winit::{ event::{ Event, WindowEvent }, event_loop::{ ControlFlow, EventLoop }, window::WindowBuilder, }; fn main() { let event_loop = EventLoop::new(); let window = WindowBuilder::new() .with_inner_size(winit::dpi::LogicalSize::new(500, 300)) .with_title("winit") .build(&event_loop).unwrap(); event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; match event { Event::WindowEvent { event: WindowEvent::CloseRequested, window_id, } if window_id == window.id() => *control_flow = ControlFlow::Exit, _ => (), } }); }
なお、winit はウインドウの生成とイベントループの管理しか提供しないので、ウインドウ内への描画は raw_window_handle
や raw_display_handle
などのハンドルを介したり、他のクレートと連携して実現する必要があります。
以下のような Window が表示できます。
まとめ
Rust で native な Window を作成する方法を紹介しました。
Rust エコシステムでは、未だ GUIライブラリの土壌は発展途上です。
WebAssembly を土台に、WebGPU の方向から発展するのが主流になっていくものと思われます。