Initial Android support and some ggez rewrite

This commit is contained in:
Alula 2020-10-07 16:08:12 +02:00
parent 8a478c6f72
commit 2f111919d6
No known key found for this signature in database
GPG Key ID: 3E00485503A1D8BA
29 changed files with 1014 additions and 1439 deletions

6
.cargo/config Normal file
View File

@ -0,0 +1,6 @@
[target.aarch64-linux-android]
rustflags = [
"-C", "link-arg=-lc++_static",
"-C", "link-arg=-lc++abi",
"-C", "link-arg=-lEGL",
]

View File

@ -73,6 +73,7 @@ jobs:
files: | files: |
doukutsu-rs.x86_64 doukutsu-rs.x86_64
data data
!data/.git
!data/README.md !data/README.md
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -134,6 +135,7 @@ jobs:
path: | path: |
doukutsu-rs.exe doukutsu-rs.exe
data data
!data/.git
!data/README.md !data/README.md
if-no-files-found: error if-no-files-found: error

View File

@ -4,6 +4,29 @@ edition = "2018"
name = "doukutsu-rs" name = "doukutsu-rs"
version = "0.1.0" version = "0.1.0"
[lib]
crate-type = ["lib", "cdylib"]
[package.metadata.android]
android_version = 28
target_sdk_version = 28
min_sdk_version = 26
build_targets = ["aarch64-linux-android"]
package_name = "io.github.doukutsu_rs.android"
apk_label = "doukutsu-rs"
opengles_version = [2, 0]
fullscreen = true
orientation = "sensorLandscape"
permission = [
{name = "android.permission.MANAGE_EXTERNAL_STORAGE"},
{name = "android.permission.READ_EXTERNAL_STORAGE"},
{name = "android.permission.WRITE_EXTERNAL_STORAGE"}
]
application_metadatas = [
{name = "android:hardwareAccelerated", value = "true"},
{name = "android:requestLegacyExternalStorage", value = "true"}
]
[profile.release] [profile.release]
lto = 'thin' lto = 'thin'
panic = 'abort' panic = 'abort'
@ -12,32 +35,33 @@ panic = 'abort'
opt-level = 1 opt-level = 1
[dependencies] [dependencies]
#cpal = {path = "./cpal"}
#gfx_device_gl = {path = "./gfx/src/backend/gl"}
#glutin = {path = "./glutin/glutin"}
approx = "0.3" approx = "0.3"
bitflags = "1" bitflags = "1"
bitvec = "0.17.4" bitvec = "0.17.4"
byteorder = "1.3" byteorder = "1.3"
case_insensitive_hashmap = "1.0.0" case_insensitive_hashmap = "1.0.0"
cpal = "0.12.1" cpal = {git = "https://github.com/alula/cpal.git", branch = "android-support"}
directories = "2" directories = "2"
gfx = "0.18" gfx = "0.18"
gfx_core = "0.9" gfx_core = "0.9"
gfx_device_gl = "0.16" gfx_device_gl = {git = "https://github.com/doukutsu-rs/gfx.git", branch = "pre-ll"}
gfx_window_glutin = "0.30"
gilrs = "0.7" gilrs = "0.7"
glyph_brush = "0.5" glutin = {git = "https://github.com/doukutsu-rs/glutin.git", branch = "android-support"}
glutin = "0.20" imgui = {git = "https://github.com/JMS55/imgui-rs.git"}
imgui = "0.4.0" imgui-gfx-renderer = {git = "https://github.com/JMS55/imgui-rs.git"}
imgui-ext = "0.3.0" imgui-winit-support = {git = "https://github.com/JMS55/imgui-rs.git", default-features = false, features = ["winit-23"]}
imgui-gfx-renderer = "0.4.0" image = {version = "0.22", default-features = false, features = ["png_codec", "pnm", "bmp"]}
imgui-winit-support = {version = "0.4.0", default-features = false, features = ["winit-19"] }
image = {version = "0.22", default-features = false, features = ["png_codec", "pnm", "bmp"] }
itertools = "0.9.0" itertools = "0.9.0"
lazy_static = "1.4.0" lazy_static = "1.4.0"
log = "0.4" log = "0.4"
lru = "0.6.0" lru = "0.6.0"
lyon = "0.13" lyon = "0.13"
mint = "0.5" mint = "0.5"
nalgebra = {version = "0.18", features = ["mint"] } nalgebra = {version = "0.18", features = ["mint"]}
num-derive = "0.3.2" num-derive = "0.3.2"
num-traits = "0.2.12" num-traits = "0.2.12"
owning_ref = "0.4.1" owning_ref = "0.4.1"
@ -53,4 +77,9 @@ varint = "0.9.0"
# remove and replace when drain_filter is in stable # remove and replace when drain_filter is in stable
vec_mut_scan = "0.3.0" vec_mut_scan = "0.3.0"
webbrowser = "0.5.5" webbrowser = "0.5.5"
winit = { version = "0.19.3" } winit = "0.23.0"
[target.'cfg(target_os = "android")'.dependencies]
ndk = "0.2.0"
ndk-glue = "0.2.0"
jni = "0.17"

BIN
src/builtin/touch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

View File

@ -168,7 +168,7 @@ pub struct WindowSetup {
pub icon: String, pub icon: String,
/// Whether or not to enable sRGB (gamma corrected color) /// Whether or not to enable sRGB (gamma corrected color)
/// handling on the display. /// handling on the display.
#[default = true] #[default = false]
pub srgb: bool, pub srgb: bool,
} }

View File

@ -15,6 +15,7 @@ use crate::ggez::filesystem::Filesystem;
use crate::ggez::graphics::{self, FilterMode, Point2}; use crate::ggez::graphics::{self, FilterMode, Point2};
use crate::ggez::input::{gamepad, keyboard, mouse}; use crate::ggez::input::{gamepad, keyboard, mouse};
use crate::ggez::timer; use crate::ggez::timer;
use glutin::platform::ContextTraitExt;
/// A `Context` is an object that holds on to global resources. /// A `Context` is an object that holds on to global resources.
/// It basically tracks hardware state such as the screen, audio /// It basically tracks hardware state such as the screen, audio
@ -79,14 +80,13 @@ impl fmt::Debug for Context {
impl Context { impl Context {
/// Tries to create a new Context using settings from the given [`Conf`](../conf/struct.Conf.html) object. /// Tries to create a new Context using settings from the given [`Conf`](../conf/struct.Conf.html) object.
/// Usually called by [`ContextBuilder::build()`](struct.ContextBuilder.html#method.build). /// Usually called by [`ContextBuilder::build()`](struct.ContextBuilder.html#method.build).
fn from_conf(conf: conf::Conf, mut fs: Filesystem) -> GameResult<(Context, winit::EventsLoop)> { fn from_conf(conf: conf::Conf, events_loop: &winit::event_loop::EventLoopWindowTarget<()>, mut fs: Filesystem) -> GameResult<Context> {
let debug_id = DebugId::new(); let debug_id = DebugId::new();
let events_loop = winit::EventsLoop::new();
let timer_context = timer::TimeContext::new(); let timer_context = timer::TimeContext::new();
let backend_spec = graphics::GlBackendSpec::from(conf.backend); let backend_spec = graphics::GlBackendSpec::from(conf.backend);
let graphics_context = graphics::context::GraphicsContext::new( let graphics_context = graphics::context::GraphicsContext::new(
&mut fs, &mut fs,
&events_loop, events_loop,
&conf.window_setup, &conf.window_setup,
conf.window_mode, conf.window_mode,
backend_spec, backend_spec,
@ -95,7 +95,12 @@ impl Context {
let mouse_context = mouse::MouseContext::new(); let mouse_context = mouse::MouseContext::new();
let keyboard_context = keyboard::KeyboardContext::new(); let keyboard_context = keyboard::KeyboardContext::new();
let gamepad_context: Box<dyn gamepad::GamepadContext> = if conf.modules.gamepad { let gamepad_context: Box<dyn gamepad::GamepadContext> = if conf.modules.gamepad {
Box::new(gamepad::GilrsGamepadContext::new()?) let gp: Box<dyn gamepad::GamepadContext> = if let Ok(ctx) = gamepad::GilrsGamepadContext::new() {
Box::new(ctx)
} else {
Box::new(gamepad::NullGamepadContext::default())
};
gp
} else { } else {
Box::new(gamepad::NullGamepadContext::default()) Box::new(gamepad::NullGamepadContext::default())
}; };
@ -114,7 +119,7 @@ impl Context {
debug_id, debug_id,
}; };
Ok((ctx, events_loop)) Ok(ctx)
} }
// TODO LATER: This should be a function in `ggez::event`, per the // TODO LATER: This should be a function in `ggez::event`, per the
@ -124,13 +129,11 @@ impl Context {
/// state it needs to, such as detecting window resizes. If you are /// state it needs to, such as detecting window resizes. If you are
/// rolling your own event loop, you should call this on the events /// rolling your own event loop, you should call this on the events
/// you receive before processing them yourself. /// you receive before processing them yourself.
pub fn process_event(&mut self, event: &winit::Event) { pub fn process_event<'a>(&mut self, event: &winit::event::Event<'a, ()>) {
match event.clone() { match event {
winit_event::Event::WindowEvent { event, .. } => match event { winit_event::Event::WindowEvent { event, .. } => match event {
winit_event::WindowEvent::Resized(logical_size) => { winit_event::WindowEvent::Resized(physical_size) => {
let hidpi_factor = self.gfx_context.window.get_hidpi_factor(); self.gfx_context.window.resize(*physical_size);
let physical_size = logical_size.to_physical(hidpi_factor as f64);
self.gfx_context.window.resize(physical_size);
self.gfx_context.resize_viewport(); self.gfx_context.resize_viewport();
} }
winit_event::WindowEvent::CursorMoved { winit_event::WindowEvent::CursorMoved {
@ -147,11 +150,11 @@ impl Context {
winit_event::ElementState::Pressed => true, winit_event::ElementState::Pressed => true,
winit_event::ElementState::Released => false, winit_event::ElementState::Released => false,
}; };
self.mouse_context.set_button(button, pressed); self.mouse_context.set_button(*button, pressed);
} }
winit_event::WindowEvent::KeyboardInput { winit_event::WindowEvent::KeyboardInput {
input: input:
winit::KeyboardInput { winit::event::KeyboardInput {
state, state,
virtual_keycode: Some(keycode), virtual_keycode: Some(keycode),
modifiers, modifiers,
@ -164,34 +167,29 @@ impl Context {
winit_event::ElementState::Released => false, winit_event::ElementState::Released => false,
}; };
self.keyboard_context self.keyboard_context
.set_modifiers(keyboard::KeyMods::from(modifiers)); .set_modifiers(keyboard::KeyMods::from(*modifiers));
self.keyboard_context.set_key(keycode, pressed); self.keyboard_context.set_key(*keycode, pressed);
}
winit_event::WindowEvent::HiDpiFactorChanged(_) => {
// Nope.
} }
_ => (), _ => (),
}, },
winit_event::Event::DeviceEvent { event, .. } => { winit_event::Event::DeviceEvent { event, .. } => {
if let winit_event::DeviceEvent::MouseMotion { delta: (x, y) } = event { if let winit_event::DeviceEvent::MouseMotion { delta: (x, y) } = event {
self.mouse_context self.mouse_context
.set_last_delta(Point2::new(x as f32, y as f32)); .set_last_delta(Point2::new(*x as f32, *y as f32));
} }
} }
_ => (), _ => (),
}; };
} }
} }
/// A builder object for creating a [`Context`](struct.Context.html). /// A builder object for creating a [`Context`](struct.Context.html).
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone)]
pub struct ContextBuilder { pub struct ContextBuilder {
pub(crate) game_id: String, pub(crate) game_id: String,
pub(crate) conf: conf::Conf, pub(crate) conf: conf::Conf,
pub(crate) paths: Vec<path::PathBuf>, pub(crate) paths: Vec<path::PathBuf>,
pub(crate) memory_zip_files: Vec<Cow<'static, [u8]>>, pub(crate) memory_zip_files: Vec<Cow<'static, [u8]>>,
pub(crate) load_conf_file: bool,
} }
impl ContextBuilder { impl ContextBuilder {
@ -202,7 +200,6 @@ impl ContextBuilder {
conf: conf::Conf::default(), conf: conf::Conf::default(),
paths: vec![], paths: vec![],
memory_zip_files: vec![], memory_zip_files: vec![],
load_conf_file: true,
} }
} }
@ -249,30 +246,15 @@ impl ContextBuilder {
self self
} }
/// Specifies whether or not to load the `conf.toml` file if it
/// exists and use its settings to override the provided values.
/// Defaults to `true` which is usually what you want, but being
/// able to fiddle with it is sometimes useful for debugging.
pub fn with_conf_file(mut self, load_conf_file: bool) -> Self {
self.load_conf_file = load_conf_file;
self
}
/// Build the `Context`. /// Build the `Context`.
pub fn build(self) -> GameResult<(Context, winit::EventsLoop)> { pub fn build(mut self, event_loop: &winit::event_loop::EventLoopWindowTarget<()>) -> GameResult<Context> {
let mut fs = Filesystem::new(self.game_id.as_ref())?; let mut fs = Filesystem::new(self.game_id.as_ref())?;
for path in &self.paths { for path in &self.paths {
fs.mount(path, true); fs.mount(path, true);
} }
let config = if self.load_conf_file { Context::from_conf(self.conf, event_loop, fs)
fs.read_config().unwrap_or(self.conf)
} else {
self.conf
};
Context::from_conf(config, fs)
} }
} }

View File

