opengl/es renderer

This commit is contained in:
Alula 2021-04-14 12:09:40 +02:00
parent 8271920178
commit a08ea7a86b
No known key found for this signature in database
GPG Key ID: 3E00485503A1D8BA
9 changed files with 784 additions and 910 deletions

View File

@ -23,11 +23,12 @@ panic = 'abort'
opt-level = 3
[features]
default = ["scripting", "backend-sdl", "ogg-playback", "netplay", "exe"]
default = ["scripting", "backend-sdl", "render-opengl", "ogg-playback", "exe"]
ogg-playback = ["lewton"]
backend-sdl = ["sdl2", "sdl2-sys"]
#backend-sokol = ["sokol"]
backend-glutin = ["winit", "glutin"]
backend-glutin = ["winit", "glutin", "render-opengl"]
render-opengl = []
scripting = ["lua-ffi"]
netplay = []
editor = []
@ -45,7 +46,7 @@ chrono = "0.4"
cpal = { git = "https://github.com/doukutsu-rs/cpal.git", rev = "e027550be0b93b7e2912c7de28a4944a7d04e070" }
directories = "3"
funty = "=1.1.0" # https://github.com/bitvecto-rs/bitvec/issues/105
glutin = { git = "https://github.com/doukutsu-rs/glutin.git", rev = "a34ee3c99b3c999b638ca2bae53cf96df2b94c04", optional = true }
glutin = { git = "https://github.com/doukutsu-rs/glutin.git", rev = "a34ee3c99b3c999b638ca2bae53cf96df2b94c04", optional = true, default_features = false, features = ["x11"] }
imgui = "0.7.0"
image = { version = "0.23", default-features = false, features = ["png", "bmp"] }
itertools = "0.10"
@ -54,7 +55,6 @@ lewton = { version = "0.10.2", optional = true }
libc = { version = "0.2", optional = true }
log = "0.4"
lua-ffi = { git = "https://github.com/doukutsu-rs/lua-ffi.git", rev = "1ef3caf772d72068297ddf75df06fd2ef8c1daab", optional = true }
lru = "0.6.0"
num-derive = "0.3.2"
num-traits = "0.2.12"
paste = "1.0.0"
@ -70,7 +70,7 @@ strum_macros = "0.20"
# remove and replace when drain_filter is in stable
vec_mut_scan = "0.4"
webbrowser = "0.5.5"
winit = { version = "0.24", optional = true }
winit = { version = "0.24", optional = true, default_features = false, features = ["x11"] }
#[build-dependencies]
#gl_generator = { version = "0.14.0", optional = true }

View File

