use std::ffi::c_void; use std::ptr::NonNull; use iced_graphics::{Shell, Viewport}; use iced_runtime::user_interface::{self, UserInterface}; use iced_wgpu::core::renderer::Style; use iced_wgpu::core::time::Instant; use iced_wgpu::core::{clipboard, keyboard, mouse, window, Color, Event, Font, Pixels, Point, Size, Theme}; use iced_wgpu::Engine; use raw_window_handle::{ AppKitDisplayHandle, AppKitWindowHandle, RawDisplayHandle, RawWindowHandle, }; use crate::editor::{EditorState, Message}; use crate::ViewportHandle; struct MacClipboard; impl clipboard::Clipboard for MacClipboard { fn read(&self, _kind: clipboard::Kind) -> Option { std::process::Command::new("pbpaste") .output() .ok() .and_then(|o| String::from_utf8(o.stdout).ok()) } fn write(&mut self, _kind: clipboard::Kind, contents: String) { use std::io::Write; if let Ok(mut child) = std::process::Command::new("pbcopy") .stdin(std::process::Stdio::piped()) .spawn() { if let Some(stdin) = child.stdin.as_mut() { let _ = stdin.write_all(contents.as_bytes()); } let _ = child.wait(); } } } pub fn create( nsview: *mut c_void, width: f32, height: f32, scale: f32, ) -> Option { let ptr = NonNull::new(nsview)?; let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { backends: wgpu::Backends::METAL, ..Default::default() }); let raw_window = RawWindowHandle::AppKit(AppKitWindowHandle::new(ptr)); let raw_display = RawDisplayHandle::AppKit(AppKitDisplayHandle::new()); let target = wgpu::SurfaceTargetUnsafe::RawHandle { raw_display_handle: raw_display, raw_window_handle: raw_window, }; let surface = unsafe { instance.create_surface_unsafe(target).ok()? }; let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::HighPerformance, compatible_surface: Some(&surface), force_fallback_adapter: false, })) .ok()?; let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())).ok()?; let phys_w = (width * scale) as u32; let phys_h = (height * scale) as u32; let caps = surface.get_capabilities(&adapter); let format = caps.formats.first().copied()?; surface.configure( &device, &wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format, width: phys_w.max(1), height: phys_h.max(1), present_mode: wgpu::PresentMode::AutoVsync, alpha_mode: caps .alpha_modes .first() .copied() .unwrap_or(wgpu::CompositeAlphaMode::Auto), view_formats: vec![], desired_maximum_frame_latency: 2, }, ); let engine = Engine::new( &adapter, device.clone(), queue.clone(), format, None, Shell::headless(), ); let renderer = iced_wgpu::Renderer::new(engine, Font::DEFAULT, Pixels(16.0)); let viewport = Viewport::with_physical_size(Size::new(phys_w.max(1), phys_h.max(1)), scale); let focus_point = Point::new(width / 2.0, height / 2.0); let initial_events = vec![ Event::Mouse(mouse::Event::CursorMoved { position: focus_point }), Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)), Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)), ]; Some(ViewportHandle { surface, device, queue, format, width: phys_w, height: phys_h, scale, renderer, viewport, cache: user_interface::Cache::new(), state: EditorState::new(), events: initial_events, cursor: mouse::Cursor::Available(focus_point), }) } pub fn render(handle: &mut ViewportHandle) { let frame = match handle.surface.get_current_texture() { Ok(f) => f, Err(_) => return, }; let view = frame.texture.create_view(&Default::default()); let logical_size = handle.viewport.logical_size(); handle .events .push(Event::Window(window::Event::RedrawRequested(Instant::now()))); let cache = std::mem::take(&mut handle.cache); let mut ui = UserInterface::build( handle.state.view(), Size::new(logical_size.width, logical_size.height), cache, &mut handle.renderer, ); let mut clipboard = MacClipboard; let mut messages: Vec = Vec::new(); for event in &handle.events { if let Event::Keyboard(keyboard::Event::KeyPressed { key: keyboard::Key::Character(c), modifiers, .. }) = event { if modifiers.logo() { match c.as_str() { "p" => messages.push(Message::TogglePreview), "t" => messages.push(Message::InsertTable), _ => {} } } } } let _ = ui.update( &handle.events, handle.cursor, &mut handle.renderer, &mut clipboard, &mut messages, ); handle.events.clear(); let cache = ui.into_cache(); for msg in messages.drain(..) { handle.state.update(msg); } let theme = Theme::Dark; let style = Style { text_color: Color::WHITE, }; let mut ui = UserInterface::build( handle.state.view(), Size::new(logical_size.width, logical_size.height), cache, &mut handle.renderer, ); ui.draw(&mut handle.renderer, &theme, &style, handle.cursor); handle.cache = ui.into_cache(); let bg = Color::from_rgb(0.08, 0.08, 0.10); handle .renderer .present(Some(bg), handle.format, &view, &handle.viewport); frame.present(); } pub fn resize(handle: &mut ViewportHandle, width: f32, height: f32, scale: f32) { let phys_w = (width * scale) as u32; let phys_h = (height * scale) as u32; if phys_w == 0 || phys_h == 0 { return; } handle.width = phys_w; handle.height = phys_h; handle.scale = scale; handle.viewport = Viewport::with_physical_size(Size::new(phys_w, phys_h), scale); handle.surface.configure( &handle.device, &wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: handle.format, width: phys_w, height: phys_h, present_mode: wgpu::PresentMode::AutoVsync, alpha_mode: wgpu::CompositeAlphaMode::Auto, view_formats: vec![], desired_maximum_frame_latency: 2, }, ); }