@ -181,8 +181,8 @@ impl From<gfx::shade::ProgramError> for GameError {
} }
} }
impl From<winit::EventsLoopClosed> for GameError { impl<T> From<winit::event_loop::EventLoopClosed<T>> for GameError {
fn from(_: glutin::EventsLoopClosed) -> GameError { fn from(_: winit::event_loop::EventLoopClosed<T>) -> GameError {
let e = "An event loop proxy attempted to wake up an event loop that no longer exists." let e = "An event loop proxy attempted to wake up an event loop that no longer exists."
.to_owned(); .to_owned();
GameError::EventLoopError(e) GameError::EventLoopError(e)
@ -208,6 +208,13 @@ impl From<gilrs::Error> for GameError {
} }
} }
#[cfg(target_os = "android")]
impl From<jni::errors::Error> for GameError {
fn from(e: jni::errors::Error) -> GameError {
GameError::WindowError(e.to_string())
}
}
impl From<lyon::lyon_tessellation::TessellationError> for GameError { impl From<lyon::lyon_tessellation::TessellationError> for GameError {
fn from(s: lyon::lyon_tessellation::TessellationError) -> GameError { fn from(s: lyon::lyon_tessellation::TessellationError) -> GameError {
let errstr = format!( let errstr = format!(

View File

@ -11,36 +11,35 @@
//! example](https://github.com/ggez/ggez/blob/master/examples/eventloop.rs). //! example](https://github.com/ggez/ggez/blob/master/examples/eventloop.rs).
use gilrs; use gilrs;
/// An analog axis of some device (gamepad thumbstick, joystick...).
pub use gilrs::Axis;
/// A button of some device (gamepad, joystick...).
pub use gilrs::Button;
use winit::{self, dpi}; use winit::{self, dpi};
/// A mouse button.
pub use winit::event::MouseButton;
/// `winit` event loop.
pub use winit::event_loop::EventLoop;
use crate::ggez::context::Context;
use crate::ggez::error::GameResult;
pub use crate::ggez::input::gamepad::GamepadId;
pub use crate::ggez::input::keyboard::{KeyCode, KeyMods};
use self::winit_event::*;
use crate::ggez::graphics::window;
// TODO LATER: I kinda hate all these re-exports. I kinda hate // TODO LATER: I kinda hate all these re-exports. I kinda hate
// a lot of the details of the `EventHandler` and input now though, // a lot of the details of the `EventHandler` and input now though,
// and look forward to ripping it all out and replacing it with newer winit. // and look forward to ripping it all out and replacing it with newer winit.
/// A mouse button.
pub use winit::MouseButton;
/// An analog axis of some device (gamepad thumbstick, joystick...).
pub use gilrs::Axis;
/// A button of some device (gamepad, joystick...).
pub use gilrs::Button;
/// `winit` events; nested in a module for re-export neatness. /// `winit` events; nested in a module for re-export neatness.
pub mod winit_event { pub mod winit_event {
pub use super::winit::{ pub use winit::event::{
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, MouseScrollDelta, DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, MouseScrollDelta,
TouchPhase, WindowEvent, TouchPhase, WindowEvent,
}; };
} }
pub use crate::ggez::input::gamepad::GamepadId;
pub use crate::ggez::input::keyboard::{KeyCode, KeyMods};
use self::winit_event::*;
/// `winit` event loop.
pub use winit::EventsLoop;
use crate::ggez::context::Context;
use crate::ggez::error::GameResult;
/// A trait defining event callbacks. This is your primary interface with /// A trait defining event callbacks. This is your primary interface with
/// `ggez`'s event loop. Implement this trait for a type and /// `ggez`'s event loop. Implement this trait for a type and
@ -71,8 +70,7 @@ pub trait EventHandler {
_button: MouseButton, _button: MouseButton,
_x: f32, _x: f32,
_y: f32, _y: f32,
) { ) {}
}
/// A mouse button was released /// A mouse button was released
fn mouse_button_up_event( fn mouse_button_up_event(
@ -81,8 +79,7 @@ pub trait EventHandler {
_button: MouseButton, _button: MouseButton,
_x: f32, _x: f32,
_y: f32, _y: f32,
) { ) {}
}
/// The mouse was moved; it provides both absolute x and y coordinates in the window, /// The mouse was moved; it provides both absolute x and y coordinates in the window,
/// and relative x and y coordinates compared to its last position. /// and relative x and y coordinates compared to its last position.
@ -130,8 +127,7 @@ pub trait EventHandler {
/// A gamepad axis moved; `id` identifies which gamepad. /// A gamepad axis moved; `id` identifies which gamepad.
/// Use [`input::gamepad()`](../input/fn.gamepad.html) to get more info about /// Use [`input::gamepad()`](../input/fn.gamepad.html) to get more info about
/// the gamepad. /// the gamepad.
fn gamepad_axis_event(&mut self, _ctx: &mut Context, _axis: Axis, _value: f32, _id: GamepadId) { fn gamepad_axis_event(&mut self, _ctx: &mut Context, _axis: Axis, _value: f32, _id: GamepadId) {}
}
/// Called when the window is shown or hidden. /// Called when the window is shown or hidden.
fn focus_event(&mut self, _ctx: &mut Context, _gained: bool) {} fn focus_event(&mut self, _ctx: &mut Context, _gained: bool) {}
@ -155,24 +151,25 @@ pub fn quit(ctx: &mut Context) {
ctx.continuing = false; ctx.continuing = false;
} }
/*
/// Runs the game's main loop, calling event callbacks on the given state /// Runs the game's main loop, calling event callbacks on the given state
/// object as events occur. /// object as events occur.
/// ///
/// It does not try to do any type of framerate limiting. See the /// It does not try to do any type of framerate limiting. See the
/// documentation for the [`timer`](../timer/index.html) module for more info. /// documentation for the [`timer`](../timer/index.html) module for more info.
pub fn run<S>(ctx: &mut Context, events_loop: &mut EventsLoop, state: &mut S) -> GameResult pub fn run<S>(ctx: &'static mut Context, events_loop: &mut EventLoop<()>, state: &'static mut S) -> GameResult
where where
S: EventHandler, S: EventHandler,
{ {
use crate::ggez::input::{keyboard, mouse}; use crate::ggez::input::{keyboard, mouse};
// If you are writing your own event loop, make sure
// you include `timer_context.tick()` and
// `ctx.process_event()` calls. These update ggez's
// internal state however necessary.
while ctx.continuing { while ctx.continuing {
// If you are writing your own event loop, make sure events_loop.run_return(|event, _target, _flow| {
// you include `timer_context.tick()` and ctx.timer_context.tick();
// `ctx.process_event()` calls. These update ggez's
// internal state however necessary.
ctx.timer_context.tick();
events_loop.poll_events(|event| {
ctx.process_event(&event); ctx.process_event(&event);
match event { match event {
Event::WindowEvent { event, .. } => match event { Event::WindowEvent { event, .. } => match event {
@ -197,12 +194,12 @@ where
} }
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: input:
KeyboardInput { KeyboardInput {
state: ElementState::Pressed, state: ElementState::Pressed,
virtual_keycode: Some(keycode), virtual_keycode: Some(keycode),
modifiers, modifiers,
.. ..
}, },
.. ..
} => { } => {
let repeat = keyboard::is_key_repeated(ctx); let repeat = keyboard::is_key_repeated(ctx);
@ -210,12 +207,12 @@ where
} }
WindowEvent::KeyboardInput { WindowEvent::KeyboardInput {
input: input:
KeyboardInput { KeyboardInput {
state: ElementState::Released, state: ElementState::Released,
virtual_keycode: Some(keycode), virtual_keycode: Some(keycode),
modifiers, modifiers,
.. ..
}, },
.. ..
} => { } => {
state.key_up_event(ctx, keycode, modifiers.into()); state.key_up_event(ctx, keycode, modifiers.into());
@ -256,30 +253,38 @@ where
Event::DeviceEvent { event, .. } => match event { Event::DeviceEvent { event, .. } => match event {
_ => (), _ => (),
}, },
Event::Awakened => (), Event::Resumed => (),
Event::Suspended(_) => (), Event::Suspended => (),
Event::RedrawRequested(win) => {
if win == ctx.gfx_context.window.window().id() {
state.draw(ctx).unwrap();
}
}
Event::MainEventsCleared => {
// Handle gamepad events if necessary.
if ctx.conf.modules.gamepad {
while let Some(gilrs::Event { id, event, .. }) = ctx.gamepad_context.next_event() {
match event {
gilrs::EventType::ButtonPressed(button, _) => {
state.gamepad_button_down_event(ctx, button, GamepadId(id));
}
gilrs::EventType::ButtonReleased(button, _) => {
state.gamepad_button_up_event(ctx, button, GamepadId(id));
}
gilrs::EventType::AxisChanged(axis, value, _) => {
state.gamepad_axis_event(ctx, axis, value, GamepadId(id));
}
_ => {}
}
}
}
state.update(ctx).unwrap();
window(ctx).window().request_redraw();
}
_ => {}
} }
}); });
// Handle gamepad events if necessary.
if ctx.conf.modules.gamepad {
while let Some(gilrs::Event { id, event, .. }) = ctx.gamepad_context.next_event() {
match event {
gilrs::EventType::ButtonPressed(button, _) => {
state.gamepad_button_down_event(ctx, button, GamepadId(id));
}
gilrs::EventType::ButtonReleased(button, _) => {
state.gamepad_button_up_event(ctx, button, GamepadId(id));
}
gilrs::EventType::AxisChanged(axis, value, _) => {
state.gamepad_axis_event(ctx, axis, value, GamepadId(id));
}
_ => {}
}
}
}
state.update(ctx)?;
state.draw(ctx)?;
} }
Ok(()) Ok(())
} }*/

View File

@ -1,18 +1,20 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::path::Path;
use std::rc::Rc; use std::rc::Rc;
use gfx::traits::FactoryExt;
use gfx::Factory; use gfx::Factory;
use gfx::traits::FactoryExt;
use glutin; use glutin;
use glyph_brush::{GlyphBrush, GlyphBrushBuilder}; use glutin::PossiblyCurrent;
use winit::{self, dpi}; use winit::{self, dpi};
use winit::window::Fullscreen;
use crate::ggez::conf::{FullscreenType, WindowMode, WindowSetup}; use crate::ggez::conf::{FullscreenType, WindowMode, WindowSetup};
use crate::ggez::context::DebugId; use crate::ggez::context::DebugId;
use crate::ggez::filesystem::Filesystem;
use crate::ggez::graphics::*;
use crate::ggez::error::GameResult; use crate::ggez::error::GameResult;
use crate::ggez::filesystem::Filesystem;
use crate::ggez::GameError;
use crate::ggez::graphics::*;
/// A structure that contains graphics state. /// A structure that contains graphics state.
/// For instance, /// For instance,
@ -20,8 +22,8 @@ use crate::ggez::error::GameResult;
/// ///
/// As an end-user you shouldn't ever have to touch this. /// As an end-user you shouldn't ever have to touch this.
pub(crate) struct GraphicsContextGeneric<B> pub(crate) struct GraphicsContextGeneric<B>
where where
B: BackendSpec, B: BackendSpec,
{ {
shader_globals: Globals, shader_globals: Globals,
pub(crate) projection: Matrix4, pub(crate) projection: Matrix4,
@ -30,10 +32,9 @@ where
pub(crate) screen_rect: Rect, pub(crate) screen_rect: Rect,
color_format: gfx::format::Format, color_format: gfx::format::Format,
depth_format: gfx::format::Format, depth_format: gfx::format::Format,
srgb: bool,
pub(crate) backend_spec: B, pub(crate) backend_spec: B,
pub(crate) window: glutin::WindowedContext, pub(crate) window: glutin::WindowedContext<PossiblyCurrent>,
pub(crate) multisample_samples: u8, pub(crate) multisample_samples: u8,
pub(crate) device: Box<B::Device>, pub(crate) device: Box<B::Device>,
pub(crate) factory: Box<B::Factory>, pub(crate) factory: Box<B::Factory>,
@ -52,15 +53,11 @@ where
default_shader: ShaderId, default_shader: ShaderId,
pub(crate) current_shader: Rc<RefCell<Option<ShaderId>>>, pub(crate) current_shader: Rc<RefCell<Option<ShaderId>>>,
pub(crate) shaders: Vec<Box<dyn ShaderHandle<B>>>, pub(crate) shaders: Vec<Box<dyn ShaderHandle<B>>>,
pub(crate) glyph_brush: GlyphBrush<'static, DrawParam>,
pub(crate) glyph_cache: ImageGeneric<B>,
pub(crate) glyph_state: Rc<RefCell<spritebatch::SpriteBatch>>,
} }
impl<B> fmt::Debug for GraphicsContextGeneric<B> impl<B> fmt::Debug for GraphicsContextGeneric<B>
where where
B: BackendSpec, B: BackendSpec,
{ {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "<GraphicsContext: {:p}>", self) write!(formatter, "<GraphicsContext: {:p}>", self)
@ -74,24 +71,16 @@ impl GraphicsContextGeneric<GlBackendSpec> {
/// Create a new GraphicsContext /// Create a new GraphicsContext
pub(crate) fn new( pub(crate) fn new(
filesystem: &mut Filesystem, filesystem: &mut Filesystem,
events_loop: &winit::EventsLoop, events_loop: &winit::event_loop::EventLoopWindowTarget<()>,
window_setup: &WindowSetup, window_setup: &WindowSetup,
window_mode: WindowMode, window_mode: WindowMode,
backend: GlBackendSpec, backend: GlBackendSpec,
debug_id: DebugId, debug_id: DebugId,
) -> GameResult<Self> { ) -> GameResult<Self> {
let srgb = window_setup.srgb; let color_format = gfx::format::Format(
let color_format = if srgb { gfx::format::SurfaceType::R8_G8_B8_A8,
gfx::format::Format( gfx::format::ChannelType::Unorm,
gfx::format::SurfaceType::R8_G8_B8_A8, );
gfx::format::ChannelType::Srgb,
)
} else {
gfx::format::Format(
gfx::format::SurfaceType::R8_G8_B8_A8,
gfx::format::ChannelType::Unorm,
)
};
let depth_format = gfx::format::Format( let depth_format = gfx::format::Format(
gfx::format::SurfaceType::D24_S8, gfx::format::SurfaceType::D24_S8,
gfx::format::ChannelType::Unorm, gfx::format::ChannelType::Unorm,
@ -110,10 +99,10 @@ impl GraphicsContextGeneric<GlBackendSpec> {
.with_vsync(window_setup.vsync); .with_vsync(window_setup.vsync);
let window_size = let window_size =
dpi::LogicalSize::from((f64::from(window_mode.width), f64::from(window_mode.height))); dpi::LogicalSize::<f64>::from((f64::from(window_mode.width), f64::from(window_mode.height)));
let mut window_builder = winit::WindowBuilder::new() let mut window_builder = glutin::window::WindowBuilder::new()
.with_title(window_setup.title.clone()) .with_title(window_setup.title.clone())
.with_dimensions(window_size) .with_inner_size(window_size)
.with_resizable(window_mode.resizable); .with_resizable(window_mode.resizable);
window_builder = if !window_setup.icon.is_empty() { window_builder = if !window_setup.icon.is_empty() {
@ -136,19 +125,15 @@ impl GraphicsContextGeneric<GlBackendSpec> {
// since we have no good control over it. // since we have no good control over it.
{ {
// Log a bunch of OpenGL state info pulled out of winit and gfx // Log a bunch of OpenGL state info pulled out of winit and gfx
let dpi::LogicalSize { let dpi::PhysicalSize {
width: w, width: w,
height: h, height: h,
} = window } = window.window().outer_size();
.get_outer_size() let dpi::PhysicalSize {
.ok_or_else(|| GameError::VideoError("Window doesn't exist!".to_owned()))?;
let dpi::LogicalSize {
width: dw, width: dw,
height: dh, height: dh,
} = window } = window.window().inner_size();
.get_inner_size() let hidpi_factor = window.window().scale_factor();
.ok_or_else(|| GameError::VideoError("Window doesn't exist!".to_owned()))?;
let hidpi_factor = window.get_hidpi_factor();
debug!( debug!(
"Window created, desired size {}x{}, hidpi factor {}.", "Window created, desired size {}x{}, hidpi factor {}.",
window_mode.width, window_mode.height, hidpi_factor window_mode.width, window_mode.height, hidpi_factor
@ -226,33 +211,6 @@ impl GraphicsContextGeneric<GlBackendSpec> {
let texture = white_image.texture.clone(); let texture = white_image.texture.clone();
let typed_thingy = backend.raw_to_typed_shader_resource(texture); let typed_thingy = backend.raw_to_typed_shader_resource(texture);
let data = pipe::Data {
vbuf: quad_vertex_buffer.clone(),
tex: (typed_thingy, sampler),
rect_instance_properties: rect_inst_props,
globals: globals_buffer,
out: screen_render_target.clone(),
};
// Glyph cache stuff.
let glyph_brush =
GlyphBrushBuilder::using_font_bytes(Font::default_font_bytes().to_vec()).build();
let (glyph_cache_width, glyph_cache_height) = glyph_brush.texture_dimensions();
let initial_contents =
vec![255; 4 * glyph_cache_width as usize * glyph_cache_height as usize];
let glyph_cache = ImageGeneric::make_raw(
&mut factory,
&sampler_info,
glyph_cache_width as u16,
glyph_cache_height as u16,
&initial_contents,
color_format,
debug_id,
)?;
let glyph_state = Rc::new(RefCell::new(spritebatch::SpriteBatch::new(
glyph_cache.clone(),
)));
// Set initial uniform values // Set initial uniform values
let left = 0.0; let left = 0.0;
let right = window_mode.width; let right = window_mode.width;
@ -264,6 +222,15 @@ impl GraphicsContextGeneric<GlBackendSpec> {
mvp_matrix: initial_projection.into(), mvp_matrix: initial_projection.into(),
}; };
let data = pipe::Data {
vbuf: quad_vertex_buffer.clone(),
mvp: globals.mvp_matrix.into(),
tex: (typed_thingy, sampler),
rect_instance_properties: rect_inst_props,
globals: globals_buffer,
out: screen_render_target.clone(),
};
let mut gfx = Self { let mut gfx = Self {
shader_globals: globals, shader_globals: globals,
projection: initial_projection, projection: initial_projection,
@ -272,7 +239,6 @@ impl GraphicsContextGeneric<GlBackendSpec> {
screen_rect: Rect::new(left, top, right - left, bottom - top), screen_rect: Rect::new(left, top, right - left, bottom - top),
color_format, color_format,
depth_format, depth_format,
srgb,
backend_spec: backend, backend_spec: backend,
window, window,
@ -293,10 +259,6 @@ impl GraphicsContextGeneric<GlBackendSpec> {
default_shader: shader.shader_id(), default_shader: shader.shader_id(),
current_shader: Rc::new(RefCell::new(None)), current_shader: Rc::new(RefCell::new(None)),
shaders: vec![draw], shaders: vec![draw],
glyph_brush,
glyph_cache,
glyph_state,
}; };
gfx.set_window_mode(window_mode)?; gfx.set_window_mode(window_mode)?;
@ -320,11 +282,11 @@ impl GraphicsContextGeneric<GlBackendSpec> {
// but still better than // but still better than
// having `winit` try to do the image loading for us. // having `winit` try to do the image loading for us.
// see https://github.com/tomaka/winit/issues/661 // see https://github.com/tomaka/winit/issues/661
pub(crate) fn load_icon(icon_file: &Path, filesystem: &mut Filesystem) -> GameResult<winit::Icon> { pub(crate) fn load_icon(icon_file: &Path, filesystem: &mut Filesystem) -> GameResult<winit::window::Icon> {
use ::image; use ::image;
use ::image::GenericImageView; use ::image::GenericImageView;
use std::io::Read; use std::io::Read;
use winit::Icon; use winit::window::Icon;
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut reader = filesystem.open(icon_file)?; let mut reader = filesystem.open(icon_file)?;
@ -338,14 +300,13 @@ pub(crate) fn load_icon(icon_file: &Path, filesystem: &mut Filesystem) -> GameRe
} }
impl<B> GraphicsContextGeneric<B> impl<B> GraphicsContextGeneric<B>
where where
B: BackendSpec + 'static, B: BackendSpec + 'static,
{ {
/// Sends the current value of the graphics context's shader globals /// Sends the current value of the graphics context's shader globals
/// to the graphics card. /// to the graphics card.
pub(crate) fn update_globals(&mut self) -> GameResult { pub(crate) fn update_globals(&mut self) -> GameResult {
self.encoder self.encoder.update_constant_buffer(&self.data.globals, &self.shader_globals);
.update_buffer(&self.data.globals, &[self.shader_globals], 0)?;
Ok(()) Ok(())
} }
@ -358,6 +319,7 @@ where
.last() .last()
.expect("Transform stack empty; should never happen"); .expect("Transform stack empty; should never happen");
let mvp = self.projection * modelview; let mvp = self.projection * modelview;
self.data.mvp = mvp.into();
self.shader_globals.mvp_matrix = mvp.into(); self.shader_globals.mvp_matrix = mvp.into();
} }
@ -406,7 +368,7 @@ where
pub(crate) fn update_instance_properties(&mut self, draw_params: DrawTransform) -> GameResult { pub(crate) fn update_instance_properties(&mut self, draw_params: DrawTransform) -> GameResult {
let mut new_draw_params = draw_params; let mut new_draw_params = draw_params;
new_draw_params.color = draw_params.color; new_draw_params.color = draw_params.color;
let properties = new_draw_params.to_instance_properties(self.srgb); let properties = new_draw_params.to_instance_properties();
self.encoder self.encoder
.update_buffer(&self.data.rect_instance_properties, &[properties], 0)?; .update_buffer(&self.data.rect_instance_properties, &[properties], 0)?;
Ok(()) Ok(())
@ -509,7 +471,7 @@ where
/// Sets window mode from a WindowMode object. /// Sets window mode from a WindowMode object.
pub(crate) fn set_window_mode(&mut self, mode: WindowMode) -> GameResult { pub(crate) fn set_window_mode(&mut self, mode: WindowMode) -> GameResult {
let window = &self.window; let window = &self.window.window();
window.set_maximized(mode.maximized); window.set_maximized(mode.maximized);
@ -522,7 +484,7 @@ where
} else { } else {
None None
}; };
window.set_min_dimensions(min_dimensions); window.set_min_inner_size(min_dimensions);
let max_dimensions = if mode.max_width > 0.0 && mode.max_height > 0.0 { let max_dimensions = if mode.max_width > 0.0 && mode.max_height > 0.0 {
Some(dpi::LogicalSize { Some(dpi::LogicalSize {
@ -532,9 +494,10 @@ where
} else { } else {
None None
}; };
window.set_max_dimensions(max_dimensions); window.set_max_inner_size(max_dimensions);
let monitor = window.get_current_monitor(); let monitor = window.current_monitor();
#[cfg(not(target_os = "android"))]
match mode.fullscreen_type { match mode.fullscreen_type {
FullscreenType::Windowed => { FullscreenType::Windowed => {
window.set_fullscreen(None); window.set_fullscreen(None);
@ -546,20 +509,23 @@ where
window.set_resizable(mode.resizable); window.set_resizable(mode.resizable);
} }
FullscreenType::True => { FullscreenType::True => {
window.set_fullscreen(Some(monitor)); window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
window.set_inner_size(dpi::LogicalSize { window.set_inner_size(dpi::LogicalSize {
width: f64::from(mode.width), width: f64::from(mode.width),
height: f64::from(mode.height), height: f64::from(mode.height),
}); });
} }
FullscreenType::Desktop => { FullscreenType::Desktop => {
let position = monitor.get_position(); if let Some(monitor) = monitor {
let dimensions = monitor.get_dimensions(); let position = monitor.position();
let hidpi_factor = window.get_hidpi_factor(); let dimensions = monitor.size();
window.set_fullscreen(None); let hidpi_factor = window.scale_factor();
window.set_decorations(false);
window.set_inner_size(dimensions.to_logical(hidpi_factor)); window.set_fullscreen(None);
window.set_position(position.to_logical(hidpi_factor)); window.set_decorations(false);
window.set_inner_size(dimensions.to_logical::<f64>(hidpi_factor));
window.set_outer_position(position.to_logical::<f64>(hidpi_factor));
}
} }
} }
Ok(()) Ok(())

View File

@ -244,14 +244,9 @@ impl From<DrawParam> for DrawTransform {
} }
impl DrawTransform { impl DrawTransform {
pub(crate) fn to_instance_properties(&self, srgb: bool) -> InstanceProperties { pub(crate) fn to_instance_properties(&self) -> InstanceProperties {
let mat: [[f32; 4]; 4] = self.matrix.into(); let mat: [[f32; 4]; 4] = self.matrix.into();
let color: [f32; 4] = if srgb { let color: [f32; 4] = self.color.into();
let linear_color: types::LinearColor = self.color.into();
linear_color.into()
} else {
self.color.into()
};
InstanceProperties { InstanceProperties {
src: self.src.into(), src: self.src.into(),
col1: mat[0], col1: mat[0],

View File

@ -0,0 +1,200 @@
//! Extensions for [glutin](https://crates.io/crates/glutin) to initialize & update old school
//! [gfx](https://crates.io/crates/gfx). _An alternative to gfx_window_glutin_.
//!
//! # Example
//! ```no_run
//! use old_school_gfx_glutin_ext::*;
//!
//! type ColorFormat = gfx::format::Srgba8;
//! type DepthFormat = gfx::format::DepthStencil;
//!
//! # fn main() -> Result<(), glutin::CreationError> {
//! # let event_loop = glutin::event_loop::EventLoop::new();
//! # let window_config = glutin::window::WindowBuilder::new();
//! // Initialize
//! let (window_ctx, mut device, mut factory, mut main_color, mut main_depth) =
//! glutin::ContextBuilder::new()
//! .with_gfx_color_depth::<ColorFormat, DepthFormat>()
//! .build_windowed(window_config, &event_loop)?
//! .init_gfx::<ColorFormat, DepthFormat>();
//!
//! # let new_size = glutin::dpi::PhysicalSize::new(1, 1);
//! // Update, ie after a resize
//! window_ctx.update_gfx(&mut main_color, &mut main_depth);
//! # Ok(()) }
//! ```
#![allow(unsafe_code)]
use gfx_core::{
format::{ChannelType, DepthFormat, Format, RenderFormat},
handle::{DepthStencilView, RawDepthStencilView, RawRenderTargetView, RenderTargetView},
memory::Typed,
texture,
};
use gfx_device_gl::Resources as R;
use glutin::{NotCurrent, PossiblyCurrent};
type GfxInitTuple<Color, Depth> = (
glutin::WindowedContext<PossiblyCurrent>,
gfx_device_gl::Device,
gfx_device_gl::Factory,
RenderTargetView<R, Color>,
DepthStencilView<R, Depth>,
);
pub trait ContextBuilderExt {
/// Calls `with_pixel_format` & `with_srgb` according to the color format.
fn with_gfx_color<Color: RenderFormat>(self) -> Self;
/// Calls `with_pixel_format` & `with_srgb` according to the color format.
fn with_gfx_color_raw(self, color_format: Format) -> Self;
/// Calls `with_depth_buffer` & `with_stencil_buffer` according to the depth format.
fn with_gfx_depth<Depth: DepthFormat>(self) -> Self;
/// Calls `with_depth_buffer` & `with_stencil_buffer` according to the depth format.
fn with_gfx_depth_raw(self, ds_format: Format) -> Self;
/// Calls `with_gfx_color` & `with_gfx_depth`.
fn with_gfx_color_depth<Color: RenderFormat, Depth: DepthFormat>(self) -> Self;
}
impl ContextBuilderExt for glutin::ContextBuilder<'_, NotCurrent> {
fn with_gfx_color<Color: RenderFormat>(self) -> Self {
self.with_gfx_color_raw(Color::get_format())
}
fn with_gfx_color_raw(self, Format(surface, channel): Format) -> Self {
let color_total_bits = surface.get_total_bits();
let alpha_bits = surface.get_alpha_stencil_bits();
self.with_pixel_format(color_total_bits - alpha_bits, alpha_bits)
}
fn with_gfx_depth<Depth: DepthFormat>(self) -> Self {
self.with_gfx_depth_raw(Depth::get_format())
}
fn with_gfx_depth_raw(self, Format(surface, _): Format) -> Self {
let depth_total_bits = surface.get_total_bits();
let stencil_bits = surface.get_alpha_stencil_bits();
self.with_depth_buffer(depth_total_bits - stencil_bits)
.with_stencil_buffer(stencil_bits)
}
fn with_gfx_color_depth<Color: RenderFormat, Depth: DepthFormat>(self) -> Self {
self.with_gfx_color::<Color>().with_gfx_depth::<Depth>()
}
}
pub trait WindowInitExt {
/// Make the context current, creates the gfx device, factory and views.
fn init_gfx<Color: RenderFormat, Depth: DepthFormat>(self) -> GfxInitTuple<Color, Depth>;
/// Make the context current, creates the gfx device, factory and views.
fn init_gfx_raw(
self,
color_format: Format,
ds_format: Format,
) -> (
glutin::WindowedContext<PossiblyCurrent>,
gfx_device_gl::Device,
gfx_device_gl::Factory,
RawRenderTargetView<R>,
RawDepthStencilView<R>,
);
}
impl WindowInitExt for glutin::WindowedContext<NotCurrent> {
fn init_gfx<Color: RenderFormat, Depth: DepthFormat>(self) -> GfxInitTuple<Color, Depth> {
let (window, device, factory, color_view, ds_view) =
self.init_gfx_raw(Color::get_format(), Depth::get_format());
(
window,
device,
factory,
Typed::new(color_view),
Typed::new(ds_view),
)
}
fn init_gfx_raw(
self,
color_format: Format,
ds_format: Format,
) -> (
glutin::WindowedContext<PossiblyCurrent>,
gfx_device_gl::Device,
gfx_device_gl::Factory,
RawRenderTargetView<R>,
RawDepthStencilView<R>,
) {
let window = unsafe { self.make_current().unwrap() };
let (device, factory) =
gfx_device_gl::create(|s| window.get_proc_address(s) as *const std::os::raw::c_void);
let dim = get_window_dimensions(&window);
let (color_view, ds_view) =
gfx_device_gl::create_main_targets_raw(dim, color_format.0, ds_format.0);
(window, device, factory, color_view, ds_view)
}
}
pub trait WindowUpdateExt {
/// Recreates the views if the dimensions have changed.
fn update_gfx<Color: RenderFormat, Depth: DepthFormat>(
&self,
color_view: &mut RenderTargetView<R, Color>,
ds_view: &mut DepthStencilView<R, Depth>,
);
/// Return new main target views if the window resolution has changed from the old dimensions.
fn updated_views_raw(
&self,
old_dimensions: texture::Dimensions,
color_format: Format,
ds_format: Format,
) -> Option<(RawRenderTargetView<R>, RawDepthStencilView<R>)>;
}
impl WindowUpdateExt for glutin::WindowedContext<PossiblyCurrent> {
fn update_gfx<Color: RenderFormat, Depth: DepthFormat>(
&self,
color_view: &mut RenderTargetView<R, Color>,
ds_view: &mut DepthStencilView<R, Depth>,
) {
let dim = color_view.get_dimensions();
debug_assert_eq!(dim, ds_view.get_dimensions());
if let Some((cv, dv)) =
self.updated_views_raw(dim, Color::get_format(), Depth::get_format())
{
*color_view = Typed::new(cv);
*ds_view = Typed::new(dv);
}
}
fn updated_views_raw(
&self,
old_dimensions: texture::Dimensions,
color_format: Format,
ds_format: Format,
) -> Option<(RawRenderTargetView<R>, RawDepthStencilView<R>)> {
let dim = get_window_dimensions(self);
if dim != old_dimensions {
Some(gfx_device_gl::create_main_targets_raw(
dim,
color_format.0,
ds_format.0,
))
} else {
None
}
}
}
fn get_window_dimensions(ctx: &glutin::WindowedContext<PossiblyCurrent>) -> texture::Dimensions {
let window = ctx.window();
let (width, height) = {
let size = window.inner_size();
(size.width as _, size.height as _)
};
let aa = ctx.get_pixel_format().multisampling.unwrap_or(0) as texture::NumSamples;
(width, height, 1, aa.into())
}

View File

@ -25,11 +25,17 @@ use std::path::Path;
use std::u16; use std::u16;
use gfx; use gfx;
use gfx::texture;
use gfx::Device; use gfx::Device;
use gfx::Factory; use gfx::Factory;
use gfx::texture;
use gfx_device_gl; use gfx_device_gl;
use glutin; use glutin;
use glutin::{NotCurrent, PossiblyCurrent};
pub use mint;
pub(crate) use nalgebra as na;
use winit::event_loop::EventLoopWindowTarget;
use glutin_ext::*;
use crate::ggez::conf; use crate::ggez::conf;
use crate::ggez::conf::WindowMode; use crate::ggez::conf::WindowMode;
@ -37,29 +43,24 @@ use crate::ggez::context::Context;
use crate::ggez::context::DebugId; use crate::ggez::context::DebugId;
use crate::ggez::GameError; use crate::ggez::GameError;
use crate::ggez::GameResult; use crate::ggez::GameResult;
pub(crate) mod canvas;
pub(crate) mod context;
pub(crate) mod drawparam;
pub(crate) mod image;
pub(crate) mod mesh;
pub(crate) mod shader;
pub(crate) mod text;
pub(crate) mod types;
pub use mint;
pub(crate) use nalgebra as na;
pub mod spritebatch;
pub use crate::ggez::graphics::canvas::*; pub use crate::ggez::graphics::canvas::*;
pub use crate::ggez::graphics::drawparam::*; pub use crate::ggez::graphics::drawparam::*;
pub use crate::ggez::graphics::image::*; pub use crate::ggez::graphics::image::*;
pub use crate::ggez::graphics::mesh::*; pub use crate::ggez::graphics::mesh::*;
pub use crate::ggez::graphics::shader::*; pub use crate::ggez::graphics::shader::*;
pub use crate::ggez::graphics::text::*;
pub use crate::ggez::graphics::types::*; pub use crate::ggez::graphics::types::*;
pub(crate) mod canvas;
pub(crate) mod context;
pub(crate) mod drawparam;
pub(crate) mod glutin_ext;
pub(crate) mod image;
pub(crate) mod mesh;
pub(crate) mod shader;
pub(crate) mod types;
pub mod spritebatch;
// This isn't really particularly nice, but it's only used // This isn't really particularly nice, but it's only used
// in a couple places and it's not very easy to change or configure. // in a couple places and it's not very easy to change or configure.
// Since the next major project is "rewrite the graphics engine" I think // Since the next major project is "rewrite the graphics engine" I think
@ -68,9 +69,12 @@ pub use crate::ggez::graphics::types::*;
// It exists basically because gfx-rs is incomplete and we can't *always* // It exists basically because gfx-rs is incomplete and we can't *always*
// specify texture formats and such entirely at runtime, which we need to // specify texture formats and such entirely at runtime, which we need to
// do to make sRGB handling work properly. // do to make sRGB handling work properly.
pub(crate) type BuggoSurfaceFormat = gfx::format::Srgba8; pub(crate) type BuggoSurfaceFormat = gfx::format::Rgba8;
type ShaderResourceType = [f32; 4]; type ShaderResourceType = [f32; 4];
type ColorFormat = gfx::format::Rgba8;
type DepthFormat = gfx::format::DepthStencil;
/// A trait providing methods for working with a particular backend, such as OpenGL, /// A trait providing methods for working with a particular backend, such as OpenGL,
/// with associated gfx-rs types for that backend. As a user you probably /// with associated gfx-rs types for that backend. As a user you probably
/// don't need to touch this unless you want to write a new graphics backend /// don't need to touch this unless you want to write a new graphics backend
@ -83,7 +87,7 @@ pub trait BackendSpec: fmt::Debug {
/// gfx command buffer type /// gfx command buffer type
type CommandBuffer: gfx::CommandBuffer<Self::Resources>; type CommandBuffer: gfx::CommandBuffer<Self::Resources>;
/// gfx device type /// gfx device type
type Device: gfx::Device<Resources = Self::Resources, CommandBuffer = Self::CommandBuffer>; type Device: gfx::Device<Resources=Self::Resources, CommandBuffer=Self::CommandBuffer>;
/// A helper function to take a RawShaderResourceView and turn it into a typed one based on /// A helper function to take a RawShaderResourceView and turn it into a typed one based on
/// the surface type defined in a `BackendSpec`. /// the surface type defined in a `BackendSpec`.
@ -104,27 +108,6 @@ pub trait BackendSpec: fmt::Debug {
typed_view typed_view
} }
/// Helper function that turns a raw to typed texture.
/// A bit hacky since we can't really specify surface formats as part
/// of this that well, alas. There's some functions, like
/// `gfx::Encoder::update_texture()`, that don't seem to have a `_raw()`
/// counterpart, so we need this, so we need `BuggoSurfaceFormat` to
/// keep fixed at compile time what texture format we're actually using.
/// Oh well!
fn raw_to_typed_texture(
&self,
texture_view: gfx::handle::RawTexture<Self::Resources>,
) -> gfx::handle::Texture<
<Self as BackendSpec>::Resources,
<BuggoSurfaceFormat as gfx::format::Formatted>::Surface,
> {
let typed_view: gfx::handle::Texture<
_,
<BuggoSurfaceFormat as gfx::format::Formatted>::Surface,
> = gfx::memory::Typed::new(texture_view);
typed_view
}
/// Returns the version of the backend, `(major, minor)`. /// Returns the version of the backend, `(major, minor)`.
/// ///
/// So for instance if the backend is using OpenGL version 3.2, /// So for instance if the backend is using OpenGL version 3.2,
@ -144,14 +127,14 @@ pub trait BackendSpec: fmt::Debug {
/// Creates the window. /// Creates the window.
fn init<'a>( fn init<'a>(
&self, &self,
window_builder: glutin::WindowBuilder, window_builder: glutin::window::WindowBuilder,
gl_builder: glutin::ContextBuilder<'a>, gl_builder: glutin::ContextBuilder<'a, NotCurrent>,
events_loop: &glutin::EventsLoop, events_loop: &winit::event_loop::EventLoopWindowTarget<()>,
color_format: gfx::format::Format, color_format: gfx::format::Format,
depth_format: gfx::format::Format, depth_format: gfx::format::Format,
) -> Result< ) -> Result<
( (
glutin::WindowedContext, glutin::WindowedContext<PossiblyCurrent>,
Self::Device, Self::Device,
Self::Factory, Self::Factory,
gfx::handle::RawRenderTargetView<Self::Resources>, gfx::handle::RawRenderTargetView<Self::Resources>,
@ -170,7 +153,7 @@ pub trait BackendSpec: fmt::Debug {
depth_view: &gfx::handle::RawDepthStencilView<Self::Resources>, depth_view: &gfx::handle::RawDepthStencilView<Self::Resources>,
color_format: gfx::format::Format, color_format: gfx::format::Format,
depth_format: gfx::format::Format, depth_format: gfx::format::Format,
window: &glutin::WindowedContext, window: &glutin::WindowedContext<PossiblyCurrent>,
) -> Option<( ) -> Option<(
gfx::handle::RawRenderTargetView<Self::Resources>, gfx::handle::RawRenderTargetView<Self::Resources>,
gfx::handle::RawDepthStencilView<Self::Resources>, gfx::handle::RawDepthStencilView<Self::Resources>,
@ -231,12 +214,12 @@ impl BackendSpec for GlBackendSpec {
fn shaders(&self) -> (&'static [u8], &'static [u8]) { fn shaders(&self) -> (&'static [u8], &'static [u8]) {
match self.api { match self.api {
glutin::Api::OpenGl => ( glutin::Api::OpenGl => (
include_bytes!("shader/basic_150.glslv"), include_bytes!("shader/basic_150.vert.glsl"),
include_bytes!("shader/basic_150.glslf"), include_bytes!("shader/basic_150.frag.glsl"),
), ),
glutin::Api::OpenGlEs => ( glutin::Api::OpenGlEs => (
include_bytes!("shader/basic_es300.glslv"), include_bytes!("shader/basic_es100.vert.glsl"),
include_bytes!("shader/basic_es300.glslf"), include_bytes!("shader/basic_es100.frag.glsl"),
), ),
a => panic!("Unsupported API: {:?}, should never happen", a), a => panic!("Unsupported API: {:?}, should never happen", a),
} }
@ -244,14 +227,14 @@ impl BackendSpec for GlBackendSpec {
fn init<'a>( fn init<'a>(
&self, &self,
window_builder: glutin::WindowBuilder, window_builder: glutin::window::WindowBuilder,
gl_builder: glutin::ContextBuilder<'a>, gl_builder: glutin::ContextBuilder<'a, NotCurrent>,
events_loop: &glutin::EventsLoop, events_loop: &EventLoopWindowTarget<()>,
color_format: gfx::format::Format, color_format: gfx::format::Format,
depth_format: gfx::format::Format, depth_format: gfx::format::Format,
) -> Result< ) -> Result<
( (
glutin::WindowedContext, glutin::WindowedContext<PossiblyCurrent>,
Self::Device, Self::Device,
Self::Factory, Self::Factory,
gfx::handle::RawRenderTargetView<Self::Resources>, gfx::handle::RawRenderTargetView<Self::Resources>,
@ -259,13 +242,10 @@ impl BackendSpec for GlBackendSpec {
), ),
glutin::CreationError, glutin::CreationError,
> { > {
gfx_window_glutin::init_raw( Ok(gl_builder
window_builder, .with_gfx_color_depth::<ColorFormat, DepthFormat>()
gl_builder, .build_windowed(window_builder, &events_loop)?
events_loop, .init_gfx_raw(color_format, depth_format))
color_format,
depth_format,
)
} }
fn info(&self, device: &Self::Device) -> String { fn info(&self, device: &Self::Device) -> String {
@ -289,7 +269,7 @@ impl BackendSpec for GlBackendSpec {
depth_view: &gfx::handle::RawDepthStencilView<Self::Resources>, depth_view: &gfx::handle::RawDepthStencilView<Self::Resources>,
color_format: gfx::format::Format, color_format: gfx::format::Format,
depth_format: gfx::format::Format, depth_format: gfx::format::Format,
window: &glutin::WindowedContext, window: &glutin::WindowedContext<PossiblyCurrent>,
) -> Option<( ) -> Option<(
gfx::handle::RawRenderTargetView<Self::Resources>, gfx::handle::RawRenderTargetView<Self::Resources>,
gfx::handle::RawDepthStencilView<Self::Resources>, gfx::handle::RawDepthStencilView<Self::Resources>,
@ -298,8 +278,7 @@ impl BackendSpec for GlBackendSpec {
// gfx_window_glutin::update_views() // gfx_window_glutin::update_views()
let dim = color_view.get_dimensions(); let dim = color_view.get_dimensions();
assert_eq!(dim, depth_view.get_dimensions()); assert_eq!(dim, depth_view.get_dimensions());
if let Some((cv, dv)) = if let Some((cv, dv)) = window.updated_views_raw(dim, color_format, depth_format)
gfx_window_glutin::update_views_raw(window, dim, color_format, depth_format)
{ {
Some((cv, dv)) Some((cv, dv))
} else { } else {
@ -366,6 +345,7 @@ gfx_defines! {
// breaks the gfx_defines! macro though. :-( // breaks the gfx_defines! macro though. :-(
pipeline pipe { pipeline pipe {
vbuf: gfx::VertexBuffer<Vertex> = (), vbuf: gfx::VertexBuffer<Vertex> = (),
mvp: gfx::Global<[[f32; 4]; 4]> = "u_MVP",
tex: gfx::TextureSampler<[f32; 4]> = "t_Texture", tex: gfx::TextureSampler<[f32; 4]> = "t_Texture",
globals: gfx::ConstantBuffer<Globals> = "Globals", globals: gfx::ConstantBuffer<Globals> = "Globals",
rect_instance_properties: gfx::InstanceBuffer<InstanceProperties> = (), rect_instance_properties: gfx::InstanceBuffer<InstanceProperties> = (),
@ -373,7 +353,7 @@ gfx_defines! {
// pipeline init values in `shader::create_shader()`. // pipeline init values in `shader::create_shader()`.
out: gfx::RawRenderTarget = out: gfx::RawRenderTarget =
("Target0", ("Target0",
gfx::format::Format(gfx::format::SurfaceType::R8_G8_B8_A8, gfx::format::ChannelType::Srgb), gfx::format::Format(gfx::format::SurfaceType::R8_G8_B8_A8, gfx::format::ChannelType::Unorm),
gfx::state::ColorMask::all(), Some(gfx::preset::blend::ALPHA) gfx::state::ColorMask::all(), Some(gfx::preset::blend::ALPHA)
), ),
} }
@ -409,18 +389,19 @@ impl Default for InstanceProperties {
} }
} }
} }
/// A structure for conveniently storing `Sampler`'s, based off /// A structure for conveniently storing `Sampler`'s, based off
/// their `SamplerInfo`. /// their `SamplerInfo`.
pub(crate) struct SamplerCache<B> pub(crate) struct SamplerCache<B>
where where
B: BackendSpec, B: BackendSpec,
{ {
samplers: HashMap<texture::SamplerInfo, gfx::handle::Sampler<B::Resources>>, samplers: HashMap<texture::SamplerInfo, gfx::handle::Sampler<B::Resources>>,
} }
impl<B> SamplerCache<B> impl<B> SamplerCache<B>
where where
B: BackendSpec, B: BackendSpec,
{ {
fn new() -> Self { fn new() -> Self {
SamplerCache { SamplerCache {
@ -467,17 +448,16 @@ impl From<gfx::buffer::CreationError> for GameError {
/// Clear the screen to the background color. /// Clear the screen to the background color.
pub fn clear(ctx: &mut Context, color: Color) { pub fn clear(ctx: &mut Context, color: Color) {
let gfx = &mut ctx.gfx_context; let gfx = &mut ctx.gfx_context;
let linear_color: types::LinearColor = color.into(); let c: [f32; 4] = color.into();
let c: [f32; 4] = linear_color.into();
gfx.encoder.clear_raw(&gfx.data.out, c.into()); gfx.encoder.clear_raw(&gfx.data.out, c.into());
} }
/// Draws the given `Drawable` object to the screen by calling its /// Draws the given `Drawable` object to the screen by calling its
/// [`draw()`](trait.Drawable.html#tymethod.draw) method. /// [`draw()`](trait.Drawable.html#tymethod.draw) method.
pub fn draw<D, T>(ctx: &mut Context, drawable: &D, params: T) -> GameResult pub fn draw<D, T>(ctx: &mut Context, drawable: &D, params: T) -> GameResult
where where
D: Drawable, D: Drawable,
T: Into<DrawParam>, T: Into<DrawParam>,
{ {
let params = params.into(); let params = params.into();
drawable.draw(ctx, params) drawable.draw(ctx, params)
@ -646,8 +626,8 @@ pub fn set_screen_coordinates(context: &mut Context, rect: Rect) -> GameResult {
/// after calling this to apply these changes and recalculate the /// after calling this to apply these changes and recalculate the
/// underlying MVP matrix. /// underlying MVP matrix.
pub fn set_projection<M>(context: &mut Context, proj: M) pub fn set_projection<M>(context: &mut Context, proj: M)
where where
M: Into<mint::ColumnMatrix4<f32>>, M: Into<mint::ColumnMatrix4<f32>>,
{ {
let proj = Matrix4::from(proj.into()); let proj = Matrix4::from(proj.into());
let gfx = &mut context.gfx_context; let gfx = &mut context.gfx_context;
@ -660,8 +640,8 @@ where
/// after calling this to apply these changes and recalculate the /// after calling this to apply these changes and recalculate the
/// underlying MVP matrix. /// underlying MVP matrix.
pub fn mul_projection<M>(context: &mut Context, transform: M) pub fn mul_projection<M>(context: &mut Context, transform: M)
where where
M: Into<mint::ColumnMatrix4<f32>>, M: Into<mint::ColumnMatrix4<f32>>,
{ {
let transform = Matrix4::from(transform.into()); let transform = Matrix4::from(transform.into());
let gfx = &mut context.gfx_context; let gfx = &mut context.gfx_context;
@ -698,8 +678,8 @@ pub fn projection(context: &Context) -> mint::ColumnMatrix4<f32> {
/// # } /// # }
/// ``` /// ```
pub fn push_transform<M>(context: &mut Context, transform: Option<M>) pub fn push_transform<M>(context: &mut Context, transform: Option<M>)
where where
M: Into<mint::ColumnMatrix4<f32>>, M: Into<mint::ColumnMatrix4<f32>>,
{ {
let transform = transform.map(|transform| Matrix4::from(transform.into())); let transform = transform.map(|transform| Matrix4::from(transform.into()));
let gfx = &mut context.gfx_context; let gfx = &mut context.gfx_context;
@ -746,8 +726,8 @@ pub fn pop_transform(context: &mut Context) {
/// # } /// # }
/// ``` /// ```
pub fn set_transform<M>(context: &mut Context, transform: M) pub fn set_transform<M>(context: &mut Context, transform: M)
where where
M: Into<mint::ColumnMatrix4<f32>>, M: Into<mint::ColumnMatrix4<f32>>,
{ {
let transform = transform.into(); let transform = transform.into();
let gfx = &mut context.gfx_context; let gfx = &mut context.gfx_context;
@ -782,8 +762,8 @@ pub fn transform(context: &Context) -> mint::ColumnMatrix4<f32> {
/// # } /// # }
/// ``` /// ```
pub fn mul_transform<M>(context: &mut Context, transform: M) pub fn mul_transform<M>(context: &mut Context, transform: M)
where where
M: Into<mint::ColumnMatrix4<f32>>, M: Into<mint::ColumnMatrix4<f32>>,
{ {
let transform = Matrix4::from(transform.into()); let transform = Matrix4::from(transform.into());
let gfx = &mut context.gfx_context; let gfx = &mut context.gfx_context;
@ -863,20 +843,21 @@ pub fn set_window_icon<P: AsRef<Path>>(context: &mut Context, path: Option<P>) -
} }
None => None, None => None,
}; };
context.gfx_context.window.set_window_icon(icon);
context.gfx_context.window.window().set_window_icon(icon);
Ok(()) Ok(())
} }
/// Sets the window title. /// Sets the window title.
pub fn set_window_title(context: &Context, title: &str) { pub fn set_window_title(context: &Context, title: &str) {
context.gfx_context.window.set_title(title); context.gfx_context.window.window().set_title(title);
} }
/// Returns a reference to the Glutin window. /// Returns a reference to the Glutin window.
/// Ideally you should not need to use this because ggez /// Ideally you should not need to use this because ggez
/// would provide all the functions you need without having /// would provide all the functions you need without having
/// to dip into Glutin itself. But life isn't always ideal. /// to dip into Glutin itself. But life isn't always ideal.
pub fn window(context: &Context) -> &glutin::WindowedContext { pub fn window(context: &Context) -> &glutin::WindowedContext<PossiblyCurrent> {
let gfx = &context.gfx_context; let gfx = &context.gfx_context;
&gfx.window &gfx.window
} }
@ -886,20 +867,16 @@ pub fn window(context: &Context) -> &glutin::WindowedContext {
/// Returns zeros if the window doesn't exist. /// Returns zeros if the window doesn't exist.
pub fn size(context: &Context) -> (f32, f32) { pub fn size(context: &Context) -> (f32, f32) {
let gfx = &context.gfx_context; let gfx = &context.gfx_context;
gfx.window let size = gfx.window.window().outer_size();
.get_outer_size() (size.width as f32, size.height as f32)
.map(|logical_size| (logical_size.width as f32, logical_size.height as f32))
.unwrap_or((0.0, 0.0))
} }
/// Returns the size of the window's underlying drawable in pixels as (width, height). /// Returns the size of the window's underlying drawable in pixels as (width, height).
/// Returns zeros if window doesn't exist. /// Returns zeros if window doesn't exist.
pub fn drawable_size(context: &Context) -> (f32, f32) { pub fn drawable_size(context: &Context) -> (f32, f32) {
let gfx = &context.gfx_context; let gfx = &context.gfx_context;
gfx.window let size = gfx.window.window().inner_size();
.get_inner_size() (size.width as f32, size.height as f32)
.map(|logical_size| (logical_size.width as f32, logical_size.height as f32))
.unwrap_or((0.0, 0.0))
} }
/// Returns raw `gfx-rs` state objects, if you want to use `gfx-rs` to write /// Returns raw `gfx-rs` state objects, if you want to use `gfx-rs` to write
@ -971,10 +948,12 @@ pub fn transform_rect(rect: Rect, param: DrawParam) -> Rect {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::graphics::{transform_rect, DrawParam, Rect};
use approx::assert_relative_eq;
use std::f32::consts::PI; use std::f32::consts::PI;
use approx::assert_relative_eq;
use crate::graphics::{DrawParam, Rect, transform_rect};
#[test] #[test]
fn headless_test_transform_rect() { fn headless_test_transform_rect() {
{ {

View File

@ -0,0 +1,13 @@
#version 100
uniform mediump sampler2D t_Texture;
varying mediump vec2 v_Uv;
varying mediump vec4 v_Color;
//uniform mediump mat4 u_MVP;
mediump vec4 Target0;
void main() {
gl_FragColor = texture2D(t_Texture, v_Uv) * v_Color;
}

View File

@ -0,0 +1,26 @@
#version 100
attribute mediump vec2 a_Pos;
attribute mediump vec2 a_Uv;
attribute mediump vec4 a_VertColor;
attribute mediump vec4 a_Src;
attribute mediump vec4 a_TCol1;
attribute mediump vec4 a_TCol2;
attribute mediump vec4 a_TCol3;
attribute mediump vec4 a_TCol4;
attribute mediump vec4 a_Color;
uniform mediump mat4 u_MVP;
varying mediump vec2 v_Uv;
varying mediump vec4 v_Color;
void main() {
v_Uv = a_Uv * a_Src.zw + a_Src.xy;
v_Color = a_Color * a_VertColor;
mat4 instance_transform = mat4(a_TCol1, a_TCol2, a_TCol3, a_TCol4);
vec4 position = instance_transform * vec4(a_Pos, 0.0, 1.0);
gl_Position = u_MVP * position;
}

View File

@ -1,14 +0,0 @@
#version 300 es
uniform mediump sampler2D t_Texture;
in mediump vec2 v_Uv;
in mediump vec4 v_Color;
out mediump vec4 Target0;
layout (std140) uniform Globals {
mediump mat4 u_MVP;
};
void main() {
Target0 = texture(t_Texture, v_Uv) * v_Color;
}

View File

@ -1,28 +0,0 @@
#version 300 es
in mediump vec2 a_Pos;
in mediump vec2 a_Uv;
in mediump vec4 a_VertColor;
in mediump vec4 a_Src;
in mediump vec4 a_TCol1;
in mediump vec4 a_TCol2;
in mediump vec4 a_TCol3;
in mediump vec4 a_TCol4;
in mediump vec4 a_Color;
layout (std140) uniform Globals {
mediump mat4 u_MVP;
};
out mediump vec2 v_Uv;
out mediump vec4 v_Color;
void main() {
v_Uv = a_Uv * a_Src.zw + a_Src.xy;
v_Color = a_Color * a_VertColor;
mat4 instance_transform = mat4(a_TCol1, a_TCol2, a_TCol3, a_TCol4);
vec4 position = instance_transform * vec4(a_Pos, 0.0, 1.0);
gl_Position = u_MVP * position;
}

View File

@ -1,16 +0,0 @@
#version 150
uniform sampler2D font_tex;
in vec2 f_tex_pos;
in vec4 f_color;
out vec4 Target0;
void main() {
float alpha = texture(font_tex, f_tex_pos).r;
if (alpha <= 0.0) {
discard;
}
Target0 = f_color * vec4(1.0, 1.0, 1.0, alpha);
}

View File

@ -1,43 +0,0 @@
#version 150
uniform mat4 transform;
in vec3 left_top;
in vec2 right_bottom;
in vec2 tex_left_top;
in vec2 tex_right_bottom;
in vec4 color;
out vec2 f_tex_pos;
out vec4 f_color;
// generate positional data based on vertex ID
void main() {
vec2 pos = vec2(0.0);
float left = left_top.x;
float right = right_bottom.x;
float top = left_top.y;
float bottom = right_bottom.y;
switch (gl_VertexID) {
case 0:
pos = vec2(left, top);
f_tex_pos = tex_left_top;
break;
case 1:
pos = vec2(right, top);
f_tex_pos = vec2(tex_right_bottom.x, tex_left_top.y);
break;
case 2:
pos = vec2(left, bottom);
f_tex_pos = vec2(tex_left_top.x, tex_right_bottom.y);
break;
case 3:
pos = vec2(right, bottom);
f_tex_pos = tex_right_bottom;
break;
}
f_color = color;
gl_Position = transform * vec4(pos, left_top.z, 1.0);
}

View File

@ -108,7 +108,7 @@ impl SpriteBatch {
new_param.scale = real_scale.into(); new_param.scale = real_scale.into();
new_param.color = new_param.color; new_param.color = new_param.color;
let primitive_param = graphics::DrawTransform::from(new_param); let primitive_param = graphics::DrawTransform::from(new_param);
primitive_param.to_instance_properties(ctx.gfx_context.is_srgb()) primitive_param.to_instance_properties()
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View File

@ -1,717 +0,0 @@
use std::borrow::Cow;
use std::cell::RefCell;
use std::f32;
use std::fmt;
use std::io::Read;
use std::path;
use glyph_brush::{self, FontId, Layout, SectionText, VariedSection};
pub use glyph_brush::{HorizontalAlign as Align, rusttype::Scale};
use glyph_brush::GlyphPositioner;
use mint;
use crate::ggez::graphics::{BlendMode, Color, Drawable, DrawParam, FilterMode, Rect, WHITE, draw, Image, BackendSpec, GlBackendSpec, Point2};
use crate::ggez::{Context, GameResult};
use gfx::texture::ImageInfoCommon;
/// Default size for fonts.
pub const DEFAULT_FONT_SCALE: f32 = 16.0;
/// A handle referring to a loaded Truetype font.
///
/// This is just an integer referring to a loaded font stored in the
/// `Context`, so is cheap to copy. Note that fonts are cached and
/// currently never *removed* from the cache, since that would
/// invalidate the whole cache and require re-loading all the other
/// fonts. So, you do not want to load a font more than once.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Font {
font_id: FontId,
// Add DebugId? It makes Font::default() less convenient.
}
/// A piece of text with optional color, font and font scale information.
/// Drawing text generally involves one or more of these.
/// These options take precedence over any similar field/argument.
/// Implements `From` for `char`, `&str`, `String` and
/// `(String, Font, Scale)`.
#[derive(Clone, Debug)]
pub struct TextFragment {
/// Text string itself.
pub text: String,
/// Fragment's color, defaults to text's color.
pub color: Option<Color>,
/// Fragment's font, defaults to text's font.
pub font: Option<Font>,
/// Fragment's scale, defaults to text's scale.
pub scale: Option<Scale>,
}
impl Default for TextFragment {
fn default() -> Self {
TextFragment {
text: "".into(),
color: None,
font: None,
scale: None,
}
}
}
impl TextFragment {
/// Creates a new fragment from `String` or `&str`.
pub fn new<T: Into<Self>>(text: T) -> Self {
text.into()
}
/// Set fragment's color, overrides text's color.
pub fn color(mut self, color: Color) -> TextFragment {
self.color = Some(color);
self
}
/// Set fragment's font, overrides text's font.
pub fn font(mut self, font: Font) -> TextFragment {
self.font = Some(font);
self
}
/// Set fragment's scale, overrides text's scale.
pub fn scale(mut self, scale: Scale) -> TextFragment {
self.scale = Some(scale);
self
}
}
impl<'a> From<&'a str> for TextFragment {
fn from(text: &'a str) -> TextFragment {
TextFragment {
text: text.to_owned(),
..Default::default()
}
}
}
impl From<char> for TextFragment {
fn from(ch: char) -> TextFragment {
TextFragment {
text: ch.to_string(),
..Default::default()
}
}
}
impl From<String> for TextFragment {
fn from(text: String) -> TextFragment {
TextFragment {
text,
..Default::default()
}
}
}
impl<T> From<(T, Font, f32)> for TextFragment
where
T: Into<TextFragment>,
{
fn from((text, font, scale): (T, Font, f32)) -> TextFragment {
text.into().font(font).scale(Scale::uniform(scale))
}
}
/// Cached font metrics that we can keep attached to a `Text`
/// so we don't have to keep recalculating them.
#[derive(Clone, Debug)]
struct CachedMetrics {
string: Option<String>,
width: Option<u32>,
height: Option<u32>,
}
impl Default for CachedMetrics {
fn default() -> CachedMetrics {
CachedMetrics {
string: None,
width: None,
height: None,
}
}
}
/// Drawable text object. Essentially a list of [`TextFragment`](struct.TextFragment.html)'s
/// and some cached size information.
///
/// It implements [`Drawable`](trait.Drawable.html) so it can be drawn immediately with
/// [`graphics::draw()`](fn.draw.html), or many of them can be queued with [`graphics::queue_text()`](fn.queue_text.html)
/// and then all drawn at once with [`graphics::draw_queued_text()`](fn.draw_queued_text.html).
#[derive(Debug, Clone)]
pub struct Text {
fragments: Vec<TextFragment>,
blend_mode: Option<BlendMode>,
filter_mode: FilterMode,
bounds: Point2,
layout: Layout<glyph_brush::BuiltInLineBreaker>,
font_id: FontId,
font_scale: Scale,
cached_metrics: RefCell<CachedMetrics>,
}
impl Default for Text {
fn default() -> Self {
Text {
fragments: Vec::new(),
blend_mode: None,
filter_mode: FilterMode::Linear,
bounds: Point2::new(f32::INFINITY, f32::INFINITY),
layout: Layout::default(),
font_id: FontId::default(),
font_scale: Scale::uniform(DEFAULT_FONT_SCALE),
cached_metrics: RefCell::new(CachedMetrics::default()),
}
}
}
impl Text {
/// Creates a `Text` from a `TextFragment`.
///
/// ```rust
/// # use ggez::graphics::Text;
/// # fn main() {
/// let text = Text::new("foo");
/// # }
/// ```
pub fn new<F>(fragment: F) -> Text
where
F: Into<TextFragment>,
{
let mut text = Text::default();
let _ = text.add(fragment);
text
}
/// Appends a `TextFragment` to the `Text`.
pub fn add<F>(&mut self, fragment: F) -> &mut Text
where
F: Into<TextFragment>,
{
self.fragments.push(fragment.into());
self.invalidate_cached_metrics();
self
}
/// Returns a read-only slice of all `TextFragment`'s.
pub fn fragments(&self) -> &[TextFragment] {
&self.fragments
}
/// Returns a mutable slice with all fragments.
pub fn fragments_mut(&mut self) -> &mut [TextFragment] {
&mut self.fragments
}
/// Specifies rectangular dimensions to try and fit contents inside of,
/// by wrapping, and alignment within the bounds. To disable wrapping,
/// give it a layout with `f32::INF` for the x value.
pub fn set_bounds<P>(&mut self, bounds: P, alignment: Align) -> &mut Text
where
P: Into<mint::Point2<f32>>,
{
self.bounds = Point2::from(bounds.into());
if self.bounds.x == f32::INFINITY {
// Layouts don't make any sense if we don't wrap text at all.
self.layout = Layout::default();
} else {
self.layout = self.layout.h_align(alignment);
}
self.invalidate_cached_metrics();
self
}
/// Specifies text's font and font scale; used for fragments that don't have their own.
pub fn set_font(&mut self, font: Font, font_scale: Scale) -> &mut Text {
self.font_id = font.font_id;
self.font_scale = font_scale;
self.invalidate_cached_metrics();
self
}
/// Converts `Text` to a type `glyph_brush` can understand and queue.
fn generate_varied_section(
&self,
relative_dest: Point2,
color: Option<Color>,
) -> VariedSection {
let sections: Vec<SectionText> = self
.fragments
.iter()
.map(|fragment| {
let color = fragment.color.or(color).unwrap_or(WHITE);
let font_id = fragment
.font
.map(|font| font.font_id)
.unwrap_or(self.font_id);
let scale = fragment.scale.unwrap_or(self.font_scale);
SectionText {
text: &fragment.text,
color: <[f32; 4]>::from(color),
font_id,
scale,
}
})
.collect();
let relative_dest_x = {
// This positions text within bounds with relative_dest being to the left, always.
let mut dest_x = relative_dest.x;
if self.bounds.x != f32::INFINITY {
use glyph_brush::Layout::Wrap;
match self.layout {
Wrap {
h_align: Align::Center,
..
} => dest_x += self.bounds.x * 0.5,
Wrap {
h_align: Align::Right,
..
} => dest_x += self.bounds.x,
_ => (),
}
}
dest_x
};
let relative_dest = (relative_dest_x, relative_dest.y);
VariedSection {
screen_position: relative_dest,
bounds: (self.bounds.x, self.bounds.y),
layout: self.layout,
text: sections,
..Default::default()
}
}
fn invalidate_cached_metrics(&mut self) {
if let Ok(mut metrics) = self.cached_metrics.try_borrow_mut() {
*metrics = CachedMetrics::default();
// Returning early avoids a double-borrow in the "else"
// part.
return;
}
warn!("Cached metrics RefCell has been poisoned.");
self.cached_metrics = RefCell::new(CachedMetrics::default());
}
/// Returns the string that the text represents.
pub fn contents(&self) -> String {
if let Ok(metrics) = self.cached_metrics.try_borrow() {
if let Some(ref string) = metrics.string {
return string.clone();
}
}
let string_accm: String = self
.fragments
.iter()
.map(|frag| frag.text.as_str())
.collect();
if let Ok(mut metrics) = self.cached_metrics.try_borrow_mut() {
metrics.string = Some(string_accm.clone());
}
string_accm
}
/// Calculates, caches, and returns width and height of formatted and wrapped text.
fn calculate_dimensions(&self, context: &mut Context) -> (u32, u32) {
let mut max_width = 0;
let mut max_height = 0;
{
let varied_section = self.generate_varied_section(Point2::new(0.0, 0.0), None);
use glyph_brush::GlyphCruncher;
let glyphs = context.gfx_context.glyph_brush.glyphs(varied_section);
for positioned_glyph in glyphs {
if let Some(rect) = positioned_glyph.pixel_bounding_box() {
let font = positioned_glyph.font().expect("Glyph doesn't have a font");
let v_metrics = font.v_metrics(positioned_glyph.scale());
let max_y = positioned_glyph.position().y + positioned_glyph.scale().y
- v_metrics.ascent;
let max_y = max_y.ceil() as u32;
max_width = std::cmp::max(max_width, rect.max.x as u32);
max_height = std::cmp::max(max_height, max_y);
}
}
}
let (width, height) = (max_width, max_height);
if let Ok(mut metrics) = self.cached_metrics.try_borrow_mut() {
metrics.width = Some(width);
metrics.height = Some(height);
}
(width, height)
}
/// Returns the width and height of the formatted and wrapped text.
pub fn dimensions(&self, context: &mut Context) -> (u32, u32) {
if let Ok(metrics) = self.cached_metrics.try_borrow() {
if let (Some(width), Some(height)) = (metrics.width, metrics.height) {
return (width, height);
}
}
self.calculate_dimensions(context)
}
/// Returns the width of formatted and wrapped text, in screen coordinates.
pub fn width(&self, context: &mut Context) -> u32 {
self.dimensions(context).0
}
/// Returns the height of formatted and wrapped text, in screen coordinates.
pub fn height(&self, context: &mut Context) -> u32 {
self.dimensions(context).1
}
}
impl Drawable for Text {
fn draw(&self, ctx: &mut Context, param: DrawParam) -> GameResult {
// Converts fraction-of-bounding-box to screen coordinates, as required by `draw_queued()`.
queue_text(ctx, self, Point2::new(0.0, 0.0), Some(param.color));
draw_queued_text(ctx, param, self.blend_mode, self.filter_mode)
}
fn dimensions(&self, ctx: &mut Context) -> Option<Rect> {
let (w, h) = self.dimensions(ctx);
Some(Rect {
w: w as _,
h: h as _,
x: 0.0,
y: 0.0,
})
}
fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
self.blend_mode = mode;
}
fn blend_mode(&self) -> Option<BlendMode> {
self.blend_mode
}
}
impl Font {
/// Load a new TTF font from the given file.
pub fn new<P>(context: &mut Context, path: P) -> GameResult<Font>
where
P: AsRef<path::Path> + fmt::Debug,
{
use crate::filesystem;
let mut stream = filesystem::open(context, path.as_ref())?;
let mut buf = Vec::new();
let _ = stream.read_to_end(&mut buf)?;
Font::new_glyph_font_bytes(context, &buf)
}
/// Loads a new TrueType font from given bytes and into a `gfx::GlyphBrush` owned
/// by the `Context`.
pub fn new_glyph_font_bytes(context: &mut Context, bytes: &[u8]) -> GameResult<Self> {
// Take a Cow here to avoid this clone where unnecessary?
// Nah, let's not complicate things more than necessary.
let v = bytes.to_vec();
let font_id = context.gfx_context.glyph_brush.add_font_bytes(v);
Ok(Font { font_id })
}
/// Returns the baked-in bytes of default font (currently `DejaVuSerif.ttf`).
pub(crate) fn default_font_bytes() -> &'static [u8] {
include_bytes!("DejaVuSansMono.ttf")
}
}
impl Default for Font {
fn default() -> Self {
Font { font_id: FontId(0) }
}
}
/// Queues the `Text` to be drawn by [`draw_queued_text()`](fn.draw_queued_text.html).
/// `relative_dest` is relative to the [`DrawParam::dest`](struct.DrawParam.html#structfield.dest)
/// passed to `draw_queued()`. Note, any `Text` drawn via [`graphics::draw()`](fn.draw.html)
/// will also draw everything already the queue.
pub fn queue_text<P>(context: &mut Context, batch: &Text, relative_dest: P, color: Option<Color>)
where
P: Into<mint::Point2<f32>>,
{
let p = Point2::from(relative_dest.into());
let varied_section = batch.generate_varied_section(p, color);
context.gfx_context.glyph_brush.queue(varied_section);
}
/// Exposes `glyph_brush`'s drawing API in case `ggez`'s text drawing is insufficient.
/// It takes `glyph_brush`'s `VariedSection` and `GlyphPositioner`, which give you lower-
/// level control over how text is drawn.
pub fn queue_text_raw<'a, S, G>(context: &mut Context, section: S, custom_layout: Option<&G>)
where
S: Into<Cow<'a, VariedSection<'a>>>,
G: GlyphPositioner,
{
let brush = &mut context.gfx_context.glyph_brush;
match custom_layout {
Some(layout) => brush.queue_custom_layout(section, layout),
None => brush.queue(section),
}
}
/// Draws all of the [`Text`](struct.Text.html)s added via [`queue_text()`](fn.queue_text.html).
///
/// the `DrawParam` applies to everything in the queue; offset is in
/// screen coordinates; color is ignored - specify it when using
/// `queue_text()` instead.
///
/// Note that all text will, and in fact must, be drawn with the same
/// `BlendMode` and `FilterMode`. This is unfortunate but currently
/// unavoidable, see [this issue](https://github.com/ggez/ggez/issues/561)
/// for more info.
pub fn draw_queued_text<D>(
ctx: &mut Context,
param: D,
blend: Option<BlendMode>,
filter: FilterMode,
) -> GameResult
where
D: Into<DrawParam>,
{
let param: DrawParam = param.into();
let gb = &mut ctx.gfx_context.glyph_brush;
let encoder = &mut ctx.gfx_context.encoder;
let gc = &ctx.gfx_context.glyph_cache.texture_handle;
let backend = &ctx.gfx_context.backend_spec;
let action = gb.process_queued(
|rect, tex_data| update_texture::<GlBackendSpec>(backend, encoder, gc, rect, tex_data),
to_vertex,
);
match action {
Ok(glyph_brush::BrushAction::ReDraw) => {
let spritebatch = ctx.gfx_context.glyph_state.clone();
let spritebatch = &mut *spritebatch.borrow_mut();
spritebatch.set_blend_mode(blend);
spritebatch.set_filter(filter);
draw(ctx, &*spritebatch, param)?;
}
Ok(glyph_brush::BrushAction::Draw(drawparams)) => {
// Gotta clone the image to avoid double-borrow's.
let spritebatch = ctx.gfx_context.glyph_state.clone();
let spritebatch = &mut *spritebatch.borrow_mut();
spritebatch.clear();
spritebatch.set_blend_mode(blend);
spritebatch.set_filter(filter);
for p in &drawparams {
// Ignore returned sprite index.
let _ = spritebatch.add(*p);
}
draw(ctx, &*spritebatch, param)?;
}
Err(glyph_brush::BrushError::TextureTooSmall { suggested }) => {
let (new_width, new_height) = suggested;
let data = vec![255; 4 * new_width as usize * new_height as usize];
let new_glyph_cache =
Image::from_rgba8(ctx, new_width as u16, new_height as u16, &data)?;
ctx.gfx_context.glyph_cache = new_glyph_cache.clone();
let spritebatch = ctx.gfx_context.glyph_state.clone();
let spritebatch = &mut *spritebatch.borrow_mut();
let _ = spritebatch.set_image(new_glyph_cache);
ctx.gfx_context
.glyph_brush
.resize_texture(new_width, new_height);
}
}
Ok(())
}
fn update_texture<B>(
backend: &B,
encoder: &mut gfx::Encoder<B::Resources, B::CommandBuffer>,
texture: &gfx::handle::RawTexture<B::Resources>,
rect: glyph_brush::rusttype::Rect<u32>,
tex_data: &[u8],
) where
B: BackendSpec,
{
let offset = [rect.min.x as u16, rect.min.y as u16];
let size = [rect.width() as u16, rect.height() as u16];
let info = ImageInfoCommon {
xoffset: offset[0],
yoffset: offset[1],
zoffset: 0,
width: size[0],
height: size[1],
depth: 0,
format: (),
mipmap: 0,
};
let tex_data_chunks: Vec<[u8; 4]> = tex_data.iter().map(|c| [255, 255, 255, *c]).collect();
let typed_tex = backend.raw_to_typed_texture(texture.clone());
encoder
.update_texture::<<super::BuggoSurfaceFormat as gfx::format::Formatted>::Surface, super::BuggoSurfaceFormat>(
&typed_tex, None, info, &tex_data_chunks,
)
.unwrap();
}
/// I THINK what we're going to need to do is have a
/// `SpriteBatch` that actually does the stuff and stores the
/// UV's and verts and such, while
///
/// Basically, `glyph_brush`'s "`to_vertex`" callback is really
/// `to_quad`; in the default code it
fn to_vertex(v: glyph_brush::GlyphVertex) -> DrawParam {
let src_rect = Rect {
x: v.tex_coords.min.x,
y: v.tex_coords.min.y,
w: v.tex_coords.max.x - v.tex_coords.min.x,
h: v.tex_coords.max.y - v.tex_coords.min.y,
};
// it LOOKS like pixel_coords are the output coordinates?
// I'm not sure though...
let dest_pt = Point2::new(v.pixel_coords.min.x as f32, v.pixel_coords.min.y as f32);
DrawParam::default()
.src(src_rect)
.dest(dest_pt)
.color(v.color.into())
}
#[cfg(test)]
mod tests {
/*
use super::*;
#[test]
fn test_metrics() {
let f = Font::default_font().expect("Could not get default font");
assert_eq!(f.height(), 17);
assert_eq!(f.width("Foo!"), 33);
// http://www.catipsum.com/index.php
let text_to_wrap = "Walk on car leaving trail of paw prints on hood and windshield sniff \
other cat's butt and hang jaw half open thereafter for give attitude. \
Annoy kitten\nbrother with poking. Mrow toy mouse squeak roll over. \
Human give me attention meow.";
let (len, v) = f.wrap(text_to_wrap, 250);
println!("{} {:?}", len, v);
assert_eq!(len, 249);
/*
let wrapped_text = vec![
"Walk on car leaving trail of paw prints",
"on hood and windshield sniff other",
"cat\'s butt and hang jaw half open",
"thereafter for give attitude. Annoy",
"kitten",
"brother with poking. Mrow toy",
"mouse squeak roll over. Human give",
"me attention meow."
];
*/
let wrapped_text = vec![
"Walk on car leaving trail of paw",
"prints on hood and windshield",
"sniff other cat\'s butt and hang jaw",
"half open thereafter for give",
"attitude. Annoy kitten",
"brother with poking. Mrow toy",
"mouse squeak roll over. Human",
"give me attention meow.",
];
assert_eq!(&v, &wrapped_text);
}
// We sadly can't have this test in the general case because it needs to create a Context,
// which creates a window, which fails on a headless server like our CI systems. :/
//#[test]
#[allow(dead_code)]
fn test_wrapping() {
use conf;
let c = conf::Conf::new();
let (ctx, _) = &mut Context::load_from_conf("test_wrapping", "ggez", c)
.expect("Could not create context?");
let font = Font::default_font().expect("Could not get default font");
let text_to_wrap = "Walk on car leaving trail of paw prints on hood and windshield sniff \
other cat's butt and hang jaw half open thereafter for give attitude. \
Annoy kitten\nbrother with poking. Mrow toy mouse squeak roll over. \
Human give me attention meow.";
let wrap_length = 250;
let (len, v) = font.wrap(text_to_wrap, wrap_length);
assert!(len < wrap_length);
for line in &v {
let t = Text::new(ctx, line, &font).unwrap();
println!(
"Width is claimed to be <= {}, should be <= {}, is {}",
len,
wrap_length,
t.width()
);
// Why does this not match? x_X
//assert!(t.width() as usize <= len);
assert!(t.width() as usize <= wrap_length);
}
}
*/
}
/*
// Creates a gfx texture with the given data
fn create_texture<F, R>(
factory: &mut F,
width: u32,
height: u32,
) -> Result<(TexSurfaceHandle<R>, TexShaderView<R>), Box<dyn Error>>
where
R: gfx::Resources,
F: gfx::Factory<R>,
{
let kind = texture::Kind::D2(
width as texture::Size,
height as texture::Size,
texture::AaMode::Single,
);
let tex = factory.create_texture(
kind,
1 as texture::Level,
gfx::memory::Bind::SHADER_RESOURCE,
gfx::memory::Usage::Dynamic,
Some(<TexChannel as format::ChannelTyped>::get_channel_type()),
)?;
let view =
factory.view_texture_as_shader_resource::<TexForm>(&tex, (0, 0), format::Swizzle::new())?;
Ok((tex, view))
}
// Updates a texture with the given data (used for updating the GlyphCache texture)
#[inline]
fn update_texture<R, C>(
encoder: &mut gfx::Encoder<R, C>,
texture: &handle::Texture<R, TexSurface>,
offset: [u16; 2],
size: [u16; 2],
data: &[u8],
) where
R: gfx::Resources,
C: gfx::CommandBuffer<R>,
{
let info = texture::ImageInfoCommon {
xoffset: offset[0],
yoffset: offset[1],
zoffset: 0,
width: size[0],
height: size[1],
depth: 0,
format: (),
mipmap: 0,
};
encoder
.update_texture::<TexSurface, TexForm>(texture, None, info, data)
.unwrap();
}
*/

View File

@ -68,9 +68,9 @@
use crate::ggez::context::Context; use crate::ggez::context::Context;
use std::collections::HashSet; use std::collections::HashSet;
use winit::ModifiersState; use winit::event::ModifiersState;
/// A key code. /// A key code.
pub use winit::VirtualKeyCode as KeyCode; pub use winit::event::VirtualKeyCode as KeyCode;
bitflags! { bitflags! {
/// Bitflags describing the state of keyboard modifiers, such as `Control` or `Shift`. /// Bitflags describing the state of keyboard modifiers, such as `Control` or `Shift`.
@ -93,16 +93,16 @@ bitflags! {
impl From<ModifiersState> for KeyMods { impl From<ModifiersState> for KeyMods {
fn from(state: ModifiersState) -> Self { fn from(state: ModifiersState) -> Self {
let mut keymod = KeyMods::empty(); let mut keymod = KeyMods::empty();
if state.shift { if state.shift() {
keymod |= Self::SHIFT; keymod |= Self::SHIFT;
} }
if state.ctrl { if state.ctrl() {
keymod |= Self::CTRL; keymod |= Self::CTRL;
} }
if state.alt { if state.alt() {
keymod |= Self::ALT; keymod |= Self::ALT;
} }
if state.logo { if state.logo() {
keymod |= Self::LOGO; keymod |= Self::LOGO;
} }
keymod keymod

View File

@ -7,7 +7,7 @@ use crate::ggez::graphics;
use crate::ggez::graphics::Point2; use crate::ggez::graphics::Point2;
use std::collections::HashMap; use std::collections::HashMap;
use winit::dpi; use winit::dpi;
pub use winit::{MouseButton, MouseCursor}; pub use winit::event::{MouseButton};
/// Stores state information for the mouse. /// Stores state information for the mouse.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -15,7 +15,6 @@ pub struct MouseContext {
last_position: Point2, last_position: Point2,
last_delta: Point2, last_delta: Point2,
buttons_pressed: HashMap<MouseButton, bool>, buttons_pressed: HashMap<MouseButton, bool>,
cursor_type: MouseCursor,
cursor_grabbed: bool, cursor_grabbed: bool,
cursor_hidden: bool, cursor_hidden: bool,
} }
@ -25,7 +24,6 @@ impl MouseContext {
Self { Self {
last_position: Point2::origin(), last_position: Point2::origin(),
last_delta: Point2::origin(), last_delta: Point2::origin(),
cursor_type: MouseCursor::Default,
buttons_pressed: HashMap::new(), buttons_pressed: HashMap::new(),
cursor_grabbed: false, cursor_grabbed: false,
cursor_hidden: false, cursor_hidden: false,
@ -55,17 +53,6 @@ impl Default for MouseContext {
} }
} }
/// Returns the current mouse cursor type of the window.
pub fn cursor_type(ctx: &Context) -> MouseCursor {
ctx.mouse_context.cursor_type
}
/// Modifies the mouse cursor type of the window.
pub fn set_cursor_type(ctx: &mut Context, cursor_type: MouseCursor) {
ctx.mouse_context.cursor_type = cursor_type;
graphics::window(ctx).set_cursor(cursor_type);
}
/// Get whether or not the mouse is grabbed (confined to the window) /// Get whether or not the mouse is grabbed (confined to the window)
pub fn cursor_grabbed(ctx: &Context) -> bool { pub fn cursor_grabbed(ctx: &Context) -> bool {
ctx.mouse_context.cursor_grabbed ctx.mouse_context.cursor_grabbed
@ -74,8 +61,8 @@ pub fn cursor_grabbed(ctx: &Context) -> bool {
/// Set whether or not the mouse is grabbed (confined to the window) /// Set whether or not the mouse is grabbed (confined to the window)
pub fn set_cursor_grabbed(ctx: &mut Context, grabbed: bool) -> GameResult<()> { pub fn set_cursor_grabbed(ctx: &mut Context, grabbed: bool) -> GameResult<()> {
ctx.mouse_context.cursor_grabbed = grabbed; ctx.mouse_context.cursor_grabbed = grabbed;
graphics::window(ctx) graphics::window(ctx).window()
.grab_cursor(grabbed) .set_cursor_grab(grabbed)
.map_err(|e| GameError::WindowError(e.to_string())) .map_err(|e| GameError::WindowError(e.to_string()))
} }
@ -87,7 +74,7 @@ pub fn cursor_hidden(ctx: &Context) -> bool {
/// Set whether or not the mouse is hidden (invisible). /// Set whether or not the mouse is hidden (invisible).
pub fn set_cursor_hidden(ctx: &mut Context, hidden: bool) { pub fn set_cursor_hidden(ctx: &mut Context, hidden: bool) {
ctx.mouse_context.cursor_hidden = hidden; ctx.mouse_context.cursor_hidden = hidden;
graphics::window(ctx).hide_cursor(hidden) graphics::window(ctx).window().set_cursor_visible(!hidden)
} }
/// Get the current position of the mouse cursor, in pixels. /// Get the current position of the mouse cursor, in pixels.
@ -105,7 +92,7 @@ where
{ {
let mintpoint = point.into(); let mintpoint = point.into();
ctx.mouse_context.last_position = Point2::from(mintpoint); ctx.mouse_context.last_position = Point2::from(mintpoint);
graphics::window(ctx) graphics::window(ctx).window()
.set_cursor_position(dpi::LogicalPosition { .set_cursor_position(dpi::LogicalPosition {
x: f64::from(mintpoint.x), x: f64::from(mintpoint.x),
y: f64::from(mintpoint.y), y: f64::from(mintpoint.y),

409
src/lib.rs Normal file
View File

@ -0,0 +1,409 @@
#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate gfx;
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate smart_default;
extern crate strum;
#[macro_use]
extern crate strum_macros;
use std::{env, mem};
use std::path;
use std::time::Instant;
use log::*;
use pretty_env_logger::env_logger::Env;
use winit::event::{ElementState, Event, KeyboardInput, WindowEvent, TouchPhase};
use winit::event_loop::ControlFlow;
use crate::builtin_fs::BuiltinFS;
use crate::ggez::{Context, ContextBuilder, filesystem, GameResult};
use crate::ggez::conf::{Backend, WindowMode, WindowSetup};
use crate::ggez::event::{KeyCode, KeyMods};
use crate::ggez::graphics;
use crate::ggez::graphics::{Canvas, DrawParam, window};
use crate::ggez::input::keyboard;
use crate::ggez::mint::ColumnMatrix4;
use crate::ggez::nalgebra::Vector2;
use crate::scene::loading_scene::LoadingScene;
use crate::scene::Scene;
use crate::shared_game_state::{SharedGameState, TimingMode};
use crate::ui::UI;
use crate::ggez::event::winit_event::ModifiersState;
mod bmfont;
mod bmfont_renderer;
mod builtin_fs;
mod bullet;
mod caret;
mod common;
mod encoding;
mod engine_constants;
mod entity;
mod frame;
mod inventory;
mod ggez;
mod live_debugger;
mod macros;
mod map;
mod menu;
mod npc;
mod physics;
mod player;
mod player_hit;
mod profile;
mod rng;
mod scene;
mod shared_game_state;
mod stage;
mod sound;
mod text_script;
mod texture_set;
mod ui;
mod weapon;
struct Game {
scene: Option<Box<dyn Scene>>,
state: SharedGameState,
ui: UI,
def_matrix: ColumnMatrix4<f32>,
start_time: Instant,
next_tick: u64,
loops: u64,
}
impl Game {
fn new(ctx: &mut Context) -> GameResult<Game> {
let s = Game {
scene: None,
ui: UI::new(ctx)?,
def_matrix: DrawParam::new().to_matrix(),
state: SharedGameState::new(ctx)?,
start_time: Instant::now(),
next_tick: 0,
loops: 0,
};
Ok(s)
}
fn update(&mut self, ctx: &mut Context) -> GameResult {
if let Some(scene) = self.scene.as_mut() {
match self.state.timing_mode {
TimingMode::_50Hz | TimingMode::_60Hz => {
while self.start_time.elapsed().as_millis() as u64 > self.next_tick && self.loops < 3 {
self.next_tick += self.state.timing_mode.get_delta() as u64;
self.loops += 1;
}
for _ in 0..self.loops {
scene.tick(&mut self.state, ctx)?;
if self.state.settings.speed_hack {
scene.tick(&mut self.state, ctx)?;
}
}
}
TimingMode::FrameSynchronized => {
scene.tick(&mut self.state, ctx)?;
if self.state.settings.speed_hack {
scene.tick(&mut self.state, ctx)?;
}
}
}
}
Ok(())
}
fn draw(&mut self, ctx: &mut Context) -> GameResult {
graphics::clear(ctx, [0.0, 0.0, 0.0, 1.0].into());
graphics::set_transform(ctx, DrawParam::new()
.scale(Vector2::new(self.state.scale, self.state.scale))
.to_matrix());
graphics::apply_transformations(ctx)?;
if let Some(scene) = self.scene.as_mut() {
scene.draw(&mut self.state, ctx)?;
graphics::set_transform(ctx, self.def_matrix);
graphics::apply_transformations(ctx)?;
self.ui.draw(&mut self.state, ctx, scene)?;
}
graphics::present(ctx)?;
self.loops = 0;
Ok(())
}
fn key_down_event(&mut self, key_code: KeyCode, _key_mod: KeyMods, repeat: bool) {
if repeat { return; }
// todo: proper keymaps?
let state = &mut self.state;
match key_code {
KeyCode::Left => { state.key_state.set_left(true) }
KeyCode::Right => { state.key_state.set_right(true) }
KeyCode::Up => { state.key_state.set_up(true) }
KeyCode::Down => { state.key_state.set_down(true) }
KeyCode::Z => { state.key_state.set_jump(true) }
KeyCode::X => { state.key_state.set_fire(true) }
KeyCode::A => { state.key_state.set_weapon_prev(true) }
KeyCode::S => { state.key_state.set_weapon_next(true) }
KeyCode::F10 => { state.settings.debug_outlines = !state.settings.debug_outlines }
KeyCode::F11 => { state.settings.god_mode = !state.settings.god_mode }
KeyCode::F12 => { state.set_speed_hack(!state.settings.speed_hack) }
_ => {}
}
}
fn key_up_event(&mut self, key_code: KeyCode, _key_mod: KeyMods) {
let state = &mut self.state;
match key_code {
KeyCode::Left => { state.key_state.set_left(false) }
KeyCode::Right => { state.key_state.set_right(false) }
KeyCode::Up => { state.key_state.set_up(false) }
KeyCode::Down => { state.key_state.set_down(false) }
KeyCode::Z => { state.key_state.set_jump(false) }
KeyCode::X => { state.key_state.set_fire(false) }
KeyCode::A => { state.key_state.set_weapon_prev(false) }
KeyCode::S => { state.key_state.set_weapon_next(false) }
_ => {}
}
}
}
#[cfg(target_os = "android")]
fn request_perms() -> GameResult {
use jni::objects::JValue;
use jni::objects::JObject;
let native_activity = ndk_glue::native_activity();
let vm_ptr = native_activity.vm();
let vm = unsafe { jni::JavaVM::from_raw(vm_ptr) }?;
let vm_env = vm.attach_current_thread()?;
fn perm_name<'a, 'b, 'c>(vm_env: &'b jni::AttachGuard<'a>, name: &'c str) -> GameResult<jni::objects::JValue<'a>> {
let class = vm_env.find_class("android/Manifest$permission")?;
Ok(vm_env.get_static_field(class, name.to_owned(), "Ljava/lang/String;")?)
}
fn has_permission(vm_env: &jni::AttachGuard, activity: &jni::sys::jobject, name: &str) -> GameResult<bool> {
let perm_granted = {
let class = vm_env.find_class("android/content/pm/PackageManager")?;
vm_env.get_static_field(class, "PERMISSION_GRANTED", "I")?.i()?
};
let perm = perm_name(vm_env, name)?;
let activity_obj = JObject::from(*activity);
let result = vm_env.call_method(activity_obj, "checkSelfPermission", "(Ljava/lang/String;)I", &[perm])?.i()?;
Ok(result == perm_granted)
}
let str_class = vm_env.find_class("java/lang/String")?;
let array = vm_env.new_object_array(2, str_class, JObject::null())?;
vm_env.set_object_array_element(array, 0, perm_name(&vm_env, "READ_EXTERNAL_STORAGE")?.l()?)?;
vm_env.set_object_array_element(array, 1, perm_name(&vm_env, "WRITE_EXTERNAL_STORAGE")?.l()?)?;
let activity_obj = JObject::from(native_activity.activity());
loop {
if has_permission(&vm_env, &native_activity.activity(), "READ_EXTERNAL_STORAGE")?
&& has_permission(&vm_env, &native_activity.activity(), "WRITE_EXTERNAL_STORAGE")? {
break;
}
vm_env.call_method(activity_obj, "requestPermissions", "([Ljava/lang/String;I)V", &[JValue::from(array), JValue::from(0)])?;
}
Ok(())
}
#[cfg(target_os = "android")]
#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
pub fn android_main() {
println!("main invoked.");
request_perms().expect("Failed to attach to the JVM and request storage permissions.");
env::set_var("CAVESTORY_DATA_DIR", "/storage/emulated/0/doukutsu");
init().unwrap();
}
fn init_ctx<P: Into<path::PathBuf>>(event_loop: &winit::event_loop::EventLoopWindowTarget<()>, resource_dir: P) -> GameResult<Context> {
let backend = if cfg!(target_os = "android") {
Backend::OpenGLES { major: 2, minor: 0 }
} else {
Backend::OpenGL { major: 3, minor: 2 }
};
let mut ctx = ContextBuilder::new("doukutsu-rs")
.window_setup(WindowSetup::default().title("Cave Story (doukutsu-rs)"))
.window_mode(WindowMode::default()
.resizable(true)
.min_dimensions(320.0, 240.0)
.dimensions(854.0, 480.0))
.add_resource_path(resource_dir)
.add_resource_path(path::PathBuf::from(str!("./")))
.backend(backend)
.build(event_loop)?;
ctx.filesystem.mount_vfs(Box::new(BuiltinFS::new()));
Ok(ctx)
}
pub fn init() -> GameResult {
pretty_env_logger::env_logger::from_env(Env::default().default_filter_or("info"))
.filter(Some("gfx_device_gl::factory"), LevelFilter::Warn)
.init();
let resource_dir = if let Ok(data_dir) = env::var("CAVESTORY_DATA_DIR") {
path::PathBuf::from(data_dir)
} else if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
let mut path = path::PathBuf::from(manifest_dir);
path.push("data");
path
} else {
path::PathBuf::from("data")
};
info!("Resource directory: {:?}", resource_dir);
info!("Initializing engine...");
let event_loop = winit::event_loop::EventLoop::new();
let mut context: Option<Context> = None;
let mut game: Option<Game> = None;
#[cfg(target_os = "android")]
{
loop {
match ndk_glue::native_window().as_ref() {
Some(_) => {
println!("NativeScreen Found:{:?}", ndk_glue::native_window());
break;
}
None => ()
}
}
}
context = Some(init_ctx(&event_loop, resource_dir.clone())?);
event_loop.run(move |event, target, flow| {
if let Some(ctx) = &mut context {
ctx.timer_context.tick();
ctx.process_event(&event);
if let Some(game) = &mut game {
game.ui.handle_events(ctx, &event);
} else {
let mut new_game = Game::new(ctx).unwrap();
new_game.state.next_scene = Some(Box::new(LoadingScene::new()));
game = Some(new_game);
}
}
match event {
Event::Resumed => {
#[cfg(target_os = "android")]
if context.is_none() {
context = Some(init_ctx(target, resource_dir.clone()).unwrap());
}
if let Some(game) = &mut game {
game.loops = 0;
}
}
Event::Suspended => {
#[cfg(target_os = "android")]
{
context = None;
}
if let Some(game) = &mut game {
game.loops = 0;
}
}
Event::WindowEvent { event, .. } => {
match event {
WindowEvent::CloseRequested => {
if let Some(game) = &mut game {
game.state.shutdown();
}
*flow = ControlFlow::Exit;
}
WindowEvent::Resized(_) => {
if let (Some(ctx), Some(game)) = (&mut context, &mut game) {
game.state.handle_resize(ctx).unwrap();
game.state.lightmap_canvas = Canvas::with_window_size(ctx).unwrap();
//old_school_gfx_glutin_ext::WindowUpdateExt::update_gfx(graphics::window(ctx), &mut game.ui.main_color, &mut game.ui.main_depth);
}
}
WindowEvent::Touch(touch) => {
if let Some(game) = &mut game {
}
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: el_state,
virtual_keycode: Some(keycode),
modifiers,
..
},
..
} => {
if let (Some(ctx), Some(game)) = (&mut context, &mut game) {
match el_state {
ElementState::Pressed => {
let repeat = keyboard::is_key_repeated(ctx);
game.key_down_event( keycode, modifiers.into(), repeat);
}
ElementState::Released => {
game.key_up_event(keycode, modifiers.into());
}
}
}
}
_ => {}
}
}
Event::RedrawRequested(win) => {
if let (Some(ctx), Some(game)) = (&mut context, &mut game) {
if win == window(ctx).window().id() {
game.draw(ctx).unwrap();
}
}
}
Event::MainEventsCleared => {
if let (Some(ctx), Some(game)) = (&mut context, &mut game) {
game.update(ctx).unwrap();
#[cfg(target_os = "android")]
game.draw(ctx).unwrap(); // redraw request is unimplemented on shitdroid
window(ctx).window().request_redraw();
if game.state.shutdown {
log::info!("Shutting down...");
*flow = ControlFlow::Exit;
return;
}
if game.state.next_scene.is_some() {
mem::swap(&mut game.scene, &mut game.state.next_scene);
game.state.next_scene = None;
game.scene.as_mut().unwrap().init(&mut game.state, ctx).unwrap();
}
}
}
_ => {}
}
});
}

View File

@ -1,268 +1,3 @@
#[macro_use] fn main() {
extern crate bitflags; doukutsu_rs::init().unwrap();
#[macro_use]
extern crate gfx;
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate smart_default;
extern crate strum;
#[macro_use]
extern crate strum_macros;
use std::{env, mem};
use std::path;
use std::time::Instant;
use log::*;
use pretty_env_logger::env_logger::Env;
use winit::{ElementState, Event, KeyboardInput, WindowEvent};
use crate::builtin_fs::BuiltinFS;
use crate::ggez::{Context, ContextBuilder, filesystem, GameResult};
use crate::ggez::conf::{WindowMode, WindowSetup};
use crate::ggez::event::{KeyCode, KeyMods};
use crate::ggez::graphics;
use crate::ggez::graphics::{Canvas, DrawParam};
use crate::ggez::input::keyboard;
use crate::ggez::mint::ColumnMatrix4;
use crate::ggez::nalgebra::Vector2;
use crate::scene::loading_scene::LoadingScene;
use crate::scene::Scene;
use crate::shared_game_state::{SharedGameState, TimingMode};
use crate::ui::UI;
mod bmfont;
mod bmfont_renderer;
mod builtin_fs;
mod bullet;
mod caret;
mod common;
mod encoding;
mod engine_constants;
mod entity;
mod frame;
mod inventory;
mod ggez;
mod live_debugger;
mod macros;
mod map;
mod menu;
mod npc;
mod physics;
mod player;
mod player_hit;
mod profile;
mod rng;
mod scene;
mod shared_game_state;
mod stage;
mod sound;
mod text_script;
mod texture_set;
mod ui;
mod weapon;
struct Game {
scene: Option<Box<dyn Scene>>,
state: SharedGameState,
ui: UI,
def_matrix: ColumnMatrix4<f32>,
start_time: Instant,
next_tick: u64,
loops: u64,
}
impl Game {
fn new(ctx: &mut Context) -> GameResult<Game> {
let s = Game {
scene: None,
ui: UI::new(ctx)?,
def_matrix: DrawParam::new().to_matrix(),
state: SharedGameState::new(ctx)?,
start_time: Instant::now(),
next_tick: 0,
loops: 0,
};
Ok(s)
}
fn update(&mut self, ctx: &mut Context) -> GameResult {
if let Some(scene) = self.scene.as_mut() {
match self.state.timing_mode {
TimingMode::_50Hz | TimingMode::_60Hz => {
while self.start_time.elapsed().as_millis() as u64 > self.next_tick && self.loops < 3 {
self.next_tick += self.state.timing_mode.get_delta() as u64;
self.loops += 1;
}
for _ in 0..self.loops {
scene.tick(&mut self.state, ctx)?;
if self.state.settings.speed_hack {
scene.tick(&mut self.state, ctx)?;
}
}
}
TimingMode::FrameSynchronized => {
scene.tick(&mut self.state, ctx)?;
if self.state.settings.speed_hack {
scene.tick(&mut self.state, ctx)?;
}
}
}
}
Ok(())
}
fn draw(&mut self, ctx: &mut Context) -> GameResult {
graphics::clear(ctx, [0.0, 0.0, 0.0, 1.0].into());
graphics::set_transform(ctx, DrawParam::new()
.scale(Vector2::new(self.state.scale, self.state.scale))
.to_matrix());
graphics::apply_transformations(ctx)?;
if let Some(scene) = self.scene.as_mut() {
scene.draw(&mut self.state, ctx)?;
graphics::set_transform(ctx, self.def_matrix);
graphics::apply_transformations(ctx)?;
self.ui.draw(&mut self.state, ctx, scene)?;
}
graphics::present(ctx)?;
self.loops = 0;
Ok(())
}
fn key_down_event(&mut self, _ctx: &mut Context, key_code: KeyCode, _key_mod: KeyMods, repeat: bool) {
if repeat { return; }
// todo: proper keymaps?
let state = &mut self.state;
match key_code {
KeyCode::Left => { state.key_state.set_left(true) }
KeyCode::Right => { state.key_state.set_right(true) }
KeyCode::Up => { state.key_state.set_up(true) }
KeyCode::Down => { state.key_state.set_down(true) }
KeyCode::Z => { state.key_state.set_jump(true) }
KeyCode::X => { state.key_state.set_fire(true) }
KeyCode::A => { state.key_state.set_weapon_prev(true) }
KeyCode::S => { state.key_state.set_weapon_next(true) }
KeyCode::F10 => { state.settings.debug_outlines = !state.settings.debug_outlines }
KeyCode::F11 => { state.settings.god_mode = !state.settings.god_mode }
KeyCode::F12 => { state.set_speed_hack(!state.settings.speed_hack) }
_ => {}
}
}
fn key_up_event(&mut self, _ctx: &mut Context, key_code: KeyCode, _key_mod: KeyMods) {
let state = &mut self.state;
match key_code {
KeyCode::Left => { state.key_state.set_left(false) }
KeyCode::Right => { state.key_state.set_right(false) }
KeyCode::Up => { state.key_state.set_up(false) }
KeyCode::Down => { state.key_state.set_down(false) }
KeyCode::Z => { state.key_state.set_jump(false) }
KeyCode::X => { state.key_state.set_fire(false) }
KeyCode::A => { state.key_state.set_weapon_prev(false) }
KeyCode::S => { state.key_state.set_weapon_next(false) }
_ => {}
}
}
}
pub fn main() -> GameResult {
pretty_env_logger::env_logger::from_env(Env::default().default_filter_or("info"))
.filter(Some("gfx_device_gl::factory"), LevelFilter::Warn)
.init();
let resource_dir = if let Ok(data_dir) = env::var("CAVESTORY_DATA_DIR") {
path::PathBuf::from(data_dir)
} else if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
let mut path = path::PathBuf::from(manifest_dir);
path.push("data");
path
} else {
path::PathBuf::from("data")
};
info!("Resource directory: {:?}", resource_dir);
info!("Initializing engine...");
let cb = ContextBuilder::new("doukutsu-rs")
.window_setup(WindowSetup::default().title("Cave Story (doukutsu-rs)"))
.window_mode(WindowMode::default()
.resizable(true)
.min_dimensions(320.0, 240.0)
.dimensions(854.0, 480.0))
.add_resource_path(resource_dir)
.add_resource_path(path::PathBuf::from(str!("./")));
let (ctx, event_loop) = &mut cb.build()?;
ctx.filesystem.mount_vfs(Box::new(BuiltinFS::new()));
let game = &mut Game::new(ctx)?;
game.state.next_scene = Some(Box::new(LoadingScene::new()));
while ctx.continuing {
ctx.timer_context.tick();
event_loop.poll_events(|event| {
ctx.process_event(&event);
game.ui.handle_events(ctx, &event);
if let Event::WindowEvent { event, .. } = event {
match event {
WindowEvent::CloseRequested => { game.state.shutdown(); }
WindowEvent::Resized(_) => {
game.state.handle_resize(ctx).unwrap();
game.state.lightmap_canvas = Canvas::with_window_size(ctx).unwrap();
gfx_window_glutin::update_views(graphics::window(ctx), &mut game.ui.main_color, &mut game.ui.main_depth);
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: el_state,
virtual_keycode: Some(keycode),
modifiers,
..
},
..
} => {
match el_state {
ElementState::Pressed => {
let repeat = keyboard::is_key_repeated(ctx);
game.key_down_event(ctx, keycode, modifiers.into(), repeat);
}
ElementState::Released => {
game.key_up_event(ctx, keycode, modifiers.into());
}
}
}
_ => {}
}
}
});
game.update(ctx)?;
game.draw(ctx)?;
if game.state.shutdown {
log::info!("Shutting down...");
break;
}
if game.state.next_scene.is_some() {
mem::swap(&mut game.scene, &mut game.state.next_scene);
game.state.next_scene = None;
game.scene.as_mut().unwrap().init(&mut game.state, ctx)?;
}
}
Ok(())
} }

View File

@ -197,14 +197,15 @@ impl StageData {
// todo: refactor to make it less repetitive. // todo: refactor to make it less repetitive.
pub fn load_stage_table(ctx: &mut Context, root: &str) -> GameResult<Vec<Self>> { pub fn load_stage_table(ctx: &mut Context, root: &str) -> GameResult<Vec<Self>> {
let stage_tbl_path = [root, "stage.tbl"].join(""); let stage_tbl_path = [root, "stage.tbl"].join("");
let stage_dat_path = [root, "stage.dat"].join(""); let stage_sect_path = [root, "stage.sect"].join("");
let mrmap_bin_path = [root, "mrmap.bin"].join(""); let mrmap_bin_path = [root, "mrmap.bin"].join("");
let stage_dat_path = [root, "stage.dat"].join("");
if filesystem::exists(ctx, &stage_tbl_path) { if filesystem::exists(ctx, &stage_tbl_path) {
// Cave Story+ stage table. // Cave Story+ stage table.
let mut stages = Vec::new(); let mut stages = Vec::new();
info!("Loading CaveStory+/Booster's Lab style stage table from {}", &stage_tbl_path); info!("Loading Cave Story+/Booster's Lab style stage table from {}", &stage_tbl_path);
let mut data = Vec::new(); let mut data = Vec::new();
filesystem::open(ctx, stage_tbl_path)?.read_to_end(&mut data)?; filesystem::open(ctx, stage_tbl_path)?.read_to_end(&mut data)?;
@ -250,6 +251,59 @@ impl StageData {
stages.push(stage); stages.push(stage);
} }
return Ok(stages);
} else if filesystem::exists(ctx, &stage_sect_path) {
// Cave Story freeware executable dump.
let mut stages = Vec::new();
info!("Loading Cave Story freeware exe dump style stage table from {}", &stage_sect_path);
let mut data = Vec::new();
filesystem::open(ctx, stage_sect_path)?.read_to_end(&mut data)?;
let count = data.len() / 0xc8;
let mut f = Cursor::new(data);
for _ in 0..count {
let mut ts_buf = vec![0u8; 0x20];
let mut map_buf = vec![0u8; 0x20];
let mut back_buf = vec![0u8; 0x20];
let mut npc1_buf = vec![0u8; 0x20];
let mut npc2_buf = vec![0u8; 0x20];
let mut name_buf = vec![0u8; 0x20];
f.read_exact(&mut ts_buf)?;
f.read_exact(&mut map_buf)?;
let bg_type = f.read_u32::<LE>()? as usize;
f.read_exact(&mut back_buf)?;
f.read_exact(&mut npc1_buf)?;
f.read_exact(&mut npc2_buf)?;
let boss_no = f.read_u8()? as usize;
f.read_exact(&mut name_buf)?;
// alignment
let _ = f.read_u8()?;
let _ = f.read_u8()?;
let _ = f.read_u8()?;
let tileset = from_shift_jis(&ts_buf[0..zero_index(&ts_buf)]);
let map = from_shift_jis(&map_buf[0..zero_index(&map_buf)]);
let background = from_shift_jis(&back_buf[0..zero_index(&back_buf)]);
let npc1 = from_shift_jis(&npc1_buf[0..zero_index(&npc1_buf)]);
let npc2 = from_shift_jis(&npc2_buf[0..zero_index(&npc2_buf)]);
let name = from_shift_jis(&name_buf[0..zero_index(&name_buf)]);
let stage = StageData {
name: name.clone(),
map: map.clone(),
boss_no,
tileset: Tileset::new(&tileset),
background: Background::new(&background),
background_type: BackgroundType::new(bg_type),
npc1: NpcType::new(&npc1),
npc2: NpcType::new(&npc2),
};
stages.push(stage);
}
return Ok(stages); return Ok(stages);
} else if filesystem::exists(ctx, &mrmap_bin_path) { } else if filesystem::exists(ctx, &mrmap_bin_path) {
// CSE2E stage table // CSE2E stage table

View File

@ -1124,8 +1124,6 @@ impl TextScriptVM {
} else if let Some(dir) = Direction::from_int(direction) { } else if let Some(dir) = Direction::from_int(direction) {
npc.direction = dir; npc.direction = dir;
} }
break;
} }
} }
} }
@ -1182,8 +1180,6 @@ impl TextScriptVM {
} }
tick_npc = *npc_id; tick_npc = *npc_id;
break;
} }
} }
} }

View File

@ -122,7 +122,7 @@ impl UI {
colors[ImGuiCol_ModalWindowDimBg as usize] = [0.20, 0.20, 0.20, 0.35]; colors[ImGuiCol_ModalWindowDimBg as usize] = [0.20, 0.20, 0.20, 0.35];
let mut platform = WinitPlatform::init(&mut imgui); let mut platform = WinitPlatform::init(&mut imgui);
platform.attach_window(imgui.io_mut(), graphics::window(ctx), HiDpiMode::Rounded); platform.attach_window(imgui.io_mut(), graphics::window(ctx).window(), HiDpiMode::Rounded);
let (factory, dev, _, depth, color) = graphics::gfx_objects(ctx); let (factory, dev, _, depth, color) = graphics::gfx_objects(ctx);
let shaders = { let shaders = {
@ -161,23 +161,25 @@ impl UI {
}) })
} }
pub fn handle_events(&mut self, ctx: &mut Context, event: &winit::Event) { pub fn handle_events(&mut self, ctx: &mut Context, event: &winit::event::Event<()>) {
self.platform.handle_event(self.imgui.io_mut(), graphics::window(ctx), &event); self.platform.handle_event(self.imgui.io_mut(), graphics::window(ctx).window(), &event);
} }
pub fn draw(&mut self, state: &mut SharedGameState, ctx: &mut Context, scene: &mut Box<dyn Scene>) -> GameResult { pub fn draw(&mut self, state: &mut SharedGameState, ctx: &mut Context, scene: &mut Box<dyn Scene>) -> GameResult {
{ {
let io = self.imgui.io_mut(); let io = self.imgui.io_mut();
self.platform.prepare_frame(io, graphics::window(ctx)).map_err(|e| RenderError(e))?; self.platform.prepare_frame(io, graphics::window(ctx).window())
.map_err(|e| RenderError(e.to_string()))?;
io.update_delta_time(self.last_frame); let now = Instant::now();
self.last_frame = Instant::now(); io.update_delta_time(now - self.last_frame);
self.last_frame = now;
} }
let mut ui = self.imgui.frame(); let mut ui = self.imgui.frame();
scene.debug_overlay_draw(&mut self.components, state, ctx, &mut ui)?; scene.debug_overlay_draw(&mut self.components, state, ctx, &mut ui)?;
self.platform.prepare_render(&ui, graphics::window(ctx)); self.platform.prepare_render(&ui, graphics::window(ctx).window());
let draw_data = ui.render(); let draw_data = ui.render();
let (factory, dev, encoder, _, _) = graphics::gfx_objects(ctx); let (factory, dev, encoder, _, _) = graphics::gfx_objects(ctx);
self.renderer self.renderer