Changed backend from glfw to winit

This commit is contained in:
2026-05-22 15:00:08 +02:00
parent 8ce3f40092
commit 26f535d053
5 changed files with 217 additions and 106 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
glfw = "0.60.0" winit = "0.30"
wgpu = "27" wgpu = "27"
pollster = "0.4.0" pollster = "0.4.0"
bytemuck = { version = "1.24.0", features = ["derive"] } bytemuck = { version = "1.24.0", features = ["derive"] }
+3 -3
View File
@@ -1,8 +1,8 @@
# pbio: simple pixel buffer and I/O system # pbio: simple pixel buffer and I/O system
Pixelbuffer-basiertes Fenster mit Palette-Lookup-Renderer (GLFW + wgpu) und Maus/Tastatureingaben. Pixelbuffer-basiertes Fenster mit Palette-Lookup-Renderer (winit + wgpu) und Maus/Tastatureingaben.
pbio nimmt einen Bytearray, interpretiert diesen als 8-Bit-Pixelbuffer und rendert ihn auf ein Fenster. Optional mit Letterbox für ein festes Seitenverhältnis pbio nimmt einen Bytearray, interpretiert diesen als 8-Bit-Pixelbuffer und rendert ihn auf ein Fenster. Optional mit Letterbox für ein festes Seitenverhältnis.
## Integration ## Integration
@@ -13,7 +13,7 @@ pbio ist nicht auf crates.io veröffentlicht. Das Crate kann direkt aus dem Git-
pbio = { git = "https://codeberg.org/irrlicht/rust-pbio.git" } pbio = { git = "https://codeberg.org/irrlicht/rust-pbio.git" }
``` ```
pbio benötigt eine funktionierende GLFW-Toolchain (CMake + C-Compiler) zum Bauen sowie aktuelle Grafiktreiber mit Vulkan-, Metal- oder DX12-Unterstützung für wgpu. pbio braucht keine System-Libraries zum Bauen — winit lädt X11/Wayland zur Laufzeit per dlopen. Zur Laufzeit reichen aktuelle Grafiktreiber mit Vulkan-, Metal- oder DX12-Unterstützung für wgpu.
## Verwendung ## Verwendung
+7
View File
@@ -0,0 +1,7 @@
with open("rgb332.gpl", "w") as f:
f.write("GIMP Palette\nName: RGB332\nColumns: 16\n#\n")
for i in range(256):
r = ((i >> 5) * 255) // 7
g = (((i >> 2) & 0x7) * 255) // 7
b = ((i & 0x3) * 255) // 3
f.write(f"{r:3d} {g:3d} {b:3d} Index {i}\n")
BIN
View File
Binary file not shown.
+206 -102
View File
@@ -1,9 +1,19 @@
//! pbio — Pixelbuffer-basiertes Fenster mit Palette-Lookup-Renderer. //! pbio — Pixelbuffer-basiertes Fenster mit Palette-Lookup-Renderer.
use std::collections::VecDeque; use std::collections::VecDeque;
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::application::ApplicationHandler;
use winit::dpi::LogicalSize;
use winit::event::{
DeviceEvent, DeviceId, ElementState, KeyEvent, MouseButton as WinitMouseButton, WindowEvent,
};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::keyboard::{KeyCode, PhysicalKey};
use winit::platform::pump_events::EventLoopExtPumpEvents;
use winit::window::{CursorGrabMode, Window, WindowAttributes, WindowId};
pub mod palette; pub mod palette;
@@ -78,7 +88,7 @@ struct Params {
// --- Platform -------------------------------------------------------------- // --- Platform --------------------------------------------------------------
pub struct Platform { pub struct Platform {
// GPU-Ressourcen vor Surface, Surface vor Window/Glfw (Drop-Reihenfolge). // GPU-Ressourcen vor Surface, Surface vor Window (Drop-Reihenfolge).
bind_group: wgpu::BindGroup, bind_group: wgpu::BindGroup,
pipeline: wgpu::RenderPipeline, pipeline: wgpu::RenderPipeline,
index_tex: wgpu::Texture, index_tex: wgpu::Texture,
@@ -93,22 +103,20 @@ pub struct Platform {
queue: wgpu::Queue, queue: wgpu::Queue,
instance: wgpu::Instance, instance: wgpu::Instance,
window: glfw::PWindow, window: Arc<Window>,
events: glfw::GlfwReceiver<(f64, glfw::WindowEvent)>, event_loop: EventLoop<()>,
glfw: glfw::Glfw,
framebuffer: Vec<u8>, framebuffer: Vec<u8>,
framebuffer_size: (u32, u32), framebuffer_size: (u32, u32),
aspect_ratio: f32, // 0.0 = keine Letterbox aspect_ratio: f32, // 0.0 = keine Letterbox
content_rect: (u32, u32, u32, u32), content_rect: (u32, u32, u32, u32),
event_queue: VecDeque<Event>, event_queue: VecDeque<Event>,
event_scratch: Vec<glfw::WindowEvent>,
close_requested: bool, close_requested: bool,
mouse_visible: bool, mouse_visible: bool,
mouse_capture: bool, mouse_capture: bool,
// Letzte rohe (window-space) Cursorposition, um Deltas zu bilden. None nach // Letzte rohe (window-space) Cursorposition, um Deltas zu bilden. None nach
// Mode-Wechsel/erstem Frame, damit der GLFW-Recenter keinen Riesensprung erzeugt. // Mode-Wechsel/erstem Frame, damit ein Cursor-Sprung keinen Riesen-Delta erzeugt.
last_cursor_pos: Option<(f64, f64)>, last_cursor_pos: Option<(f64, f64)>,
mouse_delta: (f32, f32), mouse_delta: (f32, f32),
} }
@@ -119,29 +127,27 @@ impl Platform {
} }
async fn new_async(cfg: PlatformConfig) -> Self { async fn new_async(cfg: PlatformConfig) -> Self {
use glfw::fail_on_errors; let mut event_loop = EventLoop::builder().build().expect("event loop");
let mut glfw = glfw::init(fail_on_errors!()).unwrap(); let attrs = WindowAttributes::default()
glfw.window_hint(glfw::WindowHint::ClientApi(glfw::ClientApiHint::NoApi)); .with_title(cfg.title.clone())
.with_inner_size(LogicalSize::new(cfg.window_size.0, cfg.window_size.1));
let (mut window, events) = glfw // winit 0.30 erlaubt Fenster-Erstellung nur in ApplicationHandler::resumed.
.create_window(cfg.window_size.0, cfg.window_size.1, &cfg.title, glfw::WindowMode::Windowed) // Wir pumpen, bis das Bootstrap-Handler das Window angelegt hat.
.expect("failed to create GLFW window"); let mut bootstrap = Bootstrap { attrs, window: None };
while bootstrap.window.is_none() {
event_loop.pump_app_events(Some(Duration::from_millis(10)), &mut bootstrap);
}
let window = bootstrap.window.unwrap();
window.set_framebuffer_size_polling(true); let win_size = window.inner_size();
window.set_key_polling(true);
window.set_char_polling(true);
window.set_cursor_pos_polling(true);
window.set_mouse_button_polling(true);
window.set_close_polling(true);
let win_size = window.get_framebuffer_size();
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(), backends: wgpu::Backends::all(),
..Default::default() ..Default::default()
}); });
let surface = instance.create_surface(window.render_context()).expect("surface"); let surface = instance.create_surface(window.clone()).expect("surface");
let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions { let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
compatible_surface: Some(&surface), compatible_surface: Some(&surface),
@@ -156,15 +162,26 @@ impl Platform {
let caps = surface.get_capabilities(&adapter); let caps = surface.get_capabilities(&adapter);
let format = caps.formats.iter().copied().find(|f| f.is_srgb()).unwrap_or(caps.formats[0]); let format = caps.formats.iter().copied().find(|f| f.is_srgb()).unwrap_or(caps.formats[0]);
// Mailbox/Immediate sind nicht überall in caps.present_modes — Fifo ist
// der einzige garantierte Mode.
let present_mode = if cfg.vsync {
if caps.present_modes.contains(&wgpu::PresentMode::Mailbox) {
wgpu::PresentMode::Mailbox
} else {
wgpu::PresentMode::Fifo
}
} else if caps.present_modes.contains(&wgpu::PresentMode::Immediate) {
wgpu::PresentMode::Immediate
} else {
wgpu::PresentMode::Fifo
};
let surface_config = wgpu::SurfaceConfiguration { let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT, usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format, format,
width: win_size.0 as u32, width: win_size.width,
height: win_size.1 as u32, height: win_size.height,
// TODO: Mailbox ist nicht überall in caps.present_modes — bei present_mode,
// unsupported Treibern panict configure(). Fifo wäre der sichere
// Vsync-Fallback. Erstmal beobachten.
present_mode: if cfg.vsync { wgpu::PresentMode::Mailbox } else { wgpu::PresentMode::Immediate },
alpha_mode: caps.alpha_modes[0], alpha_mode: caps.alpha_modes[0],
view_formats: vec![], view_formats: vec![],
desired_maximum_frame_latency: 2, desired_maximum_frame_latency: 2,
@@ -220,12 +237,11 @@ impl Platform {
palette: cfg.palette, palette_dirty: false, palette: cfg.palette, palette_dirty: false,
params_buf, params, params_buf, params,
surface, config: surface_config, device, queue, instance, surface, config: surface_config, device, queue, instance,
window, events, glfw, window, event_loop,
framebuffer: vec![0u8; (src_w * src_h) as usize], framebuffer: vec![0u8; (src_w * src_h) as usize],
framebuffer_size: (src_w, src_h), framebuffer_size: (src_w, src_h),
aspect_ratio, content_rect, aspect_ratio, content_rect,
event_queue: VecDeque::new(), event_queue: VecDeque::new(),
event_scratch: Vec::new(),
close_requested: false, close_requested: false,
mouse_visible: cfg.mouse_visible, mouse_visible: cfg.mouse_visible,
mouse_capture: cfg.mouse_capture, mouse_capture: cfg.mouse_capture,
@@ -249,36 +265,46 @@ impl Platform {
} }
fn apply_cursor_mode(&mut self) { fn apply_cursor_mode(&mut self) {
// capture impliziert hidden — GLFW kennt keinen "locked + visible". if self.mouse_capture {
let mode = match (self.mouse_capture, self.mouse_visible) { // Wayland kann Locked, X11/Windows fallen auf Confined zurück.
(true, _) => glfw::CursorMode::Disabled, let r = self.window.set_cursor_grab(CursorGrabMode::Locked)
(false, true) => glfw::CursorMode::Normal, .or_else(|_| self.window.set_cursor_grab(CursorGrabMode::Confined));
(false, false) => glfw::CursorMode::Hidden, if let Err(e) = r {
}; eprintln!("pbio: cursor grab failed: {:?}", e);
self.window.set_cursor_mode(mode); }
self.window.set_cursor_visible(false);
} else {
let _ = self.window.set_cursor_grab(CursorGrabMode::None);
self.window.set_cursor_visible(self.mouse_visible);
}
} }
/// Akkumulierte Mausbewegung seit dem letzten Aufruf, in Framebuffer-Pixeln /// Akkumulierte Mausbewegung seit dem letzten Aufruf, in Framebuffer-Pixeln
/// (gleiche Skala wie `Event::MouseMove`). Bei `set_mouse_capture(true)` /// (gleiche Skala wie `Event::MouseMove`). Bei `set_mouse_capture(true)`
/// liefert GLFW unbegrenzte Deltas (FPS-Stil). /// kommen die Werte aus rohen DeviceEvents (unbegrenzt, FPS-Stil).
pub fn mouse_delta(&mut self) -> (f32, f32) { pub fn mouse_delta(&mut self) -> (f32, f32) {
std::mem::replace(&mut self.mouse_delta, (0.0, 0.0)) std::mem::replace(&mut self.mouse_delta, (0.0, 0.0))
} }
pub fn should_close(&self) -> bool { self.close_requested || self.window.should_close() } pub fn should_close(&self) -> bool { self.close_requested }
pub fn request_close(&mut self) { self.close_requested = true; self.window.set_should_close(true); } pub fn request_close(&mut self) { self.close_requested = true; }
pub fn poll_events(&mut self, timeout: Option<Duration>) { pub fn poll_events(&mut self, timeout: Option<Duration>) {
match timeout { // Felder destructuren, um disjoint &mut-Borrows in den Handler zu reichen.
Some(t) => self.glfw.wait_events_timeout(t.as_secs_f64()), let Self {
None => self.glfw.wait_events(), event_loop, event_queue, close_requested, last_cursor_pos, mouse_delta,
} surface, device, config, params, params_buf, queue, content_rect,
// flush_messages borrowt &events, handle_glfw_event will &mut self → in aspect_ratio, framebuffer_size, mouse_capture, ..
// wiederverwendeten Scratch-Buffer umparken statt pro Frame allokieren. } = self;
let mut scratch = std::mem::take(&mut self.event_scratch);
scratch.extend(glfw::flush_messages(&self.events).map(|(_, ev)| ev)); let mut handler = Handler {
for ev in scratch.drain(..) { self.handle_glfw_event(ev); } event_queue, close_requested, last_cursor_pos, mouse_delta,
self.event_scratch = scratch; surface, device, config, params, params_buf, queue, content_rect,
aspect_ratio: *aspect_ratio,
framebuffer_size: *framebuffer_size,
mouse_capture: *mouse_capture,
};
event_loop.pump_app_events(timeout, &mut handler);
} }
pub fn drain_events(&mut self) -> impl Iterator<Item = Event> + '_ { pub fn drain_events(&mut self) -> impl Iterator<Item = Event> + '_ {
@@ -328,7 +354,7 @@ impl Platform {
let drawable = match self.surface.get_current_texture() { let drawable = match self.surface.get_current_texture() {
Ok(d) => d, Ok(d) => d,
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => { Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
self.surface = self.instance.create_surface(self.window.render_context()).expect("recreate surface"); self.surface = self.instance.create_surface(self.window.clone()).expect("recreate surface");
self.surface.configure(&self.device, &self.config); self.surface.configure(&self.device, &self.config);
return; return;
} }
@@ -363,57 +389,126 @@ impl Platform {
self.queue.submit([encoder.finish()]); self.queue.submit([encoder.finish()]);
drawable.present(); drawable.present();
} }
}
fn handle_glfw_event(&mut self, ev: glfw::WindowEvent) { // --- Bootstrap-Handler (nur fürs initiale Window-Create) -------------------
use glfw::WindowEvent as W;
struct Bootstrap {
attrs: WindowAttributes,
window: Option<Arc<Window>>,
}
impl ApplicationHandler for Bootstrap {
fn resumed(&mut self, el: &ActiveEventLoop) {
if self.window.is_none() {
let w = el.create_window(self.attrs.clone()).expect("create_window");
self.window = Some(Arc::new(w));
}
}
fn window_event(&mut self, _: &ActiveEventLoop, _: WindowId, _: WindowEvent) {}
}
// --- Laufender Event-Handler (pro pump_events neu aufgebaut) ---------------
struct Handler<'a> {
event_queue: &'a mut VecDeque<Event>,
close_requested: &'a mut bool,
last_cursor_pos: &'a mut Option<(f64, f64)>,
mouse_delta: &'a mut (f32, f32),
surface: &'a wgpu::Surface<'static>,
device: &'a wgpu::Device,
config: &'a mut wgpu::SurfaceConfiguration,
params: &'a mut Params,
params_buf: &'a wgpu::Buffer,
queue: &'a wgpu::Queue,
content_rect: &'a mut (u32, u32, u32, u32),
aspect_ratio: f32,
framebuffer_size: (u32, u32),
mouse_capture: bool,
}
impl<'a> ApplicationHandler for Handler<'a> {
fn resumed(&mut self, _: &ActiveEventLoop) {}
fn window_event(&mut self, _: &ActiveEventLoop, _: WindowId, ev: WindowEvent) {
match ev { match ev {
W::FramebufferSize(w, h) => { WindowEvent::Resized(size) => {
let (w, h) = (size.width, size.height);
if w > 0 && h > 0 { if w > 0 && h > 0 {
self.config.width = w as u32; self.config.width = w;
self.config.height = h as u32; self.config.height = h;
self.surface.configure(&self.device, &self.config); self.surface.configure(self.device, self.config);
self.content_rect = compute_content_rect((self.config.width, self.config.height), self.aspect_ratio); *self.content_rect = compute_content_rect((w, h), self.aspect_ratio);
let (x, y, cw, ch) = self.content_rect; let (x, y, cw, ch) = *self.content_rect;
self.params.content_offset = [x as f32, y as f32]; self.params.content_offset = [x as f32, y as f32];
self.params.content_size = [cw as f32, ch as f32]; self.params.content_size = [cw as f32, ch as f32];
self.queue.write_buffer(&self.params_buf, 0, bytemuck::bytes_of(&self.params)); self.queue.write_buffer(self.params_buf, 0, bytemuck::bytes_of(self.params));
} }
self.event_queue.push_back(Event::Resize { width: w.max(0) as u32, height: h.max(0) as u32 }); self.event_queue.push_back(Event::Resize { width: w, height: h });
} }
W::Key(k, _, action, _) => if let Some(key) = from_glfw_key(k) { WindowEvent::CloseRequested => {
self.event_queue.push_back(Event::Key { *self.close_requested = true;
key, self.event_queue.push_back(Event::CloseRequested);
pressed: action != glfw::Action::Release,
repeat: action == glfw::Action::Repeat,
});
} }
W::Char(c) => self.event_queue.push_back(Event::Char(c)), WindowEvent::KeyboardInput { event: KeyEvent { physical_key, state, repeat, text, .. }, .. } => {
W::CursorPos(x, y) => { if let PhysicalKey::Code(code) = physical_key
let (cx, cy, cw, ch) = self.content_rect; && let Some(key) = from_winit_keycode(code)
{
self.event_queue.push_back(Event::Key {
key,
pressed: state == ElementState::Pressed,
repeat,
});
}
if state == ElementState::Pressed
&& let Some(t) = text
{
for c in t.chars() {
self.event_queue.push_back(Event::Char(c));
}
}
}
WindowEvent::CursorMoved { position, .. } => {
let (x, y) = (position.x, position.y);
let (cx, cy, cw, ch) = *self.content_rect;
let (fw, fh) = self.framebuffer_size; let (fw, fh) = self.framebuffer_size;
let sx = fw as f64 / cw as f64; let sx = fw as f64 / cw.max(1) as f64;
let sy = fh as f64 / ch as f64; let sy = fh as f64 / ch.max(1) as f64;
if let Some((lx, ly)) = self.last_cursor_pos { // Im Capture-Mode liefert winit geclampte CursorMoved-Werte;
self.mouse_delta.0 += ((x - lx) * sx) as f32; // den Delta-Pfad nehmen wir dort aus DeviceEvent::MouseMotion.
self.mouse_delta.1 += ((y - ly) * sy) as f32; if !self.mouse_capture {
if let Some((lx, ly)) = *self.last_cursor_pos {
self.mouse_delta.0 += ((x - lx) * sx) as f32;
self.mouse_delta.1 += ((y - ly) * sy) as f32;
}
*self.last_cursor_pos = Some((x, y));
} }
self.last_cursor_pos = Some((x, y));
let fx = ((x - cx as f64) * sx) as f32; let fx = ((x - cx as f64) * sx) as f32;
let fy = ((y - cy as f64) * sy) as f32; let fy = ((y - cy as f64) * sy) as f32;
self.event_queue.push_back(Event::MouseMove { x: fx, y: fy }); self.event_queue.push_back(Event::MouseMove { x: fx, y: fy });
} }
W::MouseButton(btn, action, _) => if let Some(button) = from_glfw_button(btn) { WindowEvent::MouseInput { button, state, .. } => if let Some(b) = from_winit_button(button) {
self.event_queue.push_back(Event::MouseBtn { button, pressed: action != glfw::Action::Release }); self.event_queue.push_back(Event::MouseBtn {
} button: b,
W::Close => { pressed: state == ElementState::Pressed,
self.close_requested = true; });
self.event_queue.push_back(Event::CloseRequested);
} }
_ => {} _ => {}
} }
} }
fn device_event(&mut self, _: &ActiveEventLoop, _: DeviceId, ev: DeviceEvent) {
if self.mouse_capture && let DeviceEvent::MouseMotion { delta: (dx, dy) } = ev {
let (_, _, cw, ch) = *self.content_rect;
let (fw, fh) = self.framebuffer_size;
let sx = fw as f64 / cw.max(1) as f64;
let sy = fh as f64 / ch.max(1) as f64;
self.mouse_delta.0 += (dx * sx) as f32;
self.mouse_delta.1 += (dy * sy) as f32;
}
}
} }
// --- Helpers --------------------------------------------------------------- // --- Helpers ---------------------------------------------------------------
@@ -442,30 +537,39 @@ fn make_tex(device: &wgpu::Device, label: &str, w: u32, h: u32, format: wgpu::Te
}) })
} }
fn from_glfw_key(k: glfw::Key) -> Option<Key> { fn from_winit_keycode(k: KeyCode) -> Option<Key> {
use glfw::Key as G; use KeyCode as K;
macro_rules! same { ($($n:ident),* $(,)?) => { match k { Some(match k {
$(G::$n => Some(Key::$n),)* K::KeyA => Key::A, K::KeyB => Key::B, K::KeyC => Key::C, K::KeyD => Key::D,
G::LeftShift => Some(Key::LShift), G::RightShift => Some(Key::RShift), K::KeyE => Key::E, K::KeyF => Key::F, K::KeyG => Key::G, K::KeyH => Key::H,
G::LeftControl => Some(Key::LCtrl), G::RightControl => Some(Key::RCtrl), K::KeyI => Key::I, K::KeyJ => Key::J, K::KeyK => Key::K, K::KeyL => Key::L,
G::LeftAlt => Some(Key::LAlt), G::RightAlt => Some(Key::RAlt), K::KeyM => Key::M, K::KeyN => Key::N, K::KeyO => Key::O, K::KeyP => Key::P,
_ => None, K::KeyQ => Key::Q, K::KeyR => Key::R, K::KeyS => Key::S, K::KeyT => Key::T,
}}; } K::KeyU => Key::U, K::KeyV => Key::V, K::KeyW => Key::W, K::KeyX => Key::X,
same!( K::KeyY => Key::Y, K::KeyZ => Key::Z,
A, B, C, D, E, F, G, H, I, J, K, L, M, K::Digit0 => Key::Num0, K::Digit1 => Key::Num1, K::Digit2 => Key::Num2,
N, O, P, Q, R, S, T, U, V, W, X, Y, Z, K::Digit3 => Key::Num3, K::Digit4 => Key::Num4, K::Digit5 => Key::Num5,
Num0, Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9, K::Digit6 => Key::Num6, K::Digit7 => Key::Num7, K::Digit8 => Key::Num8,
Up, Down, Left, Right, K::Digit9 => Key::Num9,
Space, Enter, Escape, Tab, Backspace, K::ArrowUp => Key::Up, K::ArrowDown => Key::Down,
F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, K::ArrowLeft => Key::Left, K::ArrowRight => Key::Right,
) K::Space => Key::Space, K::Enter => Key::Enter, K::Escape => Key::Escape,
K::Tab => Key::Tab, K::Backspace => Key::Backspace,
K::ShiftLeft => Key::LShift, K::ShiftRight => Key::RShift,
K::ControlLeft => Key::LCtrl, K::ControlRight => Key::RCtrl,
K::AltLeft => Key::LAlt, K::AltRight => Key::RAlt,
K::F1 => Key::F1, K::F2 => Key::F2, K::F3 => Key::F3, K::F4 => Key::F4,
K::F5 => Key::F5, K::F6 => Key::F6, K::F7 => Key::F7, K::F8 => Key::F8,
K::F9 => Key::F9, K::F10 => Key::F10, K::F11 => Key::F11, K::F12 => Key::F12,
_ => return None,
})
} }
fn from_glfw_button(b: glfw::MouseButton) -> Option<MouseButton> { fn from_winit_button(b: WinitMouseButton) -> Option<MouseButton> {
match b { match b {
glfw::MouseButton::Button1 => Some(MouseButton::Left), WinitMouseButton::Left => Some(MouseButton::Left),
glfw::MouseButton::Button2 => Some(MouseButton::Right), WinitMouseButton::Right => Some(MouseButton::Right),
glfw::MouseButton::Button3 => Some(MouseButton::Middle), WinitMouseButton::Middle => Some(MouseButton::Middle),
_ => None, _ => None,
} }
} }