@ -17,6 +17,8 @@ pub trait BackendEventLoop {
}
pub trait BackendRenderer {
fn renderer_name(&self) -> String;
fn clear(&mut self, color: Color);
fn present(&mut self) -> GameResult;
@ -55,7 +57,7 @@ pub trait BackendTexture {
pub fn init_backend() -> GameResult<Box<dyn Backend>> {
#[cfg(all(feature = "backend-glutin"))]
{
return crate::framework::backend_opengl::GlutinBackend::new();
return crate::framework::backend_glutin::GlutinBackend::new();
}
#[cfg(feature = "backend-sokol")]

View File

@ -0,0 +1,518 @@
use std::cell::{RefCell, UnsafeCell};
use std::ffi::c_void;
use std::mem;
use std::rc::Rc;
use std::sync::Arc;
use glutin::{Api, ContextBuilder, GlProfile, GlRequest, PossiblyCurrent, WindowedContext};
use glutin::event::{ElementState, Event, TouchPhase, VirtualKeyCode, WindowEvent};
use glutin::event_loop::{ControlFlow, EventLoop};
use glutin::window::WindowBuilder;
use imgui::{DrawCmdParams, DrawData, DrawIdx, DrawVert};
use crate::{Game, GAME_SUSPENDED};
use crate::common::Rect;
use crate::framework::backend::{Backend, BackendEventLoop, BackendRenderer, BackendTexture, SpriteBatchCommand};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::gl;
use crate::framework::keyboard::ScanCode;
use crate::framework::render_opengl::{GLContext, OpenGLRenderer};
use crate::input::touch_controls::TouchPoint;
pub struct GlutinBackend;
impl GlutinBackend {
pub fn new() -> GameResult<Box<dyn Backend>> {
Ok(Box::new(GlutinBackend))
}
}
impl Backend for GlutinBackend {
fn create_event_loop(&self) -> GameResult<Box<dyn BackendEventLoop>> {
#[cfg(target_os = "android")]
loop {
match ndk_glue::native_window().as_ref() {
Some(_) => {
log::info!("NativeWindow Found: {:?}", ndk_glue::native_window());
break;
}
None => (),
}
}
Ok(Box::new(GlutinEventLoop { refs: Rc::new(UnsafeCell::new(None)) }))
}
}
pub struct GlutinEventLoop {
refs: Rc<UnsafeCell<Option<WindowedContext<PossiblyCurrent>>>>,
}
impl GlutinEventLoop {
fn get_context(&self, event_loop: &EventLoop<()>) -> &mut WindowedContext<PossiblyCurrent> {
let mut refs = unsafe { &mut *self.refs.get() };
if refs.is_none() {
let mut window = WindowBuilder::new();
let windowed_context = ContextBuilder::new();
let windowed_context = windowed_context.with_gl(GlRequest::Specific(Api::OpenGl, (3, 0)));
#[cfg(target_os = "android")]
let windowed_context = windowed_context.with_gl(GlRequest::Specific(Api::OpenGlEs, (2, 0)));
let windowed_context = windowed_context.with_gl_profile(GlProfile::Core)
.with_gl_debug_flag(false)
.with_pixel_format(24, 8)
.with_vsync(true);
#[cfg(target_os = "windows")]
{
use glutin::platform::windows::WindowBuilderExtWindows;
window = window.with_drag_and_drop(false);
}
window = window.with_title("doukutsu-rs");
let windowed_context = windowed_context.build_windowed(window, event_loop).unwrap();
let windowed_context = unsafe { windowed_context.make_current().unwrap() };
#[cfg(target_os = "android")]
if let Some(nwin) = ndk_glue::native_window().as_ref() {
unsafe {
windowed_context.surface_created(nwin.ptr().as_ptr() as *mut std::ffi::c_void);
}
}
refs.replace(windowed_context);
}
refs.as_mut().unwrap()
}
}
#[cfg(target_os = "android")]
fn request_android_redraw() {
match ndk_glue::native_window().as_ref() {
Some(native_window) => {
let a_native_window: *mut ndk_sys::ANativeWindow = native_window.ptr().as_ptr();
let a_native_activity: *mut ndk_sys::ANativeActivity = ndk_glue::native_activity().ptr().as_ptr();
unsafe {
match (*(*a_native_activity).callbacks).onNativeWindowRedrawNeeded {
Some(callback) => callback(a_native_activity, a_native_window),
None => (),
};
};
}
None => (),
}
}
#[cfg(target_os = "android")]
fn get_insets() -> GameResult<(f32, f32, f32, f32)> {
unsafe {
let vm_ptr = ndk_glue::native_activity().vm();
let vm = unsafe { jni::JavaVM::from_raw(vm_ptr) }?;
let vm_env = vm.attach_current_thread()?;
//let class = vm_env.find_class("io/github/doukutsu_rs/MainActivity")?;
let class = vm_env.new_global_ref(ndk_glue::native_activity().activity())?;
let field = vm_env.get_field(class.as_obj(), "displayInsets", "[I")?.to_jni().l as jni::sys::jintArray;
let mut elements = [0; 4];
vm_env.get_int_array_region(field, 0, &mut elements)?;
Ok((elements[0] as f32, elements[1] as f32, elements[2] as f32, elements[3] as f32))
}
}
impl BackendEventLoop for GlutinEventLoop {
fn run(&mut self, game: &mut Game, ctx: &mut Context) {
let event_loop = EventLoop::new();
let state_ref = unsafe { &mut *game.state.get() };
let window: &'static mut WindowedContext<PossiblyCurrent> =
unsafe { std::mem::transmute(self.get_context(&event_loop)) };
{
let size = window.window().inner_size();
ctx.screen_size = (size.width.max(1) as f32, size.height.max(1) as f32);
state_ref.handle_resize(ctx).unwrap();
}
// it won't ever return
let (game, ctx): (&'static mut Game, &'static mut Context) =
unsafe { (std::mem::transmute(game), std::mem::transmute(ctx)) };
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event: WindowEvent::CloseRequested, window_id }
if window_id == window.window().id() =>
{
state_ref.shutdown();
}
Event::Resumed => {
println!("resumed!");
{
let mut mutex = GAME_SUSPENDED.lock().unwrap();
*mutex = false;
}
#[cfg(target_os = "android")]
if let Some(nwin) = ndk_glue::native_window().as_ref() {
state_ref.graphics_reset();
unsafe {
window.surface_created(nwin.ptr().as_ptr() as *mut std::ffi::c_void);
request_android_redraw();
}
}
}
Event::Suspended => {
println!("suspended!");
{
let mut mutex = GAME_SUSPENDED.lock().unwrap();
*mutex = true;
}
#[cfg(target_os = "android")]
unsafe {
window.surface_destroyed();
}
}
Event::WindowEvent { event: WindowEvent::Resized(size), window_id }
if window_id == window.window().id() =>
{
if let Some(renderer) = ctx.renderer.as_ref() {
if let Ok(imgui) = renderer.imgui() {
imgui.io_mut().display_size = [size.width as f32, size.height as f32];
}
ctx.screen_size = (size.width as f32, size.height as f32);
state_ref.handle_resize(ctx).unwrap();
}
}
Event::WindowEvent { event: WindowEvent::Touch(touch), window_id }
if window_id == window.window().id() =>
{
let mut controls = &mut state_ref.touch_controls;
let scale = state_ref.scale as f64;
match touch.phase {
TouchPhase::Started | TouchPhase::Moved => {
if let Some(point) = controls.points.iter_mut().find(|p| p.id == touch.id) {
point.last_position = point.position;
point.position = (touch.location.x / scale, touch.location.y / scale);
} else {
controls.touch_id_counter = controls.touch_id_counter.wrapping_add(1);
let point = TouchPoint {
id: touch.id,
touch_id: controls.touch_id_counter,
position: (touch.location.x / scale, touch.location.y / scale),
last_position: (0.0, 0.0),
};
controls.points.push(point);
if touch.phase == TouchPhase::Started {
controls.clicks.push(point);
}
}
}
TouchPhase::Ended | TouchPhase::Cancelled => {
controls.points.retain(|p| p.id != touch.id);
controls.clicks.retain(|p| p.id != touch.id);
}
}
}
Event::WindowEvent { event: WindowEvent::KeyboardInput { input, .. }, window_id }
if window_id == window.window().id() =>
{
if let Some(keycode) = input.virtual_keycode {
if let Some(drs_scan) = conv_keycode(keycode) {
let key_state = match input.state {
ElementState::Pressed => true,
ElementState::Released => false,
};
ctx.keyboard_context.set_key(drs_scan, key_state);
}
}
}
Event::RedrawRequested(id) if id == window.window().id() => {
{
let mutex = GAME_SUSPENDED.lock().unwrap();
if *mutex {
return;
}
}
#[cfg(not(target_os = "android"))]
{
if let Err(err) = game.draw(ctx) {
log::error!("Failed to draw frame: {}", err);
}
window.window().request_redraw();
}
#[cfg(target_os = "android")]
request_android_redraw();
}
Event::MainEventsCleared => {
if state_ref.shutdown {
log::info!("Shutting down...");
*control_flow = ControlFlow::Exit;
return;
}
{
let mutex = GAME_SUSPENDED.lock().unwrap();
if *mutex {
return;
}
}
game.update(ctx).unwrap();
#[cfg(target_os = "android")]
{
match get_insets() {
Ok(insets) => {
ctx.screen_insets = insets;
}
Err(e) => {
log::error!("Failed to update insets: {}", e);
}
}
if let Err(err) = game.draw(ctx) {
log::error!("Failed to draw frame: {}", err);
}
}
if state_ref.next_scene.is_some() {
mem::swap(&mut game.scene, &mut state_ref.next_scene);
state_ref.next_scene = None;
game.scene.as_mut().unwrap().init(state_ref, ctx).unwrap();
game.loops = 0;
state_ref.frame_time = 0.0;
}
}
_ => (),
}
});
}
fn new_renderer(&self) -> GameResult<Box<dyn BackendRenderer>> {
let mut imgui = imgui::Context::create();
imgui.io_mut().display_size = [640.0, 480.0];
let refs = self.refs.clone();
let user_data = Rc::into_raw(refs) as *mut c_void;
unsafe fn get_proc_address(user_data: &mut *mut c_void, name: &str) -> *const c_void {
let refs = Rc::from_raw(*user_data as *mut UnsafeCell<Option<WindowedContext<PossiblyCurrent>>>);
let result = {
let refs = &mut *refs.get();
if let Some(refs) = refs {
refs.get_proc_address(name)
} else {
std::ptr::null()
}
};
*user_data = Rc::into_raw(refs) as *mut c_void;
result
}
unsafe fn swap_buffers(user_data: &mut *mut c_void) {
let refs = Rc::from_raw(*user_data as *mut UnsafeCell<Option<WindowedContext<PossiblyCurrent>>>);
{
let refs = &mut *refs.get();
if let Some(refs) = refs {
refs.swap_buffers();
}
}
*user_data = Rc::into_raw(refs) as *mut c_void;
}
let gl_context = GLContext { get_proc_address, swap_buffers, user_data };
Ok(Box::new(OpenGLRenderer::new(gl_context, UnsafeCell::new(imgui))))
}
}
fn conv_keycode(code: VirtualKeyCode) -> Option<ScanCode> {
match code {
VirtualKeyCode::Key1 => Some(ScanCode::Key1),
VirtualKeyCode::Key2 => Some(ScanCode::Key2),
VirtualKeyCode::Key3 => Some(ScanCode::Key3),
VirtualKeyCode::Key4 => Some(ScanCode::Key4),
VirtualKeyCode::Key5 => Some(ScanCode::Key5),
VirtualKeyCode::Key6 => Some(ScanCode::Key6),
VirtualKeyCode::Key7 => Some(ScanCode::Key7),
VirtualKeyCode::Key8 => Some(ScanCode::Key8),
VirtualKeyCode::Key9 => Some(ScanCode::Key9),
VirtualKeyCode::Key0 => Some(ScanCode::Key0),
VirtualKeyCode::A => Some(ScanCode::A),
VirtualKeyCode::B => Some(ScanCode::B),
VirtualKeyCode::C => Some(ScanCode::C),
VirtualKeyCode::D => Some(ScanCode::D),
VirtualKeyCode::E => Some(ScanCode::E),
VirtualKeyCode::F => Some(ScanCode::F),
VirtualKeyCode::G => Some(ScanCode::G),
VirtualKeyCode::H => Some(ScanCode::H),
VirtualKeyCode::I => Some(ScanCode::I),
VirtualKeyCode::J => Some(ScanCode::J),
VirtualKeyCode::K => Some(ScanCode::K),
VirtualKeyCode::L => Some(ScanCode::L),
VirtualKeyCode::M => Some(ScanCode::M),
VirtualKeyCode::N => Some(ScanCode::N),
VirtualKeyCode::O => Some(ScanCode::O),
VirtualKeyCode::P => Some(ScanCode::P),
VirtualKeyCode::Q => Some(ScanCode::Q),
VirtualKeyCode::R => Some(ScanCode::R),
VirtualKeyCode::S => Some(ScanCode::S),
VirtualKeyCode::T => Some(ScanCode::T),
VirtualKeyCode::U => Some(ScanCode::U),
VirtualKeyCode::V => Some(ScanCode::V),
VirtualKeyCode::W => Some(ScanCode::W),
VirtualKeyCode::X => Some(ScanCode::X),
VirtualKeyCode::Y => Some(ScanCode::Y),
VirtualKeyCode::Z => Some(ScanCode::Z),
VirtualKeyCode::Escape => Some(ScanCode::Escape),
VirtualKeyCode::F1 => Some(ScanCode::F1),
VirtualKeyCode::F2 => Some(ScanCode::F2),
VirtualKeyCode::F3 => Some(ScanCode::F3),
VirtualKeyCode::F4 => Some(ScanCode::F4),
VirtualKeyCode::F5 => Some(ScanCode::F5),
VirtualKeyCode::F6 => Some(ScanCode::F6),
VirtualKeyCode::F7 => Some(ScanCode::F7),
VirtualKeyCode::F8 => Some(ScanCode::F8),
VirtualKeyCode::F9 => Some(ScanCode::F9),
VirtualKeyCode::F10 => Some(ScanCode::F10),
VirtualKeyCode::F11 => Some(ScanCode::F11),
VirtualKeyCode::F12 => Some(ScanCode::F12),
VirtualKeyCode::F13 => Some(ScanCode::F13),
VirtualKeyCode::F14 => Some(ScanCode::F14),
VirtualKeyCode::F15 => Some(ScanCode::F15),
VirtualKeyCode::F16 => Some(ScanCode::F16),
VirtualKeyCode::F17 => Some(ScanCode::F17),
VirtualKeyCode::F18 => Some(ScanCode::F18),
VirtualKeyCode::F19 => Some(ScanCode::F19),
VirtualKeyCode::F20 => Some(ScanCode::F20),
VirtualKeyCode::F21 => Some(ScanCode::F21),
VirtualKeyCode::F22 => Some(ScanCode::F22),
VirtualKeyCode::F23 => Some(ScanCode::F23),
VirtualKeyCode::F24 => Some(ScanCode::F24),
VirtualKeyCode::Snapshot => Some(ScanCode::Snapshot),
VirtualKeyCode::Scroll => Some(ScanCode::Scroll),
VirtualKeyCode::Pause => Some(ScanCode::Pause),
VirtualKeyCode::Insert => Some(ScanCode::Insert),
VirtualKeyCode::Home => Some(ScanCode::Home),
VirtualKeyCode::Delete => Some(ScanCode::Delete),
VirtualKeyCode::End => Some(ScanCode::End),
VirtualKeyCode::PageDown => Some(ScanCode::PageDown),
VirtualKeyCode::PageUp => Some(ScanCode::PageUp),
VirtualKeyCode::Left => Some(ScanCode::Left),
VirtualKeyCode::Up => Some(ScanCode::Up),
VirtualKeyCode::Right => Some(ScanCode::Right),
VirtualKeyCode::Down => Some(ScanCode::Down),
VirtualKeyCode::Back => Some(ScanCode::Back),
VirtualKeyCode::Return => Some(ScanCode::Return),
VirtualKeyCode::Space => Some(ScanCode::Space),
VirtualKeyCode::Compose => Some(ScanCode::Compose),
VirtualKeyCode::Caret => Some(ScanCode::Caret),
VirtualKeyCode::Numlock => Some(ScanCode::Numlock),
VirtualKeyCode::Numpad0 => Some(ScanCode::Numpad0),
VirtualKeyCode::Numpad1 => Some(ScanCode::Numpad1),
VirtualKeyCode::Numpad2 => Some(ScanCode::Numpad2),
VirtualKeyCode::Numpad3 => Some(ScanCode::Numpad3),
VirtualKeyCode::Numpad4 => Some(ScanCode::Numpad4),
VirtualKeyCode::Numpad5 => Some(ScanCode::Numpad5),
VirtualKeyCode::Numpad6 => Some(ScanCode::Numpad6),
VirtualKeyCode::Numpad7 => Some(ScanCode::Numpad7),
VirtualKeyCode::Numpad8 => Some(ScanCode::Numpad8),
VirtualKeyCode::Numpad9 => Some(ScanCode::Numpad9),
VirtualKeyCode::NumpadAdd => Some(ScanCode::NumpadAdd),
VirtualKeyCode::NumpadDivide => Some(ScanCode::NumpadDivide),
VirtualKeyCode::NumpadDecimal => Some(ScanCode::NumpadDecimal),
VirtualKeyCode::NumpadComma => Some(ScanCode::NumpadComma),
VirtualKeyCode::NumpadEnter => Some(ScanCode::NumpadEnter),
VirtualKeyCode::NumpadEquals => Some(ScanCode::NumpadEquals),
VirtualKeyCode::NumpadMultiply => Some(ScanCode::NumpadMultiply),
VirtualKeyCode::NumpadSubtract => Some(ScanCode::NumpadSubtract),
VirtualKeyCode::AbntC1 => Some(ScanCode::AbntC1),
VirtualKeyCode::AbntC2 => Some(ScanCode::AbntC2),
VirtualKeyCode::Apostrophe => Some(ScanCode::Apostrophe),
VirtualKeyCode::Apps => Some(ScanCode::Apps),
VirtualKeyCode::Asterisk => Some(ScanCode::Asterisk),
VirtualKeyCode::At => Some(ScanCode::At),
VirtualKeyCode::Ax => Some(ScanCode::Ax),
VirtualKeyCode::Backslash => Some(ScanCode::Backslash),
VirtualKeyCode::Calculator => Some(ScanCode::Calculator),
VirtualKeyCode::Capital => Some(ScanCode::Capital),
VirtualKeyCode::Colon => Some(ScanCode::Colon),
VirtualKeyCode::Comma => Some(ScanCode::Comma),
VirtualKeyCode::Convert => Some(ScanCode::Convert),
VirtualKeyCode::Equals => Some(ScanCode::Equals),
VirtualKeyCode::Grave => Some(ScanCode::Grave),
VirtualKeyCode::Kana => Some(ScanCode::Kana),
VirtualKeyCode::Kanji => Some(ScanCode::Kanji),
VirtualKeyCode::LAlt => Some(ScanCode::LAlt),
VirtualKeyCode::LBracket => Some(ScanCode::LBracket),
VirtualKeyCode::LControl => Some(ScanCode::LControl),
VirtualKeyCode::LShift => Some(ScanCode::LShift),
VirtualKeyCode::LWin => Some(ScanCode::LWin),
VirtualKeyCode::Mail => Some(ScanCode::Mail),
VirtualKeyCode::MediaSelect => Some(ScanCode::MediaSelect),
VirtualKeyCode::MediaStop => Some(ScanCode::MediaStop),
VirtualKeyCode::Minus => Some(ScanCode::Minus),
VirtualKeyCode::Mute => Some(ScanCode::Mute),
VirtualKeyCode::MyComputer => Some(ScanCode::MyComputer),
VirtualKeyCode::NavigateForward => Some(ScanCode::NavigateForward),
VirtualKeyCode::NavigateBackward => Some(ScanCode::NavigateBackward),
VirtualKeyCode::NextTrack => Some(ScanCode::NextTrack),
VirtualKeyCode::NoConvert => Some(ScanCode::NoConvert),
VirtualKeyCode::OEM102 => Some(ScanCode::OEM102),
VirtualKeyCode::Period => Some(ScanCode::Period),
VirtualKeyCode::PlayPause => Some(ScanCode::PlayPause),
VirtualKeyCode::Plus => Some(ScanCode::Plus),
VirtualKeyCode::Power => Some(ScanCode::Power),
VirtualKeyCode::PrevTrack => Some(ScanCode::PrevTrack),
VirtualKeyCode::RAlt => Some(ScanCode::RAlt),
VirtualKeyCode::RBracket => Some(ScanCode::RBracket),
VirtualKeyCode::RControl => Some(ScanCode::RControl),
VirtualKeyCode::RShift => Some(ScanCode::RShift),
VirtualKeyCode::RWin => Some(ScanCode::RWin),
VirtualKeyCode::Semicolon => Some(ScanCode::Semicolon),
VirtualKeyCode::Slash => Some(ScanCode::Slash),
VirtualKeyCode::Sleep => Some(ScanCode::Sleep),
VirtualKeyCode::Stop => Some(ScanCode::Stop),
VirtualKeyCode::Sysrq => Some(ScanCode::Sysrq),
VirtualKeyCode::Tab => Some(ScanCode::Tab),
VirtualKeyCode::Underline => Some(ScanCode::Underline),
VirtualKeyCode::Unlabeled => Some(ScanCode::Unlabeled),
VirtualKeyCode::VolumeDown => Some(ScanCode::VolumeDown),
VirtualKeyCode::VolumeUp => Some(ScanCode::VolumeUp),
VirtualKeyCode::Wake => Some(ScanCode::Wake),
VirtualKeyCode::WebBack => Some(ScanCode::WebBack),
VirtualKeyCode::WebFavorites => Some(ScanCode::WebFavorites),
VirtualKeyCode::WebForward => Some(ScanCode::WebForward),
VirtualKeyCode::WebHome => Some(ScanCode::WebHome),
VirtualKeyCode::WebRefresh => Some(ScanCode::WebRefresh),
VirtualKeyCode::WebSearch => Some(ScanCode::WebSearch),
VirtualKeyCode::WebStop => Some(ScanCode::WebStop),
VirtualKeyCode::Yen => Some(ScanCode::Yen),
VirtualKeyCode::Copy => Some(ScanCode::Copy),
VirtualKeyCode::Paste => Some(ScanCode::Paste),
VirtualKeyCode::Cut => Some(ScanCode::Cut),
}
}

