initial commit
This commit is contained in:
commit
ff94e1048e
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
/data
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "ferretro-dev-gui"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
authors = ["viv <vvnl+git@protonmail.com>"]
|
||||
|
||||
[build-dependencies]
|
||||
cc = "^1"
|
||||
|
||||
[dependencies]
|
||||
crossbeam-channel = "^0.4"
|
||||
structopt = "^0.3"
|
||||
ferretro = { git = "ssh://git@vvn.space:2222/cinnabon/rustro.git", branch = "viv/ffmpeg2"}
|
||||
failure = "^0.1"
|
||||
libloading = "^0.5"
|
||||
|
||||
mini_gl_fb = { git = "https://github.com/vivlim/mini_gl_fb" } # bumped glutin version to 0.27.0 to match the glium i have
|
||||
|
||||
conrod_glium = { git = "https://github.com/vivlim/conrod" } # bumped version of glium to 0.30.1 to use winit 0.24 which fixes https://github.com/rust-windowing/winit/issues/1782
|
||||
conrod_core = { git = "https://github.com/vivlim/conrod" }
|
||||
glium = "0.30.1"
|
||||
|
||||
gilrs = "0.8.1"
|
|
@ -0,0 +1,754 @@
|
|||
extern crate crossbeam_channel;
|
||||
extern crate ferretro;
|
||||
extern crate mini_gl_fb;
|
||||
extern crate conrod_glium;
|
||||
extern crate conrod_core;
|
||||
extern crate failure;
|
||||
|
||||
use conrod_core::{Colorable, Positionable, Widget, widget, widget_ids};
|
||||
use glium::Surface;
|
||||
use glium::backend::Facade;
|
||||
use mini_gl_fb::glutin::dpi::LogicalSize;
|
||||
use mini_gl_fb::glutin::event_loop::EventLoop;
|
||||
use mini_gl_fb::glutin::event::{Event, WindowEvent, VirtualKeyCode, KeyboardInput, ElementState};
|
||||
use mini_gl_fb::{GlutinBreakout, config, get_fancy};
|
||||
use mini_gl_fb::glutin::window::{Window, WindowId};
|
||||
use mini_gl_fb::glutin::event_loop::ControlFlow;
|
||||
use mini_gl_fb::glutin::platform::run_return::EventLoopExtRunReturn;
|
||||
|
||||
use failure::{Fallible};
|
||||
use ferretro::retro;
|
||||
use ferretro::retro::ffi::{GameGeometry, SystemInfo, SystemAvInfo};
|
||||
use ferretro::retro::constants::{InputIndex, JoypadButton, AnalogAxis, DeviceType};
|
||||
use ferretro::retro::wrapped_types::{ControllerDescription2, InputDescriptor2, InputDeviceId, SubsystemInfo2, Variable2};
|
||||
use ferretro::retro::wrapper::LibretroWrapper;
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::cell::RefCell;
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::CStr;
|
||||
use std::io::{Read, Write};
|
||||
use std::ops::Add;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use structopt::StructOpt;
|
||||
|
||||
use gilrs::{Button, GamepadId, Gilrs, Axis};
|
||||
|
||||
struct MyEmulator {
|
||||
pub retro: retro::wrapper::LibretroWrapper,
|
||||
pub frame: u64,
|
||||
core_path: PathBuf,
|
||||
sys_path: Option<PathBuf>,
|
||||
game_path: Option<PathBuf>,
|
||||
|
||||
preferred_pad: Option<u32>,
|
||||
|
||||
sys_info: SystemInfo,
|
||||
av_info: SystemAvInfo,
|
||||
|
||||
gamepads: Gilrs,
|
||||
|
||||
title: String,
|
||||
|
||||
video_buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl MyEmulator {
|
||||
pub fn new(core_path: impl AsRef<Path>, sys_path: &Option<impl AsRef<Path>>) -> Pin<Box<Self>> {
|
||||
let core_path = PathBuf::from(core_path.as_ref());
|
||||
let lib = libloading::Library::new(&core_path).unwrap();
|
||||
let raw_retro = retro::loading::LibretroApi::from_library(lib).unwrap();
|
||||
let retro = retro::wrapper::LibretroWrapper::from(raw_retro);
|
||||
|
||||
let sys_info = retro.get_system_info();
|
||||
let title = format!(
|
||||
"{} - rust libretro",
|
||||
unsafe { CStr::from_ptr(sys_info.library_name) }.to_string_lossy()
|
||||
);
|
||||
|
||||
let mut av_info = retro.get_system_av_info();
|
||||
|
||||
// HACK: some cores don't report this 'til we get an environ call to set_system_av_info...
|
||||
// which is too late for this constructor to pass along to SDL.
|
||||
if av_info.timing.sample_rate == 0.0 {
|
||||
av_info.timing.sample_rate = 32040.0;
|
||||
}
|
||||
|
||||
let bpp: usize = 4;
|
||||
let mut video_buffer = vec![0u8 ; av_info.geometry.base_width as usize * av_info.geometry.base_height as usize * bpp];
|
||||
|
||||
let mut gamepads = Gilrs::new().unwrap();
|
||||
|
||||
let emu = MyEmulator {
|
||||
retro,
|
||||
frame: 0,
|
||||
core_path,
|
||||
sys_path: sys_path.as_ref().map(|p| p.as_ref().to_path_buf()),
|
||||
game_path: None,
|
||||
preferred_pad: None,
|
||||
av_info,
|
||||
sys_info,
|
||||
gamepads,
|
||||
title,
|
||||
video_buffer,
|
||||
};
|
||||
let mut pin_emu = Box::pin(emu);
|
||||
retro::wrapper::set_handler(pin_emu.as_mut());
|
||||
pin_emu.retro.init();
|
||||
pin_emu
|
||||
}
|
||||
|
||||
pub fn run_frame(&mut self){
|
||||
self.frame += 1;
|
||||
self.retro.run();
|
||||
// update fb
|
||||
}
|
||||
/*
|
||||
pub fn run(&mut self) {
|
||||
self.audio_device.resume();
|
||||
let mut event_pump = self.sdl_context.event_pump().unwrap();
|
||||
'running: loop {
|
||||
unsafe {
|
||||
gl::ClearColor(0.3, 0.6, 0.3, 1.0);
|
||||
gl::Clear(gl::COLOR_BUFFER_BIT);
|
||||
}
|
||||
let frame_begin = Instant::now();
|
||||
|
||||
for event in event_pump.poll_iter() {
|
||||
match event {
|
||||
Event::Quit { .. }
|
||||
| Event::KeyDown {
|
||||
keycode: Some(Keycode::Escape),
|
||||
..
|
||||
} => break 'running,
|
||||
| Event::KeyDown {
|
||||
keycode: Some(Keycode::F3),
|
||||
..
|
||||
} => self.serialize().unwrap(),
|
||||
| Event::KeyDown {
|
||||
keycode: Some(Keycode::F4),
|
||||
..
|
||||
} => self.unserialize_newest().unwrap(),
|
||||
_ => {
|
||||
self.geodiff.on_input(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The rest of the game loop goes here...
|
||||
self.retro.run();
|
||||
self.geodiff.on_frame();
|
||||
self.window.gl_swap_window();
|
||||
|
||||
// similar hack to the sample rate, make sure we don't divide by zero.
|
||||
let mut spf = 1.0 / self.av_info.timing.fps;
|
||||
if spf.is_nan() || spf.is_infinite() {
|
||||
spf = 1.0 / 60.0;
|
||||
}
|
||||
Duration::from_secs_f64(spf)
|
||||
.checked_sub(frame_begin.elapsed())
|
||||
.map(std::thread::sleep);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub fn load_game(&mut self, rom: impl AsRef<Path>) {
|
||||
let path = rom.as_ref();
|
||||
let mut data = None;
|
||||
let mut v = Vec::new();
|
||||
if !self.sys_info.need_fullpath {
|
||||
if let Ok(mut f) = std::fs::File::open(path) {
|
||||
if f.read_to_end(&mut v).is_ok() {
|
||||
data = Some(v.as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
self.retro
|
||||
.load_game(Some(path), data, None)
|
||||
.unwrap();
|
||||
if let Some(device) = self.preferred_pad {
|
||||
for port in 0..1 as u32 { // TODO: don't hardcode single controller
|
||||
self.retro.set_controller_port_device(port, device);
|
||||
}
|
||||
}
|
||||
|
||||
// Store the path so we can use it to build save state filenames.
|
||||
self.game_path = Some(path.clone().into());
|
||||
}
|
||||
|
||||
fn send_audio_samples(&mut self) {
|
||||
// not implemented
|
||||
}
|
||||
|
||||
|
||||
pub fn serialize(&self) -> Fallible<()> {
|
||||
let data = self.retro.serialize()?;
|
||||
|
||||
if let Some(game_path) = self.game_path.borrow() {
|
||||
let filename = build_savestate_filename(game_path);
|
||||
|
||||
match std::fs::File::create(filename.clone()) {
|
||||
Ok(mut f) => {
|
||||
f.write_all(&data)?;
|
||||
println!("State written to {:?}.", filename.into_os_string().into_string());
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => Err(e.into())
|
||||
}
|
||||
} else {
|
||||
Err(failure::err_msg("Game filename is not set, cannot determine serialized target filename"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unserialize_newest(&mut self) -> Fallible<()> {
|
||||
if let Some(game_path) = self.game_path.borrow()
|
||||
{
|
||||
return match get_newest_savestate(game_path) {
|
||||
Some(path) => self.unserialize(path),
|
||||
None => Err(failure::err_msg("No existing state found"))
|
||||
};
|
||||
}
|
||||
Err(failure::err_msg("Game filename is not set, cannot determine serialized target filename"))
|
||||
}
|
||||
|
||||
pub fn unserialize(&mut self, state: impl AsRef<Path>) -> Fallible<()> {
|
||||
let path = state.as_ref();
|
||||
let mut v = Vec::new();
|
||||
if let Ok(mut f) = std::fs::File::open(path) {
|
||||
if f.read_to_end(&mut v).is_ok(){
|
||||
return self.retro.unserialize(v.as_ref());
|
||||
}
|
||||
}
|
||||
Err(failure::err_msg("Couldn't read file to unserialize"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MyEmulator {
|
||||
fn drop(&mut self) {
|
||||
retro::wrapper::unset_handler();
|
||||
}
|
||||
}
|
||||
|
||||
impl retro::wrapper::Handler for MyEmulator {
|
||||
fn libretro_core(&mut self) -> &mut LibretroWrapper {
|
||||
&mut self.retro
|
||||
}
|
||||
|
||||
fn video_refresh(&mut self, data: &[u8], width: u32, height: u32, pitch: u32) {
|
||||
let buffer_len = self.video_buffer.len();
|
||||
let data_len = data.len();
|
||||
|
||||
let expected_len = (width * height * pitch) as usize;
|
||||
if buffer_len != expected_len {
|
||||
//println!("expected length {} doesn't match destination buffer: {}", expected_len, buffer_len);
|
||||
}
|
||||
|
||||
if data_len != expected_len {
|
||||
//println!("expected length {} doesn't match data size: {}", expected_len, data_len);
|
||||
}
|
||||
|
||||
if buffer_len == data_len {
|
||||
self.video_buffer.copy_from_slice(data);
|
||||
}
|
||||
else if buffer_len > data_len {
|
||||
self.video_buffer.as_mut_slice()[0..data_len].copy_from_slice(data);
|
||||
}
|
||||
else if buffer_len < data_len {
|
||||
self.video_buffer.copy_from_slice(&data[0..buffer_len]);
|
||||
}
|
||||
/*
|
||||
if let Ok(mut tex) =
|
||||
self.canvas
|
||||
.texture_creator()
|
||||
.create_texture_static(self.pixel_format, width, height)
|
||||
{
|
||||
if tex.update(rect, data, pitch as usize).is_ok() {
|
||||
self.canvas.clear();
|
||||
self.canvas.copy(&tex, None, None).unwrap();
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
fn audio_sample(&mut self, left: i16, right: i16) {
|
||||
// not implemented
|
||||
self.send_audio_samples()
|
||||
}
|
||||
|
||||
fn audio_sample_batch(&mut self, stereo_pcm: &[i16]) -> usize {
|
||||
// not implemented
|
||||
self.send_audio_samples();
|
||||
stereo_pcm.len()
|
||||
}
|
||||
|
||||
fn input_poll(&mut self) {
|
||||
//self.gamepad_subsys.update();
|
||||
}
|
||||
|
||||
fn input_state(&mut self, port: u32, device: InputDeviceId, index: InputIndex) -> i16 {
|
||||
match self.gamepads.gamepads().nth(port.try_into().unwrap()){
|
||||
Some(gamepad) => {
|
||||
match device {
|
||||
InputDeviceId::Joypad(button) => {
|
||||
match button_map(&button) {
|
||||
Some(gilrs_button) => gamepad.1.is_pressed(gilrs_button) as i16,
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
InputDeviceId::Analog(axis) => {
|
||||
let gilrs_axis = axis_map(index, axis);
|
||||
match gamepad.1.axis_data(gilrs_axis) {
|
||||
Some(data) => data.value() as i16,
|
||||
None => 0
|
||||
}
|
||||
},
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_can_dupe(&mut self) -> Option<bool> { Some(true) }
|
||||
|
||||
fn get_system_directory(&mut self) -> Option<PathBuf> {
|
||||
self.sys_path.clone()
|
||||
}
|
||||
|
||||
fn set_pixel_format(&mut self, pix_fmt: retro::ffi::PixelFormat) -> bool {
|
||||
// not implemented
|
||||
true
|
||||
}
|
||||
fn get_variable(&mut self, key: &str) -> Option<String> {
|
||||
match key {
|
||||
"beetle_saturn_analog_stick_deadzone" => Some("15%".to_string()),
|
||||
"parallel-n64-gfxplugin" => Some("angrylion".to_string()),
|
||||
"parallel-n64-astick-deadzone" => Some("15%".to_string()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_variables(&mut self, variables: Vec<Variable2>) -> bool {
|
||||
for v in variables {
|
||||
eprintln!("{:?}", v);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn get_libretro_path(&mut self) -> Option<PathBuf> {
|
||||
Some(self.core_path.clone())
|
||||
}
|
||||
|
||||
fn get_input_device_capabilities(&mut self) -> Option<u64> {
|
||||
let bits = (1 << (DeviceType::Joypad as u32)) | (1 << (DeviceType::Analog as u32));
|
||||
Some(bits as u64)
|
||||
}
|
||||
|
||||
fn get_save_directory(&mut self) -> Option<PathBuf> {
|
||||
Some(std::env::temp_dir())
|
||||
}
|
||||
|
||||
fn set_system_av_info(&mut self, av_info: SystemAvInfo) -> bool {
|
||||
self.set_geometry(av_info.geometry.clone());
|
||||
self.av_info = av_info;
|
||||
true
|
||||
}
|
||||
|
||||
fn set_subsystem_info(&mut self, subsystem_info: Vec<SubsystemInfo2>) -> bool {
|
||||
println!("subsystem info: {:?}", subsystem_info);
|
||||
true
|
||||
}
|
||||
|
||||
fn set_controller_info(&mut self, controller_info: Vec<ControllerDescription2>) -> bool {
|
||||
for ci in controller_info {
|
||||
// so we can have analog support in beetle/mednafen saturn
|
||||
if ci.name.as_str() == "3D Control Pad" {
|
||||
self.preferred_pad = Some(ci.device_id());
|
||||
break;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn set_input_descriptors(&mut self, descriptors: Vec<InputDescriptor2>) -> bool {
|
||||
for id in descriptors {
|
||||
println!("{:?}", id);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn set_geometry(&mut self, geom: GameGeometry) -> bool {
|
||||
/*
|
||||
let _ = self.canvas.window_mut().set_size(geom.base_width, geom.base_height);
|
||||
let _ = self.canvas.set_logical_size(geom.base_width, geom.base_height);
|
||||
self.av_info.geometry = geom;
|
||||
*/
|
||||
true
|
||||
}
|
||||
|
||||
fn log_print(&mut self, level: retro::ffi::LogLevel, msg: &str) {
|
||||
eprint!("[{:?}] {}", level, msg);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() -> failure::Fallible<()> {
|
||||
let opt: Opt = Opt::from_args();
|
||||
|
||||
let mut event_loop = EventLoop::new();
|
||||
let mut multi_window = MultiWindow::new();
|
||||
|
||||
let mut emu = Rc::new(RefCell::new(MyEmulator::new(&opt.core, &opt.system)));
|
||||
let mut emu_win = EmuWindow::new(&event_loop, &emu);
|
||||
|
||||
{
|
||||
let mut emu = emu.borrow_mut();
|
||||
emu.load_game(&opt.rom);
|
||||
}
|
||||
multi_window.add(emu_win);
|
||||
|
||||
multi_window.add(ConrodWindow::new(&event_loop, &emu));
|
||||
|
||||
//let geodiff = GeodiffUi::new(fb.glutin_breakout().context, config.window_size.width, config.window_size.height);
|
||||
|
||||
multi_window.run(&mut event_loop);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(StructOpt)]
|
||||
struct Opt {
|
||||
/// Core module to use.
|
||||
#[structopt(short, long, parse(from_os_str))]
|
||||
core: PathBuf,
|
||||
/// ROM to load using the core.
|
||||
#[structopt(short, long, parse(from_os_str))]
|
||||
rom: PathBuf,
|
||||
/// System directory, often containing BIOS files
|
||||
#[structopt(short, long, parse(from_os_str))]
|
||||
system: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn button_map(retro_button: &JoypadButton) -> Option<Button> {
|
||||
match retro_button {
|
||||
JoypadButton::B => Some(Button::South),
|
||||
JoypadButton::Y => Some(Button::West),
|
||||
JoypadButton::Select => Some(Button::Select),
|
||||
JoypadButton::Start => Some(Button::Start),
|
||||
JoypadButton::Up => Some(Button::DPadUp),
|
||||
JoypadButton::Down => Some(Button::DPadDown),
|
||||
JoypadButton::Left => Some(Button::DPadLeft),
|
||||
JoypadButton::Right => Some(Button::DPadRight),
|
||||
JoypadButton::A => Some(Button::East),
|
||||
JoypadButton::X => Some(Button::North),
|
||||
JoypadButton::L => Some(Button::LeftTrigger),
|
||||
JoypadButton::R => Some(Button::RightTrigger),
|
||||
JoypadButton::L2 => Some(Button::LeftTrigger2),
|
||||
JoypadButton::R2 => Some(Button::RightTrigger2),
|
||||
JoypadButton::L3 => Some(Button::LeftThumb),
|
||||
JoypadButton::R3 => Some(Button::RightThumb),
|
||||
}
|
||||
}
|
||||
|
||||
fn axis_map(index: InputIndex, axis: AnalogAxis) -> Axis {
|
||||
match (index, axis) {
|
||||
(InputIndex::Left, AnalogAxis::X) => Axis::LeftStickX,
|
||||
(InputIndex::Left, AnalogAxis::Y) => Axis::LeftStickY,
|
||||
(InputIndex::Right, AnalogAxis::X) => Axis::RightStickX,
|
||||
(InputIndex::Right, AnalogAxis::Y) => Axis::RightStickY,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_savestate_filename(game_name: &PathBuf) -> PathBuf {
|
||||
let mut index = 0;
|
||||
|
||||
let builder = |index: usize| game_name.as_path().with_extension(format!("state{}", index));
|
||||
let mut candidate = builder(index);
|
||||
|
||||
while candidate.exists() {
|
||||
index += 1;
|
||||
candidate = builder(index);
|
||||
}
|
||||
|
||||
candidate.into()
|
||||
}
|
||||
|
||||
fn get_newest_savestate(game_name: &PathBuf) -> Option<PathBuf> {
|
||||
let mut index = 0;
|
||||
|
||||
let builder = |index: usize| game_name.as_path().with_extension(format!("state{}", index));
|
||||
let mut candidate = builder(index);
|
||||
|
||||
if !candidate.exists() {
|
||||
return None;
|
||||
}
|
||||
|
||||
while candidate.exists() {
|
||||
index += 1;
|
||||
candidate = builder(index);
|
||||
}
|
||||
|
||||
// the current index is an unused index, the previous one exists.
|
||||
Some(builder(index - 1).into())
|
||||
}
|
||||
|
||||
/// A window being tracked by a `MultiWindow`. All tracked windows will be forwarded all events
|
||||
/// received on the `MultiWindow`'s event loop.
|
||||
trait TrackedWindow {
|
||||
/// Handles one event from the event loop. Returns true if the window needs to be kept alive,
|
||||
/// otherwise it will be closed. Window events should be checked to ensure that their ID is one
|
||||
/// that the TrackedWindow is interested in.
|
||||
fn handle_event(&mut self, event: &Event<()>) -> bool;
|
||||
}
|
||||
|
||||
/// Manages multiple `TrackedWindow`s by forwarding events to them.
|
||||
struct MultiWindow {
|
||||
windows: Vec<Option<Box<dyn TrackedWindow>>>,
|
||||
}
|
||||
|
||||
impl MultiWindow {
|
||||
/// Creates a new `MultiWindow`.
|
||||
pub fn new() -> Self {
|
||||
MultiWindow {
|
||||
windows: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a new `TrackedWindow` to the `MultiWindow`.
|
||||
pub fn add(&mut self, window: Box<dyn TrackedWindow>) {
|
||||
self.windows.push(Some(window))
|
||||
}
|
||||
|
||||
/// Runs the event loop until all `TrackedWindow`s are closed.
|
||||
pub fn run(&mut self, event_loop: &mut EventLoop<()>) {
|
||||
if !self.windows.is_empty() {
|
||||
event_loop.run_return(|event, _, flow| {
|
||||
*flow = ControlFlow::Poll;
|
||||
|
||||
for option in &mut self.windows {
|
||||
if let Some(window) = option.as_mut() {
|
||||
if !window.handle_event(&event) {
|
||||
option.take();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.windows.retain(Option::is_some);
|
||||
|
||||
if self.windows.is_empty() {
|
||||
*flow = ControlFlow::Exit;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct EmuWindow {
|
||||
pub emu: Rc<RefCell<Pin<Box<MyEmulator>>>>,
|
||||
pub breakout: GlutinBreakout,
|
||||
next_frame_time: Instant,
|
||||
frame_duration: Duration,
|
||||
}
|
||||
|
||||
impl EmuWindow {
|
||||
pub fn new(event_loop: &EventLoop<()>, emu: &Rc<RefCell<Pin<Box<MyEmulator>>>>) -> Box<Self> {
|
||||
let emu_inner = emu.borrow_mut();
|
||||
let config = config! {
|
||||
window_title: emu_inner.title.clone(),
|
||||
window_size: LogicalSize::new(emu_inner.av_info.geometry.base_width.into(), emu_inner.av_info.geometry.base_height.into())
|
||||
};
|
||||
let breakout = get_fancy(config, &event_loop).glutin_breakout();
|
||||
|
||||
Box::from(EmuWindow {
|
||||
emu: Rc::clone(emu),
|
||||
breakout,
|
||||
next_frame_time: Instant::now(),
|
||||
frame_duration: Duration::from_secs_f64(1.0/60.0),
|
||||
})
|
||||
}
|
||||
|
||||
fn window(&self) -> &Window {
|
||||
self.breakout.context.window()
|
||||
}
|
||||
|
||||
pub fn matches_id(&self, id: WindowId) -> bool {
|
||||
id == self.window().id()
|
||||
}
|
||||
/// Updates the window's buffer. Should only be done inside of RedrawRequested events; outside
|
||||
/// of them, use `request_redraw` instead.
|
||||
fn redraw(&mut self) {
|
||||
{
|
||||
let mut emu = self.emu.borrow_mut();
|
||||
self.breakout.fb.update_buffer(emu.video_buffer.as_slice());
|
||||
}
|
||||
self.breakout.context.swap_buffers().unwrap();
|
||||
}
|
||||
|
||||
/// Requests a redraw event for this window.
|
||||
fn request_redraw(&self) {
|
||||
self.window().request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
impl TrackedWindow for EmuWindow {
|
||||
fn handle_event(&mut self, event: &Event<()>) -> bool {
|
||||
match *event {
|
||||
Event::WindowEvent {
|
||||
window_id: id,
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} if self.matches_id(id) => {
|
||||
return false; // closed
|
||||
}
|
||||
Event::WindowEvent {
|
||||
window_id: id,
|
||||
event: WindowEvent::KeyboardInput {
|
||||
input: KeyboardInput {
|
||||
virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
} if self.matches_id(id) => {
|
||||
return false; // close on esc
|
||||
}
|
||||
Event::RedrawRequested(id) if self.matches_id(id) => {
|
||||
unsafe { self.breakout.make_current().unwrap(); }
|
||||
self.redraw();
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
if now > self.next_frame_time {
|
||||
self.next_frame_time = now.add(self.frame_duration);
|
||||
{
|
||||
let mut emu = self.emu.borrow_mut();
|
||||
emu.run_frame();
|
||||
}
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
widget_ids!(struct Ids { text, circle });
|
||||
|
||||
struct ConrodWindow {
|
||||
ui: conrod_core::Ui,
|
||||
renderer: conrod_glium::Renderer,
|
||||
display: glium::Display,
|
||||
image_map: conrod_core::image::Map<glium::texture::Texture2d>,
|
||||
window_id: WindowId,
|
||||
ids: Ids,
|
||||
pub emu: Rc<RefCell<Pin<Box<MyEmulator>>>>,
|
||||
}
|
||||
impl ConrodWindow {
|
||||
pub fn new(event_loop: &EventLoop<()>, emu: &Rc<RefCell<Pin<Box<MyEmulator>>>>) -> Box<Self> {
|
||||
let config = config! {
|
||||
window_title: "rustro debugger".to_string(),
|
||||
window_size: LogicalSize::new(1024.0, 768.0)
|
||||
};
|
||||
let mut ui = conrod_core::UiBuilder::new([config.window_size.width, config.window_size.height]).build();
|
||||
let ids = Ids::new(ui.widget_id_generator());
|
||||
let breakout = get_fancy(config, &event_loop).glutin_breakout();
|
||||
let image_map = conrod_core::image::Map::<glium::texture::Texture2d>::new();
|
||||
|
||||
let window_id = breakout.context.window().id();
|
||||
|
||||
let display = glium::Display::from_gl_window(breakout.context).unwrap();
|
||||
//let display = glium::Display::new(breakout.context.window(), breakout.context, event_loop).unwrap();
|
||||
let mut renderer = conrod_glium::Renderer::new(&display).unwrap();
|
||||
|
||||
Box::from(ConrodWindow {
|
||||
ui,
|
||||
renderer,
|
||||
display,
|
||||
image_map,
|
||||
window_id,
|
||||
ids,
|
||||
emu: Rc::clone(emu),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
pub fn matches_id(&self, id: WindowId) -> bool {
|
||||
id == self.window_id
|
||||
}
|
||||
/// Updates the window's buffer. Should only be done inside of RedrawRequested events; outside
|
||||
/// of them, use `request_redraw` instead.
|
||||
fn redraw(&mut self) {
|
||||
|
||||
//if let Some(primitives) = ui.draw_if_changed() {
|
||||
let primitives = self.ui.draw();
|
||||
self.renderer.fill(&self.display, primitives, &self.image_map);
|
||||
let mut target = self.display.draw();
|
||||
target.clear_color(0.0, 1.0, 0.0, 1.0);
|
||||
self.renderer.draw(&self.display, &mut target, &self.image_map).unwrap();
|
||||
target.finish().unwrap();
|
||||
//}
|
||||
}
|
||||
|
||||
/// Requests a redraw event for this window.
|
||||
fn request_redraw(&self) {
|
||||
self.display.gl_window().window().request_redraw()
|
||||
}
|
||||
}
|
||||
impl TrackedWindow for ConrodWindow {
|
||||
fn handle_event(&mut self, event: &Event<()>) -> bool {
|
||||
match *event {
|
||||
Event::WindowEvent {
|
||||
window_id: id,
|
||||
event: WindowEvent::CloseRequested,
|
||||
..
|
||||
} if self.matches_id(id) => {
|
||||
return false; // closed
|
||||
}
|
||||
Event::WindowEvent {
|
||||
window_id: id,
|
||||
event: WindowEvent::KeyboardInput {
|
||||
input: KeyboardInput {
|
||||
virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||
state: ElementState::Pressed,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
..
|
||||
} if self.matches_id(id) => {
|
||||
return false; // close on esc
|
||||
}
|
||||
Event::MainEventsCleared => {
|
||||
let mut emu = self.emu.borrow_mut();
|
||||
|
||||
let mut ui = self.ui.set_widgets();
|
||||
widget::Text::new(format!("frame {}", emu.frame).as_str())
|
||||
.middle_of(ui.window)
|
||||
.color(conrod_core::color::WHITE)
|
||||
.font_size(32)
|
||||
.set(self.ids.text, &mut ui);
|
||||
widget::Circle::fill(32.0)
|
||||
.x_y(emu.frame as f64, 64.0)
|
||||
.color(conrod_core::color::BLUE)
|
||||
.set(self.ids.circle, &mut ui);
|
||||
|
||||
|
||||
self.display.gl_window().window().request_redraw();
|
||||
}
|
||||
Event::RedrawRequested(id) if self.matches_id(id) => {
|
||||
self.redraw();
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue