forked from jess/Acord
1
0
Fork 0
Acord/viewport/src/browser/handle.rs

323 lines
9.7 KiB
Rust

use std::path::PathBuf;
use iced_graphics::{Shell, Viewport};
use iced_runtime::user_interface::{self, UserInterface};
use iced_wgpu::core::renderer::Style;
use iced_wgpu::core::{
clipboard, mouse, window, Color, Event, Font, Pixels, Point, Size, Theme,
};
use iced_wgpu::Engine;
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
#[cfg(target_os = "macos")]
use raw_window_handle::{AppKitDisplayHandle, AppKitWindowHandle};
#[cfg(target_os = "windows")]
use raw_window_handle::{Win32WindowHandle, WindowsDisplayHandle};
#[cfg(any(target_os = "macos", target_os = "windows"))]
use std::ptr::NonNull;
#[cfg(any(target_os = "macos", target_os = "windows"))]
use std::os::raw::c_void;
use crate::palette;
use super::state::{BrowserMessage, BrowserState};
use super::ui;
/// Owns the browser window's wgpu surface, iced renderer, and BrowserState.
pub struct BrowserHandle {
pub surface: wgpu::Surface<'static>,
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub format: wgpu::TextureFormat,
pub width: u32,
pub height: u32,
pub scale: f32,
pub renderer: iced_wgpu::Renderer,
pub viewport: Viewport,
pub cache: user_interface::Cache,
pub state: BrowserState,
pub events: Vec<Event>,
pub cursor: mouse::Cursor,
pub needs_redraw: bool,
}
/// The browser doesn't read or write the system clipboard.
struct NoopClipboard;
impl clipboard::Clipboard for NoopClipboard {
fn read(&self, _kind: clipboard::Kind) -> Option<String> { None }
fn write(&mut self, _kind: clipboard::Kind, _contents: String) {}
}
/// Caller must keep the underlying winit Window alive for the surface's lifetime.
pub fn create(
raw_display: RawDisplayHandle,
raw_window: RawWindowHandle,
width: f32,
height: f32,
scale: f32,
notes_dir: PathBuf,
) -> Option<BrowserHandle> {
#[cfg(target_os = "macos")]
let backends = wgpu::Backends::METAL;
#[cfg(target_os = "windows")]
let backends = wgpu::Backends::DX12;
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
let backends = wgpu::Backends::VULKAN;
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends,
..Default::default()
});
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(13.0));
let viewport = Viewport::with_physical_size(Size::new(phys_w.max(1), phys_h.max(1)), scale);
Some(BrowserHandle {
surface,
device,
queue,
format,
width: phys_w,
height: phys_h,
scale,
renderer,
viewport,
cache: user_interface::Cache::new(),
state: BrowserState::new(notes_dir),
events: Vec::new(),
cursor: mouse::Cursor::Available(Point::new(0.0, 0.0)),
needs_redraw: true,
})
}
/// One frame: drains pending events into messages, applies them, then redraws.
pub fn render(handle: &mut BrowserHandle) {
let pending = !handle.events.is_empty();
if !handle.needs_redraw && !pending {
return;
}
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(iced_wgpu::core::time::Instant::now())));
// pre-scans events so the modifier and cursor state are visible to message handlers fired this frame.
for ev in &handle.events {
match ev {
Event::Keyboard(iced_wgpu::core::keyboard::Event::ModifiersChanged(m)) => {
handle.state.current_modifiers = *m;
}
Event::Mouse(iced_wgpu::core::mouse::Event::CursorMoved { position }) => {
handle.state.cursor_pos = *position;
}
_ => {}
}
}
let cache = std::mem::take(&mut handle.cache);
let mut ui = UserInterface::build(
ui::view(&handle.state),
Size::new(logical_size.width, logical_size.height),
cache,
&mut handle.renderer,
);
let mut clipboard = NoopClipboard;
let mut messages: Vec<BrowserMessage> = Vec::new();
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);
}
// Second UI build draws against post-message state.
let mut ui = UserInterface::build(
ui::view(&handle.state),
Size::new(logical_size.width, logical_size.height),
cache,
&mut handle.renderer,
);
let theme = Theme::Dark;
let style = Style { text_color: Color::WHITE };
ui.draw(&mut handle.renderer, &theme, &style, handle.cursor);
handle.cache = ui.into_cache();
handle
.renderer
.present(Some(palette::current().base), handle.format, &view, &handle.viewport);
frame.present();
handle.needs_redraw = false;
}
pub fn resize(handle: &mut BrowserHandle, 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,
},
);
handle.needs_redraw = true;
}
pub fn push_mouse_move(handle: &mut BrowserHandle, x: f32, y: f32) {
let position = Point::new(x, y);
handle.cursor = mouse::Cursor::Available(position);
handle.events.push(Event::Mouse(mouse::Event::CursorMoved { position }));
handle.needs_redraw = true;
}
pub fn push_mouse_button(handle: &mut BrowserHandle, button: u8, pressed: bool) {
let btn = match button {
0 => mouse::Button::Left,
1 => mouse::Button::Right,
2 => mouse::Button::Middle,
n => mouse::Button::Other(n as u16),
};
let ev = if pressed {
mouse::Event::ButtonPressed(btn)
} else {
mouse::Event::ButtonReleased(btn)
};
handle.events.push(Event::Mouse(ev));
handle.needs_redraw = true;
}
pub fn push_scroll(handle: &mut BrowserHandle, delta_x: f32, delta_y: f32) {
handle.events.push(Event::Mouse(mouse::Event::WheelScrolled {
delta: mouse::ScrollDelta::Pixels { x: delta_x, y: delta_y },
}));
handle.needs_redraw = true;
}
pub fn push_event(handle: &mut BrowserHandle, event: Event) {
handle.events.push(event);
handle.needs_redraw = true;
}
pub fn take_pending_open(handle: &mut BrowserHandle) -> Option<PathBuf> {
handle.state.take_pending_open()
}
pub fn refresh(handle: &mut BrowserHandle) {
handle.state.refresh();
handle.needs_redraw = true;
}
#[cfg(any(target_os = "macos", target_os = "windows"))]
pub fn create_from_native(
native_handle: *mut c_void,
width: f32,
height: f32,
scale: f32,
notes_dir: PathBuf,
) -> Option<BrowserHandle> {
let ptr = NonNull::new(native_handle)?;
#[cfg(target_os = "macos")]
let (raw_window, raw_display) = (
RawWindowHandle::AppKit(AppKitWindowHandle::new(ptr)),
RawDisplayHandle::AppKit(AppKitDisplayHandle::new()),
);
#[cfg(target_os = "windows")]
let (raw_window, raw_display) = {
let wh = Win32WindowHandle::new(std::num::NonZero::new(ptr.as_ptr() as isize).unwrap());
(
RawWindowHandle::Win32(wh),
RawDisplayHandle::Windows(WindowsDisplayHandle::new()),
)
};
create(raw_display, raw_window, width, height, scale, notes_dir)
}
pub fn push_key_native(
handle: &mut BrowserHandle,
keycode: u32,
modifier_flags: u32,
pressed: bool,
text: Option<&str>,
) {
for ev in crate::bridge::build_key_events(keycode, modifier_flags, pressed, text) {
handle.events.push(ev);
}
handle.needs_redraw = true;
}