View File

@ -82,6 +82,10 @@ impl BackendTexture for NullTexture {
pub struct NullRenderer(RefCell<imgui::Context>);
impl BackendRenderer for NullRenderer {
fn renderer_name(&self) -> String {
"Null".to_owned()
}
fn clear(&mut self, _color: Color) {
}

View File

@ -1,6 +1,7 @@
use core::mem;
use std::cell::RefCell;
use std::cell::{RefCell, UnsafeCell};
use std::collections::HashMap;
use std::ffi::c_void;
use std::rc::Rc;
use imgui::internal::RawWrapper;
@ -11,14 +12,15 @@ use sdl2::mouse::{Cursor, SystemCursor};
use sdl2::pixels::PixelFormatEnum;
use sdl2::render::{Texture, TextureCreator, WindowCanvas};
use sdl2::video::WindowContext;
use sdl2::{keyboard, pixels, EventPump, Sdl};
use sdl2::{keyboard, pixels, EventPump, Sdl, VideoSubsystem};
use crate::common::{Color, Rect};
use crate::framework::backend::{Backend, BackendEventLoop, BackendRenderer, BackendTexture, SpriteBatchCommand};
use crate::framework::context::Context;
use crate::framework::error::{GameError, GameResult};
use crate::framework::graphics::{BlendMode};
use crate::framework::graphics::BlendMode;
use crate::framework::keyboard::ScanCode;
use crate::framework::render_opengl::{GLContext, OpenGLRenderer};
use crate::framework::ui::init_imgui;
use crate::Game;
use crate::GAME_SUSPENDED;
@ -46,29 +48,49 @@ impl Backend for SDL2Backend {
struct SDL2EventLoop {
event_pump: EventPump,
refs: Rc<RefCell<SDL2Context>>,
opengl_available: RefCell<bool>,
}
struct SDL2Context {
video: VideoSubsystem,
canvas: WindowCanvas,
texture_creator: TextureCreator<WindowContext>,
gl_context: Option<sdl2::video::GLContext>,
blend_mode: sdl2::render::BlendMode,
}
impl SDL2EventLoop {
pub fn new(sdl: &Sdl) -> GameResult<Box<dyn BackendEventLoop>> {
sdl2::hint::set("SDL_HINT_RENDER_DRIVER", "opengles2");
let event_pump = sdl.event_pump().map_err(|e| GameError::WindowError(e))?;
let video = sdl.video().map_err(|e| GameError::WindowError(e))?;
let window =
video.window("Cave Story (doukutsu-rs)", 640, 480).position_centered().resizable().build().map_err(|e| GameError::WindowError(e.to_string()))?;
let window = video
.window("Cave Story (doukutsu-rs)", 640, 480)
.position_centered()
.resizable()
.build()
.map_err(|e| GameError::WindowError(e.to_string()))?;
let canvas = window.into_canvas().accelerated().present_vsync().build().map_err(|e| GameError::RenderError(e.to_string()))?;
let canvas = window
.into_canvas()
.accelerated()
.present_vsync()
.build()
.map_err(|e| GameError::RenderError(e.to_string()))?;
let texture_creator = canvas.texture_creator();
let event_loop =
SDL2EventLoop { event_pump, refs: Rc::new(RefCell::new(SDL2Context { canvas, texture_creator, blend_mode: sdl2::render::BlendMode::Blend })) };
let opengl_available = if let Ok(v) = std::env::var("CAVESTORY_NO_OPENGL") { v != "1" } else { true };
let event_loop = SDL2EventLoop {
event_pump,
refs: Rc::new(RefCell::new(SDL2Context {
video,
canvas,
texture_creator,
gl_context: None,
blend_mode: sdl2::render::BlendMode::Blend,
})),
opengl_available: RefCell::new(opengl_available),
};
Ok(Box::new(event_loop))
}
@ -169,12 +191,71 @@ impl BackendEventLoop for SDL2EventLoop {
state.frame_time = 0.0;
}
imgui_sdl2.prepare_frame(imgui.io_mut(), self.refs.borrow().canvas.window(), &self.event_pump.mouse_state());
imgui_sdl2.prepare_frame(
imgui.io_mut(),
self.refs.borrow().canvas.window(),
&self.event_pump.mouse_state(),
);
game.draw(ctx).unwrap();
}
}
fn new_renderer(&self) -> GameResult<Box<dyn BackendRenderer>> {
#[cfg(feature = "render-opengl")]
{
let mut refs = self.refs.borrow_mut();
match refs.canvas.window().gl_create_context() {
Ok(gl_ctx) => {
refs.canvas.window().gl_make_current(&gl_ctx);
refs.gl_context = Some(gl_ctx);
}
Err(err) => {
*self.opengl_available.borrow_mut() = false;
log::error!("Failed to initialize OpenGL context, falling back to SDL2 renderer: {}", err);
}
}
}
#[cfg(feature = "render-opengl")]
if *self.opengl_available.borrow() {
let mut imgui = init_imgui()?;
let refs = self.refs.clone();
let user_data = Rc::into_raw(refs) as *mut c_void;
unsafe fn get_proc_address(user_data: &mut *mut c_void, name: &str) -> *const c_void {
let refs = Rc::from_raw(*user_data as *mut RefCell<SDL2Context>);
let result = {
let refs = &mut *refs.as_ptr();
refs.video.gl_get_proc_address(name) as *const _
};
log::info!("gl proc {} -> {:?}", name, result);
*user_data = Rc::into_raw(refs) as *mut c_void;
result
}
unsafe fn swap_buffers(user_data: &mut *mut c_void) {
let refs = Rc::from_raw(*user_data as *mut RefCell<SDL2Context>);
{
let refs = &mut *refs.as_ptr();
refs.canvas.window().gl_swap_window();
}
*user_data = Rc::into_raw(refs) as *mut c_void;
}
let gl_context = GLContext { get_proc_address, swap_buffers, user_data };
return Ok(Box::new(OpenGLRenderer::new(gl_context, UnsafeCell::new(imgui))));
}
SDL2Renderer::new(self.refs.clone())
}
}
@ -224,7 +305,13 @@ impl SDL2Renderer {
imgui_textures.insert(
id,
SDL2Texture { refs: refs.clone(), texture: Some(texture), width: font_tex.width as u16, height: font_tex.height as u16, commands: vec![] },
SDL2Texture {
refs: refs.clone(),
texture: Some(texture),
width: font_tex.width as u16,
height: font_tex.height as u16,
commands: vec![],
},
);
}
@ -233,7 +320,12 @@ impl SDL2Renderer {
ImguiSdl2::new(&mut imgui, refs.canvas.window())
};
Ok(Box::new(SDL2Renderer { refs, imgui: Rc::new(RefCell::new(imgui)), imgui_event: Rc::new(RefCell::new(imgui_sdl2)), imgui_textures }))
Ok(Box::new(SDL2Renderer {
refs,
imgui: Rc::new(RefCell::new(imgui)),
imgui_event: Rc::new(RefCell::new(imgui_sdl2)),
imgui_textures,
}))
}
}
@ -242,7 +334,10 @@ fn to_sdl(color: Color) -> pixels::Color {
pixels::Color::RGBA(r, g, b, a)
}
unsafe fn set_raw_target(renderer: *mut sdl2::sys::SDL_Renderer, raw_texture: *mut sdl2::sys::SDL_Texture) -> GameResult {
unsafe fn set_raw_target(
renderer: *mut sdl2::sys::SDL_Renderer,
raw_texture: *mut sdl2::sys::SDL_Texture,
) -> GameResult {
if sdl2::sys::SDL_SetRenderTarget(renderer, raw_texture) == 0 {
Ok(())
} else {
@ -271,6 +366,10 @@ fn max3(x: f32, y: f32, z: f32) -> f32 {
}
impl BackendRenderer for SDL2Renderer {
fn renderer_name(&self) -> String {
"SDL2_Renderer".to_owned()
}
fn clear(&mut self, color: Color) {
let mut refs = self.refs.borrow_mut();
@ -366,7 +465,12 @@ impl BackendRenderer for SDL2Renderer {
refs.canvas.set_draw_color(pixels::Color::RGBA(r, g, b, a));
refs.canvas
.fill_rect(sdl2::rect::Rect::new(rect.left as i32, rect.top as i32, rect.width() as u32, rect.height() as u32))
.fill_rect(sdl2::rect::Rect::new(
rect.left as i32,
rect.top as i32,
rect.width() as u32,
rect.height() as u32,
))
.map_err(|e| GameError::RenderError(e.to_string()))?;
Ok(())
@ -383,15 +487,30 @@ impl BackendRenderer for SDL2Renderer {
0 => {} // no-op
1 => {
refs.canvas
.draw_rect(sdl2::rect::Rect::new(rect.left as i32, rect.top as i32, rect.width() as u32, rect.height() as u32))
.draw_rect(sdl2::rect::Rect::new(
rect.left as i32,
rect.top as i32,
rect.width() as u32,
rect.height() as u32,
))
.map_err(|e| GameError::RenderError(e.to_string()))?;
}
_ => {
let rects = [
sdl2::rect::Rect::new(rect.left as i32, rect.top as i32, rect.width() as u32, line_width as u32),
sdl2::rect::Rect::new(rect.left as i32, rect.bottom as i32 - line_width as i32, rect.width() as u32, line_width as u32),
sdl2::rect::Rect::new(
rect.left as i32,
rect.bottom as i32 - line_width as i32,
rect.width() as u32,
line_width as u32,
),
sdl2::rect::Rect::new(rect.left as i32, rect.top as i32, line_width as u32, rect.height() as u32),
sdl2::rect::Rect::new(rect.right as i32 - line_width as i32, rect.top as i32, line_width as u32, rect.height() as u32),
sdl2::rect::Rect::new(
rect.right as i32 - line_width as i32,
rect.top as i32,
line_width as u32,
rect.height() as u32,
),
];
refs.canvas.fill_rects(&rects).map_err(|e| GameError::RenderError(e.to_string()))?;
@ -433,9 +552,12 @@ impl BackendRenderer for SDL2Renderer {
continue;
}
let v1 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i] as usize];
let v2 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 1] as usize];
let v3 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 2] as usize];
let v1 = draw_list.vtx_buffer()
[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i] as usize];
let v2 = draw_list.vtx_buffer()
[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 1] as usize];
let v3 = draw_list.vtx_buffer()
[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 2] as usize];
vert_x[0] = (v1.pos[0] - 0.5) as i16;
vert_y[0] = (v1.pos[1] - 0.5) as i16;
@ -446,9 +568,12 @@ impl BackendRenderer for SDL2Renderer {
#[allow(clippy::float_cmp)]
if i < count - 3 {
let v4 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 3] as usize];
let v5 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 4] as usize];
let v6 = draw_list.vtx_buffer()[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 5] as usize];
let v4 = draw_list.vtx_buffer()
[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 3] as usize];
let v5 = draw_list.vtx_buffer()
[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 4] as usize];
let v6 = draw_list.vtx_buffer()
[cmd_params.vtx_offset + idx_buffer[cmd_params.idx_offset + i + 5] as usize];
min[0] = min3(v1.pos[0], v2.pos[0], v3.pos[0]);
min[1] = min3(v1.pos[1], v2.pos[1], v3.pos[1]);
@ -484,13 +609,20 @@ impl BackendRenderer for SDL2Renderer {
((tex_pos[2] - tex_pos[0]) * surf.width as f32) as u32,
((tex_pos[3] - tex_pos[1]) * surf.height as f32) as u32,
);
let dest = sdl2::rect::Rect::new(min[0] as i32, min[1] as i32, (max[0] - min[0]) as u32, (max[1] - min[1]) as u32);
let dest = sdl2::rect::Rect::new(
min[0] as i32,
min[1] as i32,
(max[0] - min[0]) as u32,
(max[1] - min[1]) as u32,
);
let tex = surf.texture.as_mut().unwrap();
tex.set_color_mod(v1.col[0], v1.col[1], v1.col[2]);
tex.set_alpha_mod(v1.col[3]);
refs.canvas.copy(tex, src, dest).map_err(|e| GameError::RenderError(e.to_string()))?;
refs.canvas
.copy(tex, src, dest)
.map_err(|e| GameError::RenderError(e.to_string()))?;
} else {
/*sdl2::sys::gfx::primitives::filledPolygonRGBA(
refs.canvas.raw(),
@ -842,7 +974,9 @@ impl ImguiSdl2 {
pub fn ignore_event(&self, event: &Event) -> bool {
match *event {
Event::KeyDown { .. } | Event::KeyUp { .. } | Event::TextEditing { .. } | Event::TextInput { .. } => self.ignore_keyboard,
Event::KeyDown { .. } | Event::KeyUp { .. } | Event::TextEditing { .. } | Event::TextInput { .. } => {
self.ignore_keyboard
}
Event::MouseMotion { .. }
| Event::MouseButtonDown { .. }
| Event::MouseButtonUp { .. }
@ -910,7 +1044,12 @@ impl ImguiSdl2 {
}
}
pub fn prepare_frame(&mut self, io: &mut imgui::Io, window: &sdl2::video::Window, mouse_state: &sdl2::mouse::MouseState) {
pub fn prepare_frame(
&mut self,
io: &mut imgui::Io,
window: &sdl2::video::Window,
mouse_state: &sdl2::mouse::MouseState,
) {
let mouse_util = window.subsystem().sdl().mouse();
let (win_w, win_h) = window.size();

View File

@ -1,358 +0,0 @@
use std::cell::RefCell;
use std::mem;
use std::ops::DerefMut;
use std::time::Duration;
use imgui::{DrawData, Ui};
use ndk::input_queue::InputQueue;
use sokol::app::{SApp, SAppDesc, SAppEvent, SAppEventType, SAppKeycode};
use sokol::gfx::{sg_isvalid, sg_query_backend, sg_shutdown};
use crate::common::{Color, Rect};
use crate::framework::backend::{Backend, BackendEventLoop, BackendRenderer, BackendTexture, SpriteBatchCommand};
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::graphics::BlendMode;
use crate::framework::keyboard::ScanCode;
use crate::Game;
pub struct SokolBackend;
impl SokolBackend {
pub fn new() -> GameResult<Box<dyn Backend>> {
Ok(Box::new(SokolBackend))
}
}
impl Backend for SokolBackend {
fn create_event_loop(&self) -> GameResult<Box<dyn BackendEventLoop>> {
Ok(Box::new(SokolEventLoop))
}
}
pub struct SokolEventLoop;
#[cfg(target_os = "android")]
extern "C" {
fn sapp_android_on_create(
activity: *mut ndk_sys::ANativeActivity,
window: *mut ndk_sys::ANativeWindow,
input_queue: *mut ndk_sys::AInputQueue,
);
}
impl BackendEventLoop for SokolEventLoop {
fn run(&mut self, game: &mut Game, ctx: &mut Context) {
#[cfg(target_os = "android")]
unsafe {
let activity = ndk_glue::native_activity().ptr().as_ptr();
let window = match ndk_glue::native_window().as_ref() {
None => std::ptr::null_mut(),
Some(p) => p.ptr().as_ptr(),
};
let input_queue = match ndk_glue::input_queue().as_ref() {
None => std::ptr::null_mut(),
Some(p) => p.ptr().as_ptr(),
};
println!("activity = {:?} window = {:?} input_queue = {:?}", activity, window, input_queue);
sapp_android_on_create(activity, window, input_queue);
}
struct Callbacks<'a, 'b> {
ctx: &'a mut Context,
game: &'b mut Game,
};
impl<'a, 'b> SApp for Callbacks<'a, 'b> {
fn sapp_init(&mut self) {
let state_ref = unsafe { &mut *self.game.state.get() };
self.ctx.screen_size = (640.0, 480.0);
state_ref.handle_resize(self.ctx).unwrap();
}
fn sapp_frame(&mut self) {
let state_ref = unsafe { &mut *self.game.state.get() };
self.game.update(self.ctx).unwrap();
// todo: not really supported on iOS/consoles
if state_ref.shutdown {
log::info!("Shutting down...");
std::process::exit(0);
return;
}
if state_ref.next_scene.is_some() {
mem::swap(&mut self.game.scene, &mut state_ref.next_scene);
state_ref.next_scene = None;
self.game.scene.as_mut().unwrap().init(state_ref, self.ctx).unwrap();
self.game.loops = 0;
state_ref.frame_time = 0.0;
}
self.game.draw(self.ctx).unwrap();
}
fn sapp_cleanup(&mut self) {
if sg_isvalid() {
sg_shutdown();
}
}
fn sapp_event(&mut self, event: SAppEvent) {
let state_ref = unsafe { &mut *self.game.state.get() };
println!("event: {:?}", event.event_type);
match event.event_type {
SAppEventType::Invalid => {}
SAppEventType::KeyDown => {
if let Some(drs_scan) = conv_scancode(event.key_code) {
state_ref.process_debug_keys(drs_scan);
self.ctx.keyboard_context.set_key(drs_scan, true);
}
}
SAppEventType::KeyUp => {
if let Some(drs_scan) = conv_scancode(event.key_code) {
self.ctx.keyboard_context.set_key(drs_scan, false);
}
}
SAppEventType::Char => {}
SAppEventType::MouseDown => {}
SAppEventType::MouseUp => {}
SAppEventType::MouseScroll => {}
SAppEventType::MouseMove => {}
SAppEventType::MouseEnter => {}
SAppEventType::MouseLeave => {}
SAppEventType::TouchesBegan => {}
SAppEventType::TouchesMoved => {}
SAppEventType::TouchesEnded => {}
SAppEventType::TouchesCancelled => {}
SAppEventType::Resized => {}
SAppEventType::Iconified => {}
SAppEventType::Restored => {}
SAppEventType::Suspended => {}
SAppEventType::Resumed => {}
SAppEventType::UpdateCursor => {}
SAppEventType::QuitRequested => {
state_ref.shutdown();
}
}
}
}
sokol::app::sapp_run(
Callbacks { ctx: unsafe { std::mem::transmute(ctx) }, game: unsafe { std::mem::transmute(game) } },
SAppDesc {
width: 640,
height: 480,
window_title: "doukutsu-rs".to_string(),
ios_keyboard_resizes_canvas: false,
..Default::default()
},
);
loop {
std::thread::sleep(Duration::from_millis(10))
}
}
fn new_renderer(&self) -> GameResult<Box<dyn BackendRenderer>> {
let mut imgui = imgui::Context::create();
imgui.io_mut().display_size = [640.0, 480.0];
imgui.fonts().build_alpha8_texture();
log::info!("Using Sokol backend: {:?}", sg_query_backend());
Ok(Box::new(SokolRenderer(RefCell::new(imgui))))
}
}
pub struct NullTexture(u16, u16);
impl BackendTexture for NullTexture {
fn dimensions(&self) -> (u16, u16) {
(self.0, self.1)
}
fn add(&mut self, command: SpriteBatchCommand) {}
fn clear(&mut self) {}
fn draw(&mut self) -> GameResult<()> {
Ok(())
}
}
pub struct SokolRenderer(RefCell<imgui::Context>);
impl BackendRenderer for SokolRenderer {
fn clear(&mut self, color: Color) {}
fn present(&mut self) -> GameResult {
Ok(())
}
fn create_texture_mutable(&mut self, width: u16, height: u16) -> GameResult<Box<dyn BackendTexture>> {
Ok(Box::new(NullTexture(width, height)))
}
fn create_texture(&mut self, width: u16, height: u16, data: &[u8]) -> GameResult<Box<dyn BackendTexture>> {
Ok(Box::new(NullTexture(width, height)))
}
fn set_blend_mode(&mut self, blend: BlendMode) -> GameResult {
Ok(())
}
fn set_render_target(&mut self, texture: Option<&Box<dyn BackendTexture>>) -> GameResult {
Ok(())
}
fn draw_rect(&mut self, rect: Rect<isize>, color: Color) -> GameResult {
Ok(())
}
fn draw_outline_rect(&mut self, rect: Rect<isize>, line_width: usize, color: Color) -> GameResult {
Ok(())
}
fn imgui(&self) -> GameResult<&mut imgui::Context> {
unsafe { Ok(&mut *self.0.as_ptr()) }
}
fn render_imgui(&mut self, draw_data: &DrawData) -> GameResult {
Ok(())
}
fn prepare_frame<'ui>(&self, ui: &Ui<'ui>) -> GameResult {
Ok(())
}
}
fn conv_scancode(code: SAppKeycode) -> Option<ScanCode> {
match code {
SAppKeycode::KeySpace => Some(ScanCode::Space),
SAppKeycode::KeyApostrophe => Some(ScanCode::Apostrophe),
SAppKeycode::KeyComma => Some(ScanCode::Comma),
SAppKeycode::KeyMinus => Some(ScanCode::Minus),
SAppKeycode::KeyPeriod => Some(ScanCode::Period),
SAppKeycode::KeySlash => Some(ScanCode::Slash),
SAppKeycode::Key0 => Some(ScanCode::Key0),
SAppKeycode::Key1 => Some(ScanCode::Key1),
SAppKeycode::Key2 => Some(ScanCode::Key2),
SAppKeycode::Key3 => Some(ScanCode::Key3),
SAppKeycode::Key4 => Some(ScanCode::Key4),
SAppKeycode::Key5 => Some(ScanCode::Key5),
SAppKeycode::Key6 => Some(ScanCode::Key6),
SAppKeycode::Key7 => Some(ScanCode::Key7),
SAppKeycode::Key8 => Some(ScanCode::Key8),
SAppKeycode::Key9 => Some(ScanCode::Key9),
SAppKeycode::KeySemicolon => Some(ScanCode::Semicolon),
SAppKeycode::KeyEqual => Some(ScanCode::Equals),
SAppKeycode::KeyA => Some(ScanCode::A),
SAppKeycode::KeyB => Some(ScanCode::B),
SAppKeycode::KeyC => Some(ScanCode::C),
SAppKeycode::KeyD => Some(ScanCode::D),
SAppKeycode::KeyE => Some(ScanCode::E),
SAppKeycode::KeyF => Some(ScanCode::F),
SAppKeycode::KeyG => Some(ScanCode::G),
SAppKeycode::KeyH => Some(ScanCode::H),
SAppKeycode::KeyI => Some(ScanCode::I),
SAppKeycode::KeyJ => Some(ScanCode::J),
SAppKeycode::KeyK => Some(ScanCode::K),
SAppKeycode::KeyL => Some(ScanCode::L),
SAppKeycode::KeyM => Some(ScanCode::M),
SAppKeycode::KeyN => Some(ScanCode::N),
SAppKeycode::KeyO => Some(ScanCode::O),
SAppKeycode::KeyP => Some(ScanCode::P),
SAppKeycode::KeyQ => Some(ScanCode::Q),
SAppKeycode::KeyR => Some(ScanCode::R),
SAppKeycode::KeyS => Some(ScanCode::S),
SAppKeycode::KeyT => Some(ScanCode::T),
SAppKeycode::KeyU => Some(ScanCode::U),
SAppKeycode::KeyV => Some(ScanCode::V),
SAppKeycode::KeyW => Some(ScanCode::W),
SAppKeycode::KeyX => Some(ScanCode::X),
SAppKeycode::KeyY => Some(ScanCode::Y),
SAppKeycode::KeyZ => Some(ScanCode::Z),
SAppKeycode::KeyLeftBracket => Some(ScanCode::LBracket),
SAppKeycode::KeyBackslash => Some(ScanCode::Backslash),
SAppKeycode::KeyRightBracket => Some(ScanCode::RBracket),
SAppKeycode::KeyGraveAccent => Some(ScanCode::Grave),
SAppKeycode::KeyWorld1 => Some(ScanCode::AbntC1),
SAppKeycode::KeyWorld2 => Some(ScanCode::AbntC2),
SAppKeycode::KeyEscape => Some(ScanCode::Escape),
SAppKeycode::KeyEnter => Some(ScanCode::Return),
SAppKeycode::KeyTab => Some(ScanCode::Tab),
SAppKeycode::KeyBackspace => Some(ScanCode::Backspace),
SAppKeycode::KeyInsert => Some(ScanCode::Insert),
SAppKeycode::KeyDelete => Some(ScanCode::Delete),
SAppKeycode::KeyRight => Some(ScanCode::Right),
SAppKeycode::KeyLeft => Some(ScanCode::Left),
SAppKeycode::KeyDown => Some(ScanCode::Down),
SAppKeycode::KeyUp => Some(ScanCode::Up),
SAppKeycode::KeyPageUp => Some(ScanCode::PageUp),
SAppKeycode::KeyPageDown => Some(ScanCode::PageDown),
SAppKeycode::KeyHome => Some(ScanCode::Home),
SAppKeycode::KeyEnd => Some(ScanCode::End),
SAppKeycode::KeyCapsLock => Some(ScanCode::Capslock),
SAppKeycode::KeyScrollLock => Some(ScanCode::Scrolllock),
SAppKeycode::KeyNumLock => Some(ScanCode::Numlock),
SAppKeycode::KeyPrintScreen => Some(ScanCode::Sysrq),
SAppKeycode::KeyPause => Some(ScanCode::Pause),
SAppKeycode::KeyF1 => Some(ScanCode::F1),
SAppKeycode::KeyF2 => Some(ScanCode::F2),
SAppKeycode::KeyF3 => Some(ScanCode::F3),
SAppKeycode::KeyF4 => Some(ScanCode::F4),
SAppKeycode::KeyF5 => Some(ScanCode::F5),
SAppKeycode::KeyF6 => Some(ScanCode::F6),
SAppKeycode::KeyF7 => Some(ScanCode::F7),
SAppKeycode::KeyF8 => Some(ScanCode::F8),
SAppKeycode::KeyF9 => Some(ScanCode::F9),
SAppKeycode::KeyF10 => Some(ScanCode::F10),
SAppKeycode::KeyF11 => Some(ScanCode::F11),
SAppKeycode::KeyF12 => Some(ScanCode::F12),
SAppKeycode::KeyF13 => Some(ScanCode::F13),
SAppKeycode::KeyF14 => Some(ScanCode::F14),
SAppKeycode::KeyF15 => Some(ScanCode::F15),
SAppKeycode::KeyF16 => Some(ScanCode::F16),
SAppKeycode::KeyF17 => Some(ScanCode::F17),
SAppKeycode::KeyF18 => Some(ScanCode::F18),
SAppKeycode::KeyF19 => Some(ScanCode::F19),
SAppKeycode::KeyF20 => Some(ScanCode::F20),
SAppKeycode::KeyF21 => Some(ScanCode::F21),
SAppKeycode::KeyF22 => Some(ScanCode::F22),
SAppKeycode::KeyF23 => Some(ScanCode::F23),
SAppKeycode::KeyF24 => Some(ScanCode::F24),
SAppKeycode::KeyKP0 => Some(ScanCode::Numpad0),
SAppKeycode::KeyKP1 => Some(ScanCode::Numpad1),
SAppKeycode::KeyKP2 => Some(ScanCode::Numpad2),
SAppKeycode::KeyKP3 => Some(ScanCode::Numpad3),
SAppKeycode::KeyKP4 => Some(ScanCode::Numpad4),
SAppKeycode::KeyKP5 => Some(ScanCode::Numpad5),
SAppKeycode::KeyKP6 => Some(ScanCode::Numpad6),
SAppKeycode::KeyKP7 => Some(ScanCode::Numpad7),
SAppKeycode::KeyKP8 => Some(ScanCode::Numpad8),
SAppKeycode::KeyKP9 => Some(ScanCode::Numpad9),
SAppKeycode::KeyKPDecimal => Some(ScanCode::NumpadDecimal),
SAppKeycode::KeyKPDivide => Some(ScanCode::NumpadDivide),
SAppKeycode::KeyKPMultiply => Some(ScanCode::NumpadMultiply),
SAppKeycode::KeyKPSubtract => Some(ScanCode::NumpadSubtract),
SAppKeycode::KeyKPAdd => Some(ScanCode::NumpadAdd),
SAppKeycode::KeyKPEnter => Some(ScanCode::NumpadEnter),
SAppKeycode::KeyKPEqual => Some(ScanCode::NumpadEquals),
SAppKeycode::KeyLeftShift => Some(ScanCode::LShift),
SAppKeycode::KeyLeftControl => Some(ScanCode::LControl),
SAppKeycode::KeyLeftAlt => Some(ScanCode::LAlt),
SAppKeycode::KeyLeftSuper => Some(ScanCode::LWin),
SAppKeycode::KeyRightShift => Some(ScanCode::RShift),
SAppKeycode::KeyRightControl => Some(ScanCode::RControl),
SAppKeycode::KeyRightAlt => Some(ScanCode::RAlt),
SAppKeycode::KeyRightSuper => Some(ScanCode::RWin),
SAppKeycode::KeyMenu => Some(ScanCode::Menu),
_ => None,
}
}

View File

@ -1,8 +1,8 @@
pub mod backend;
pub mod backend_null;
#[cfg(feature = "backend-glutin")]
pub mod backend_opengl;
#[cfg(feature = "backend-glutin")]
pub mod backend_glutin;
#[cfg(feature = "render-opengl")]
mod gl;
#[cfg(feature = "backend-sdl")]
pub mod backend_sdl2;
@ -13,5 +13,7 @@ pub mod error;
pub mod filesystem;
pub mod graphics;
pub mod keyboard;
#[cfg(feature = "render-opengl")]
pub mod render_opengl;
pub mod ui;
pub mod vfs;

View File

@ -1,325 +1,27 @@
use std::cell::{RefCell, UnsafeCell};
use std::ffi::CStr;
use std::ffi::{c_void, CStr};
use std::mem;
use std::rc::Rc;
use std::sync::Arc;
use glutin::event::{Event, TouchPhase, WindowEvent, ElementState, VirtualKeyCode};
use glutin::event_loop::{ControlFlow, EventLoop};
use glutin::window::WindowBuilder;
use glutin::{Api, ContextBuilder, GlProfile, GlRequest, PossiblyCurrent, WindowedContext};
use imgui::{DrawCmd, DrawCmdParams, DrawData, DrawIdx, DrawVert};
use gl::types::*;
use crate::common::{Color, Rect};
use crate::framework::backend::{Backend, BackendEventLoop, BackendRenderer, BackendTexture, SpriteBatchCommand};
use crate::framework::context::Context;
use crate::framework::backend::{BackendRenderer, BackendTexture, SpriteBatchCommand};
use crate::framework::error::GameError::RenderError;
use crate::framework::error::GameResult;
use crate::framework::gl;
use crate::framework::gl::types::*;
use crate::framework::graphics::BlendMode;
use crate::input::touch_controls::TouchPoint;
use crate::{Game, GAME_SUSPENDED};
use crate::framework::keyboard::ScanCode;
use std::mem::MaybeUninit;
pub struct GlutinBackend;
impl GlutinBackend {
pub fn new() -> GameResult<Box<dyn Backend>> {
Ok(Box::new(GlutinBackend))
}
pub struct GLContext {
pub get_proc_address: unsafe fn(user_data: &mut *mut c_void, name: &str) -> *const c_void,
pub swap_buffers: unsafe fn(user_data: &mut *mut c_void),
pub user_data: *mut c_void,
}
impl Backend for GlutinBackend {
fn create_event_loop(&self) -> GameResult<Box<dyn BackendEventLoop>> {
#[cfg(target_os = "android")]
loop {
match ndk_glue::native_window().as_ref() {
Some(_) => {
log::info!("NativeWindow Found: {:?}", ndk_glue::native_window());
break;
}
None => (),
}
}
Ok(Box::new(GlutinEventLoop { refs: Rc::new(UnsafeCell::new(None)) }))
}
}
pub struct GlutinEventLoop {
refs: Rc<UnsafeCell<Option<WindowedContext<PossiblyCurrent>>>>,
}
impl GlutinEventLoop {
fn get_context(&self, event_loop: &EventLoop<()>) -> &mut WindowedContext<PossiblyCurrent> {
let mut refs = unsafe { &mut *self.refs.get() };
if refs.is_none() {
let mut window = WindowBuilder::new();
let windowed_context = ContextBuilder::new()
.with_gl(GlRequest::Specific(Api::OpenGlEs, (2, 0)))
.with_gl_profile(GlProfile::Core)
.with_gl_debug_flag(false)
.with_pixel_format(24, 8)
.with_vsync(true);
#[cfg(target_os = "windows")]
{
use glutin::platform::windows::WindowBuilderExtWindows;
window = window.with_drag_and_drop(false);
}
window = window.with_title("doukutsu-rs");
let windowed_context = windowed_context.build_windowed(window, event_loop)
.unwrap();
let windowed_context = unsafe { windowed_context.make_current().unwrap() };
#[cfg(target_os = "android")]
if let Some(nwin) = ndk_glue::native_window().as_ref() {
unsafe {
windowed_context.surface_created(nwin.ptr().as_ptr() as *mut std::ffi::c_void);
}
}
refs.replace(windowed_context);
}
refs.as_mut().unwrap()
}
}
#[cfg(target_os = "android")]
fn request_android_redraw() {
match ndk_glue::native_window().as_ref() {
Some(native_window) => {
let a_native_window: *mut ndk_sys::ANativeWindow = native_window.ptr().as_ptr();
let a_native_activity: *mut ndk_sys::ANativeActivity = ndk_glue::native_activity().ptr().as_ptr();
unsafe {
match (*(*a_native_activity).callbacks).onNativeWindowRedrawNeeded {
Some(callback) => callback(a_native_activity, a_native_window),
None => (),
};
};
}
None => (),
}
}
#[cfg(target_os = "android")]
fn get_insets() -> GameResult<(f32, f32, f32, f32)> {
unsafe {
let vm_ptr = ndk_glue::native_activity().vm();
let vm = unsafe { jni::JavaVM::from_raw(vm_ptr) }?;
let vm_env = vm.attach_current_thread()?;
//let class = vm_env.find_class("io/github/doukutsu_rs/MainActivity")?;
let class = vm_env.new_global_ref(ndk_glue::native_activity().activity())?;
let field = vm_env.get_field(class.as_obj(), "displayInsets", "[I")?.to_jni().l as jni::sys::jintArray;
let mut elements = [0; 4];
vm_env.get_int_array_region(field, 0, &mut elements)?;
Ok((elements[0] as f32, elements[1] as f32, elements[2] as f32, elements[3] as f32))
}
}
impl BackendEventLoop for GlutinEventLoop {
fn run(&mut self, game: &mut Game, ctx: &mut Context) {
let event_loop = EventLoop::new();
let state_ref = unsafe { &mut *game.state.get() };
let window: &'static mut WindowedContext<PossiblyCurrent> =
unsafe { std::mem::transmute(self.get_context(&event_loop)) };
{
let size = window.window().inner_size();
ctx.screen_size = (size.width.max(1) as f32, size.height.max(1) as f32);
state_ref.handle_resize(ctx).unwrap();
}
// it won't ever return
let (game, ctx): (&'static mut Game, &'static mut Context) =
unsafe { (std::mem::transmute(game), std::mem::transmute(ctx)) };
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::WindowEvent { event: WindowEvent::CloseRequested, window_id }
if window_id == window.window().id() =>
{
state_ref.shutdown();
}
Event::Resumed => {
println!("resumed!");
{
let mut mutex = GAME_SUSPENDED.lock().unwrap();
*mutex = false;
}
#[cfg(target_os = "android")]
if let Some(nwin) = ndk_glue::native_window().as_ref() {
state_ref.graphics_reset();
unsafe {
window.surface_created(nwin.ptr().as_ptr() as *mut std::ffi::c_void);
request_android_redraw();
}
}
}
Event::Suspended => {
println!("suspended!");
{
let mut mutex = GAME_SUSPENDED.lock().unwrap();
*mutex = true;
}
#[cfg(target_os = "android")]
unsafe {
window.surface_destroyed();
}
}
Event::WindowEvent { event: WindowEvent::Resized(size), window_id }
if window_id == window.window().id() =>
{
if let Some(renderer) = ctx.renderer.as_ref() {
if let Ok(imgui) = renderer.imgui() {
imgui.io_mut().display_size = [size.width as f32, size.height as f32];
}
ctx.screen_size = (size.width as f32, size.height as f32);
state_ref.handle_resize(ctx).unwrap();
}
}
Event::WindowEvent { event: WindowEvent::Touch(touch), window_id }
if window_id == window.window().id() =>
{
let mut controls = &mut state_ref.touch_controls;
let scale = state_ref.scale as f64;
match touch.phase {
TouchPhase::Started | TouchPhase::Moved => {
if let Some(point) = controls.points.iter_mut().find(|p| p.id == touch.id) {
point.last_position = point.position;
point.position = (touch.location.x / scale, touch.location.y / scale);
} else {
controls.touch_id_counter = controls.touch_id_counter.wrapping_add(1);
let point = TouchPoint {
id: touch.id,
touch_id: controls.touch_id_counter,
position: (touch.location.x / scale, touch.location.y / scale),
last_position: (0.0, 0.0),
};
controls.points.push(point);
if touch.phase == TouchPhase::Started {
controls.clicks.push(point);
}
}
}
TouchPhase::Ended | TouchPhase::Cancelled => {
controls.points.retain(|p| p.id != touch.id);
controls.clicks.retain(|p| p.id != touch.id);
}
}
}
Event::WindowEvent { event: WindowEvent::KeyboardInput { input, .. }, window_id }
if window_id == window.window().id() => {
if let Some(keycode) = input.virtual_keycode {
if let Some(drs_scan) = conv_keycode(keycode) {
let key_state = match input.state {
ElementState::Pressed => true,
ElementState::Released => false,
};
ctx.keyboard_context.set_key(drs_scan, key_state);
}
}
}
Event::RedrawRequested(id) if id == window.window().id() => {
{
let mutex = GAME_SUSPENDED.lock().unwrap();
if *mutex {
return;
}
}
#[cfg(not(target_os = "android"))]
{
if let Err(err) = game.draw(ctx) {
log::error!("Failed to draw frame: {}", err);
}
window.window().request_redraw();
}
#[cfg(target_os = "android")]
request_android_redraw();
}
Event::MainEventsCleared => {
if state_ref.shutdown {
log::info!("Shutting down...");
*control_flow = ControlFlow::Exit;
return;
}
{
let mutex = GAME_SUSPENDED.lock().unwrap();
if *mutex {
return;
}
}
game.update(ctx).unwrap();
#[cfg(target_os = "android")]
{
match get_insets() {
Ok(insets) => {
ctx.screen_insets = insets;
}
Err(e) => {
log::error!("Failed to update insets: {}", e);
}
}
if let Err(err) = game.draw(ctx) {
log::error!("Failed to draw frame: {}", err);
}
}
if state_ref.next_scene.is_some() {
mem::swap(&mut game.scene, &mut state_ref.next_scene);
state_ref.next_scene = None;
game.scene.as_mut().unwrap().init(state_ref, ctx).unwrap();
game.loops = 0;
state_ref.frame_time = 0.0;
}
}
_ => (),
}
});
}
fn new_renderer(&self) -> GameResult<Box<dyn BackendRenderer>> {
let mut imgui = imgui::Context::create();
imgui.io_mut().display_size = [640.0, 480.0];
Ok(Box::new(GlutinRenderer {
refs: self.refs.clone(),
imgui: UnsafeCell::new(imgui),
imgui_data: ImguiData::new(),
context_active: Arc::new(RefCell::new(true)),
def_matrix: [[0.0; 4]; 4],
}))
}
}
pub struct GlutinTexture {
pub struct OpenGLTexture {
width: u16,
height: u16,
texture_id: u32,
@ -338,7 +40,7 @@ struct VertexData {
color: (u8, u8, u8, u8),
}
impl BackendTexture for GlutinTexture {
impl BackendTexture for OpenGLTexture {
fn dimensions(&self) -> (u16, u16) {
(self.width, self.height)
}
@ -537,7 +239,7 @@ impl BackendTexture for GlutinTexture {
}
}
impl Drop for GlutinTexture {
impl Drop for OpenGLTexture {
fn drop(&mut self) {
if *self.context_active.as_ref().borrow() {
unsafe {
@ -582,7 +284,7 @@ fn check_shader_compile_status(shader: u32, gl: &Gl) -> bool {
true
}
const IMGUI_SHADER_VERT: &str = r"
const VERTEX_SHADER_BASIC: &str = r"
#version 100
precision mediump float;
@ -603,7 +305,7 @@ void main()
";
const IMGUI_SHADER_FRAG: &str = r"
const FRAGMENT_SHADER_TEXTURED: &str = r"
#version 100
precision mediump float;
@ -619,6 +321,21 @@ void main()
";
const FRAGMENT_SHADER_COLOR: &str = r"
#version 100
precision mediump float;
varying vec2 Frag_UV;
varying vec4 Frag_Color;
void main()
{
gl_FragColor = Frag_Color;
}
";
#[derive(Copy, Clone)]
struct Locs {
texture: GLint,
@ -654,10 +371,10 @@ impl ImguiData {
fn init(&mut self, imgui: &mut imgui::Context, gl: &Gl) {
self.initialized = true;
let vert_sources = [IMGUI_SHADER_VERT.as_ptr() as *const GLchar];
let frag_sources = [IMGUI_SHADER_FRAG.as_ptr() as *const GLchar];
let vert_sources_len = [IMGUI_SHADER_VERT.len() as GLint - 1];
let frag_sources_len = [IMGUI_SHADER_FRAG.len() as GLint - 1];
let vert_sources = [VERTEX_SHADER_BASIC.as_ptr() as *const GLchar];
let frag_sources = [FRAGMENT_SHADER_TEXTURED.as_ptr() as *const GLchar];
let vert_sources_len = [VERTEX_SHADER_BASIC.len() as GLint - 1];
let frag_sources_len = [FRAGMENT_SHADER_TEXTURED.len() as GLint - 1];
unsafe {
self.program = gl.gl.CreateProgram();
@ -726,31 +443,28 @@ impl ImguiData {
}
}
pub struct GlutinRenderer {
refs: Rc<UnsafeCell<Option<WindowedContext<PossiblyCurrent>>>>,
imgui: UnsafeCell<imgui::Context>,
imgui_data: ImguiData,
context_active: Arc<RefCell<bool>>,
def_matrix: [[f32; 4]; 4],
}
pub struct Gl {
pub gl: gl::Gles2,
}
static mut GL_PROC: Option<Gl> = None;
pub fn load_gl(gl_context: &glutin::Context<PossiblyCurrent>) -> &'static Gl {
pub fn load_gl(gl_context: &mut GLContext) -> &'static Gl {
unsafe {
if let Some(gl) = GL_PROC.as_ref() {
return gl;
}
let gl = gl::Gles2::load_with(|ptr| gl_context.get_proc_address(ptr) as *const _);
let gl = gl::Gles2::load_with(|ptr| (gl_context.get_proc_address)(&mut gl_context.user_data, ptr));
let version = unsafe {
let data = CStr::from_ptr(gl.GetString(gl::VERSION) as *const _).to_bytes().to_vec();
String::from_utf8(data).unwrap()
let p = gl.GetString(gl::VERSION);
if p.is_null() {
"unknown".to_owned()
} else {
let data = CStr::from_ptr(p as *const _).to_bytes().to_vec();
String::from_utf8(data).unwrap()
}
};
log::info!("OpenGL version {}", version);
@ -760,25 +474,41 @@ pub fn load_gl(gl_context: &glutin::Context<PossiblyCurrent>) -> &'static Gl {
}
}
impl GlutinRenderer {
fn get_context(&mut self) -> Option<(&mut WindowedContext<PossiblyCurrent>, &'static Gl)> {
let (refs, imgui) = unsafe { ((&mut *self.refs.get()).as_mut(), &mut *self.imgui.get()) };
pub struct OpenGLRenderer {
refs: GLContext,
imgui: UnsafeCell<imgui::Context>,
imgui_data: ImguiData,
context_active: Arc<RefCell<bool>>,
def_matrix: [[f32; 4]; 4],
}
refs.map(|context| {
let gl = load_gl(context);
impl OpenGLRenderer {
pub fn new(refs: GLContext, imgui: UnsafeCell<imgui::Context>) -> OpenGLRenderer {
OpenGLRenderer {
refs,
imgui,
imgui_data: ImguiData::new(),
context_active: Arc::new(RefCell::new(true)),
def_matrix: [[0.0; 4]; 4],
}
}
if !self.imgui_data.initialized {
self.imgui_data.init(imgui, gl);
}
fn get_context(&mut self) -> Option<(&mut GLContext, &'static Gl)> {
let imgui = unsafe { &mut *self.imgui.get() };
(context, gl)
})
let gl = load_gl(&mut self.refs);
if !self.imgui_data.initialized {
self.imgui_data.init(imgui, gl);
}
Some((&mut self.refs, gl))
}
}
fn field_offset<T, U, F: for<'a> FnOnce(&'a T) -> &'a U>(f: F) -> usize {
unsafe {
let instance = mem::zeroed::<T>();
let instance = MaybeUninit::uninit().assume_init();
let offset = {
let field: &U = f(&instance);
@ -800,7 +530,11 @@ where
val
}
impl BackendRenderer for GlutinRenderer {
impl BackendRenderer for OpenGLRenderer {
fn renderer_name(&self) -> String {
"OpenGL(ES)".to_owned()
}
fn clear(&mut self, color: Color) {
if let Some((_, gl)) = self.get_context() {
unsafe {
@ -812,7 +546,7 @@ impl BackendRenderer for GlutinRenderer {
fn present(&mut self) -> GameResult {
{
let mutex = GAME_SUSPENDED.lock().unwrap();
let mutex = crate::GAME_SUSPENDED.lock().unwrap();
if *mutex {
return Ok(());
}
@ -821,9 +555,9 @@ impl BackendRenderer for GlutinRenderer {
if let Some((context, gl)) = self.get_context() {
unsafe {
gl.gl.Finish();
}
context.swap_buffers().map_err(|e| RenderError(e.to_string()))?;
(context.swap_buffers)(&mut context.user_data);
}
}
Ok(())
@ -895,7 +629,7 @@ impl BackendRenderer for GlutinRenderer {
// todo error checking: glCheckFramebufferStatus()
Ok(Box::new(GlutinTexture {
Ok(Box::new(OpenGLTexture {
texture_id,
framebuffer_id,
width,
@ -934,7 +668,7 @@ impl BackendRenderer for GlutinRenderer {
gl.gl.BindTexture(gl::TEXTURE_2D, current_texture_id);
Ok(Box::new(GlutinTexture {
Ok(Box::new(OpenGLTexture {
texture_id,
framebuffer_id: 0,
width,
@ -977,7 +711,7 @@ impl BackendRenderer for GlutinRenderer {
if let Some((_, gl)) = self.get_context() {
unsafe {
if let Some(texture) = texture {
let gl_texture: &Box<GlutinTexture> = std::mem::transmute(texture);
let gl_texture: &Box<OpenGLTexture> = std::mem::transmute(texture);
let matrix = [
[2.0 / (gl_texture.width as f32), 0.0, 0.0, 0.0],
@ -1201,6 +935,7 @@ impl BackendRenderer for GlutinRenderer {
}
}
gl.gl.Disable(gl::SCISSOR_TEST);
//gl.gl.DeleteVertexArrays(1, &vao);
}
}
@ -1209,176 +944,8 @@ impl BackendRenderer for GlutinRenderer {
}
}
impl Drop for GlutinRenderer {
impl Drop for OpenGLRenderer {
fn drop(&mut self) {
*self.context_active.as_ref().borrow_mut() = false;
}
}
fn conv_keycode(code: VirtualKeyCode) -> Option<ScanCode> {
match code {
VirtualKeyCode::Key1 => Some(ScanCode::Key1),
VirtualKeyCode::Key2 => Some(ScanCode::Key2),
VirtualKeyCode::Key3 => Some(ScanCode::Key3),
VirtualKeyCode::Key4 => Some(ScanCode::Key4),
VirtualKeyCode::Key5 => Some(ScanCode::Key5),
VirtualKeyCode::Key6 => Some(ScanCode::Key6),
VirtualKeyCode::Key7 => Some(ScanCode::Key7),
VirtualKeyCode::Key8 => Some(ScanCode::Key8),
VirtualKeyCode::Key9 => Some(ScanCode::Key9),
VirtualKeyCode::Key0 => Some(ScanCode::Key0),
VirtualKeyCode::A => Some(ScanCode::A),
VirtualKeyCode::B => Some(ScanCode::B),
VirtualKeyCode::C => Some(ScanCode::C),
VirtualKeyCode::D => Some(ScanCode::D),
VirtualKeyCode::E => Some(ScanCode::E),
VirtualKeyCode::F => Some(ScanCode::F),
VirtualKeyCode::G => Some(ScanCode::G),
VirtualKeyCode::H => Some(ScanCode::H),
VirtualKeyCode::I => Some(ScanCode::I),
VirtualKeyCode::J => Some(ScanCode::J),
VirtualKeyCode::K => Some(ScanCode::K),
VirtualKeyCode::L => Some(ScanCode::L),
VirtualKeyCode::M => Some(ScanCode::M),
VirtualKeyCode::N => Some(ScanCode::N),
VirtualKeyCode::O => Some(ScanCode::O),
VirtualKeyCode::P => Some(ScanCode::P),
VirtualKeyCode::Q => Some(ScanCode::Q),
VirtualKeyCode::R => Some(ScanCode::R),
VirtualKeyCode::S => Some(ScanCode::S),
VirtualKeyCode::T => Some(ScanCode::T),
VirtualKeyCode::U => Some(ScanCode::U),
VirtualKeyCode::V => Some(ScanCode::V),
VirtualKeyCode::W => Some(ScanCode::W),
VirtualKeyCode::X => Some(ScanCode::X),
VirtualKeyCode::Y => Some(ScanCode::Y),
VirtualKeyCode::Z => Some(ScanCode::Z),
VirtualKeyCode::Escape => Some(ScanCode::Escape),
VirtualKeyCode::F1 => Some(ScanCode::F1),
VirtualKeyCode::F2 => Some(ScanCode::F2),
VirtualKeyCode::F3 => Some(ScanCode::F3),
VirtualKeyCode::F4 => Some(ScanCode::F4),
VirtualKeyCode::F5 => Some(ScanCode::F5),
VirtualKeyCode::F6 => Some(ScanCode::F6),
VirtualKeyCode::F7 => Some(ScanCode::F7),
VirtualKeyCode::F8 => Some(ScanCode::F8),
VirtualKeyCode::F9 => Some(ScanCode::F9),
VirtualKeyCode::F10 => Some(ScanCode::F10),
VirtualKeyCode::F11 => Some(ScanCode::F11),
VirtualKeyCode::F12 => Some(ScanCode::F12),
VirtualKeyCode::F13 => Some(ScanCode::F13),
VirtualKeyCode::F14 => Some(ScanCode::F14),
VirtualKeyCode::F15 => Some(ScanCode::F15),
VirtualKeyCode::F16 => Some(ScanCode::F16),
VirtualKeyCode::F17 => Some(ScanCode::F17),
VirtualKeyCode::F18 => Some(ScanCode::F18),
VirtualKeyCode::F19 => Some(ScanCode::F19),
VirtualKeyCode::F20 => Some(ScanCode::F20),
VirtualKeyCode::F21 => Some(ScanCode::F21),
VirtualKeyCode::F22 => Some(ScanCode::F22),
VirtualKeyCode::F23 => Some(ScanCode::F23),
VirtualKeyCode::F24 => Some(ScanCode::F24),
VirtualKeyCode::Snapshot => Some(ScanCode::Snapshot),
VirtualKeyCode::Scroll => Some(ScanCode::Scroll),
VirtualKeyCode::Pause => Some(ScanCode::Pause),
VirtualKeyCode::Insert => Some(ScanCode::Insert),
VirtualKeyCode::Home => Some(ScanCode::Home),
VirtualKeyCode::Delete => Some(ScanCode::Delete),
VirtualKeyCode::End => Some(ScanCode::End),
VirtualKeyCode::PageDown => Some(ScanCode::PageDown),
VirtualKeyCode::PageUp => Some(ScanCode::PageUp),
VirtualKeyCode::Left => Some(ScanCode::Left),
VirtualKeyCode::Up => Some(ScanCode::Up),
VirtualKeyCode::Right => Some(ScanCode::Right),
VirtualKeyCode::Down => Some(ScanCode::Down),
VirtualKeyCode::Back => Some(ScanCode::Back),
VirtualKeyCode::Return => Some(ScanCode::Return),
VirtualKeyCode::Space => Some(ScanCode::Space),
VirtualKeyCode::Compose => Some(ScanCode::Compose),
VirtualKeyCode::Caret => Some(ScanCode::Caret),
VirtualKeyCode::Numlock => Some(ScanCode::Numlock),
VirtualKeyCode::Numpad0 => Some(ScanCode::Numpad0),
VirtualKeyCode::Numpad1 => Some(ScanCode::Numpad1),
VirtualKeyCode::Numpad2 => Some(ScanCode::Numpad2),
VirtualKeyCode::Numpad3 => Some(ScanCode::Numpad3),
VirtualKeyCode::Numpad4 => Some(ScanCode::Numpad4),
VirtualKeyCode::Numpad5 => Some(ScanCode::Numpad5),
VirtualKeyCode::Numpad6 => Some(ScanCode::Numpad6),
VirtualKeyCode::Numpad7 => Some(ScanCode::Numpad7),
VirtualKeyCode::Numpad8 => Some(ScanCode::Numpad8),
VirtualKeyCode::Numpad9 => Some(ScanCode::Numpad9),
VirtualKeyCode::NumpadAdd => Some(ScanCode::NumpadAdd),
VirtualKeyCode::NumpadDivide => Some(ScanCode::NumpadDivide),
VirtualKeyCode::NumpadDecimal => Some(ScanCode::NumpadDecimal),
VirtualKeyCode::NumpadComma => Some(ScanCode::NumpadComma),
VirtualKeyCode::NumpadEnter => Some(ScanCode::NumpadEnter),
VirtualKeyCode::NumpadEquals => Some(ScanCode::NumpadEquals),
VirtualKeyCode::NumpadMultiply => Some(ScanCode::NumpadMultiply),
VirtualKeyCode::NumpadSubtract => Some(ScanCode::NumpadSubtract),
VirtualKeyCode::AbntC1 => Some(ScanCode::AbntC1),
VirtualKeyCode::AbntC2 => Some(ScanCode::AbntC2),
VirtualKeyCode::Apostrophe => Some(ScanCode::Apostrophe),
VirtualKeyCode::Apps => Some(ScanCode::Apps),
VirtualKeyCode::Asterisk => Some(ScanCode::Asterisk),
VirtualKeyCode::At => Some(ScanCode::At),
VirtualKeyCode::Ax => Some(ScanCode::Ax),
VirtualKeyCode::Backslash => Some(ScanCode::Backslash),
VirtualKeyCode::Calculator => Some(ScanCode::Calculator),
VirtualKeyCode::Capital => Some(ScanCode::Capital),
VirtualKeyCode::Colon => Some(ScanCode::Colon),
VirtualKeyCode::Comma => Some(ScanCode::Comma),
VirtualKeyCode::Convert => Some(ScanCode::Convert),
VirtualKeyCode::Equals => Some(ScanCode::Equals),
VirtualKeyCode::Grave => Some(ScanCode::Grave),
VirtualKeyCode::Kana => Some(ScanCode::Kana),
VirtualKeyCode::Kanji => Some(ScanCode::Kanji),
VirtualKeyCode::LAlt => Some(ScanCode::LAlt),
VirtualKeyCode::LBracket => Some(ScanCode::LBracket),
VirtualKeyCode::LControl => Some(ScanCode::LControl),
VirtualKeyCode::LShift => Some(ScanCode::LShift),
VirtualKeyCode::LWin => Some(ScanCode::LWin),
VirtualKeyCode::Mail => Some(ScanCode::Mail),
VirtualKeyCode::MediaSelect => Some(ScanCode::MediaSelect),
VirtualKeyCode::MediaStop => Some(ScanCode::MediaStop),
VirtualKeyCode::Minus => Some(ScanCode::Minus),
VirtualKeyCode::Mute => Some(ScanCode::Mute),
VirtualKeyCode::MyComputer => Some(ScanCode::MyComputer),
VirtualKeyCode::NavigateForward => Some(ScanCode::NavigateForward),
VirtualKeyCode::NavigateBackward => Some(ScanCode::NavigateBackward),
VirtualKeyCode::NextTrack => Some(ScanCode::NextTrack),
VirtualKeyCode::NoConvert => Some(ScanCode::NoConvert),
VirtualKeyCode::OEM102 => Some(ScanCode::OEM102),
VirtualKeyCode::Period => Some(ScanCode::Period),
VirtualKeyCode::PlayPause => Some(ScanCode::PlayPause),
VirtualKeyCode::Plus => Some(ScanCode::Plus),
VirtualKeyCode::Power => Some(ScanCode::Power),
VirtualKeyCode::PrevTrack => Some(ScanCode::PrevTrack),
VirtualKeyCode::RAlt => Some(ScanCode::RAlt),
VirtualKeyCode::RBracket => Some(ScanCode::RBracket),
VirtualKeyCode::RControl => Some(ScanCode::RControl),
VirtualKeyCode::RShift => Some(ScanCode::RShift),
VirtualKeyCode::RWin => Some(ScanCode::RWin),
VirtualKeyCode::Semicolon => Some(ScanCode::Semicolon),
VirtualKeyCode::Slash => Some(ScanCode::Slash),
VirtualKeyCode::Sleep => Some(ScanCode::Sleep),
VirtualKeyCode::Stop => Some(ScanCode::Stop),
VirtualKeyCode::Sysrq => Some(ScanCode::Sysrq),
VirtualKeyCode::Tab => Some(ScanCode::Tab),
VirtualKeyCode::Underline => Some(ScanCode::Underline),
VirtualKeyCode::Unlabeled => Some(ScanCode::Unlabeled),
VirtualKeyCode::VolumeDown => Some(ScanCode::VolumeDown),
VirtualKeyCode::VolumeUp => Some(ScanCode::VolumeUp),
VirtualKeyCode::Wake => Some(ScanCode::Wake),
VirtualKeyCode::WebBack => Some(ScanCode::WebBack),
VirtualKeyCode::WebFavorites => Some(ScanCode::WebFavorites),
VirtualKeyCode::WebForward => Some(ScanCode::WebForward),
VirtualKeyCode::WebHome => Some(ScanCode::WebHome),
VirtualKeyCode::WebRefresh => Some(ScanCode::WebRefresh),
VirtualKeyCode::WebSearch => Some(ScanCode::WebSearch),
VirtualKeyCode::WebStop => Some(ScanCode::WebStop),
VirtualKeyCode::Yen => Some(ScanCode::Yen),
VirtualKeyCode::Copy => Some(ScanCode::Copy),
VirtualKeyCode::Paste => Some(ScanCode::Paste),
VirtualKeyCode::Cut => Some(ScanCode::Cut),
}
}

View File

@ -121,8 +121,8 @@ impl Scene for TitleScene {
} else {
self.option_menu.push_entry(MenuEntry::Disabled("Seasonal textures".to_string()));
}
self.option_menu.push_entry(MenuEntry::Active("Join our Discord".to_string()));
self.option_menu.push_entry(MenuEntry::Disabled(DISCORD_LINK.to_owned()));
self.option_menu.push_entry(MenuEntry::Active(DISCORD_LINK.to_owned()));
self.option_menu.push_entry(MenuEntry::Disabled(["Renderer: ", &ctx.renderer.as_ref().unwrap().renderer_name()].join("")));
self.option_menu.push_entry(MenuEntry::Active("Back".to_string()));
self.save_select_menu.push_entry(MenuEntry::NewSave);