1
0
Fork 0
mirror of https://github.com/doukutsu-rs/doukutsu-rs synced 2024-11-23 06:02:55 +00:00

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

View file

@ -4,6 +4,29 @@ edition = "2018"
name = "doukutsu-rs"
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]
lto = 'thin'
panic = 'abort'
@ -12,32 +35,33 @@ panic = 'abort'
opt-level = 1
[dependencies]
#cpal = {path = "./cpal"}
#gfx_device_gl = {path = "./gfx/src/backend/gl"}
#glutin = {path = "./glutin/glutin"}
approx = "0.3"
bitflags = "1"
bitvec = "0.17.4"
byteorder = "1.3"
case_insensitive_hashmap = "1.0.0"
cpal = "0.12.1"
cpal = {git = "https://github.com/alula/cpal.git", branch = "android-support"}
directories = "2"
gfx = "0.18"
gfx_core = "0.9"
gfx_device_gl = "0.16"
gfx_window_glutin = "0.30"
gfx_device_gl = {git = "https://github.com/doukutsu-rs/gfx.git", branch = "pre-ll"}
gilrs = "0.7"
glyph_brush = "0.5"
glutin = "0.20"
imgui = "0.4.0"
imgui-ext = "0.3.0"
imgui-gfx-renderer = "0.4.0"
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"] }
glutin = {git = "https://github.com/doukutsu-rs/glutin.git", branch = "android-support"}
imgui = {git = "https://github.com/JMS55/imgui-rs.git"}
imgui-gfx-renderer = {git = "https://github.com/JMS55/imgui-rs.git"}
imgui-winit-support = {git = "https://github.com/JMS55/imgui-rs.git", default-features = false, features = ["winit-23"]}
image = {version = "0.22", default-features = false, features = ["png_codec", "pnm", "bmp"]}
itertools = "0.9.0"
lazy_static = "1.4.0"
log = "0.4"
lru = "0.6.0"
lyon = "0.13"
mint = "0.5"
nalgebra = {version = "0.18", features = ["mint"] }
nalgebra = {version = "0.18", features = ["mint"]}
num-derive = "0.3.2"
num-traits = "0.2.12"
owning_ref = "0.4.1"
@ -53,4 +77,9 @@ varint = "0.9.0"
# remove and replace when drain_filter is in stable
vec_mut_scan = "0.3.0"
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,
/// Whether or not to enable sRGB (gamma corrected color)
/// handling on the display.
#[default = true]
#[default = false]
pub srgb: bool,
}

View file

@ -15,6 +15,7 @@ use crate::ggez::filesystem::Filesystem;
use crate::ggez::graphics::{self, FilterMode, Point2};
use crate::ggez::input::{gamepad, keyboard, mouse};
use crate::ggez::timer;
use glutin::platform::ContextTraitExt;
/// A `Context` is an object that holds on to global resources.
/// It basically tracks hardware state such as the screen, audio
@ -79,14 +80,13 @@ impl fmt::Debug for Context {
impl Context {
/// 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).
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 events_loop = winit::EventsLoop::new();
let timer_context = timer::TimeContext::new();
let backend_spec = graphics::GlBackendSpec::from(conf.backend);
let graphics_context = graphics::context::GraphicsContext::new(
&mut fs,
&events_loop,
events_loop,
&conf.window_setup,
conf.window_mode,
backend_spec,
@ -95,7 +95,12 @@ impl Context {
let mouse_context = mouse::MouseContext::new();
let keyboard_context = keyboard::KeyboardContext::new();
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 {
Box::new(gamepad::NullGamepadContext::default())
};
@ -114,7 +119,7 @@ impl Context {
debug_id,
};
Ok((ctx, events_loop))
Ok(ctx)
}
// 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
/// rolling your own event loop, you should call this on the events
/// you receive before processing them yourself.
pub fn process_event(&mut self, event: &winit::Event) {
match event.clone() {
pub fn process_event<'a>(&mut self, event: &winit::event::Event<'a, ()>) {
match event {
winit_event::Event::WindowEvent { event, .. } => match event {
winit_event::WindowEvent::Resized(logical_size) => {
let hidpi_factor = self.gfx_context.window.get_hidpi_factor();
let physical_size = logical_size.to_physical(hidpi_factor as f64);
self.gfx_context.window.resize(physical_size);
winit_event::WindowEvent::Resized(physical_size) => {
self.gfx_context.window.resize(*physical_size);
self.gfx_context.resize_viewport();
}
winit_event::WindowEvent::CursorMoved {
@ -147,11 +150,11 @@ impl Context {
winit_event::ElementState::Pressed => true,
winit_event::ElementState::Released => false,
};
self.mouse_context.set_button(button, pressed);
self.mouse_context.set_button(*button, pressed);
}
winit_event::WindowEvent::KeyboardInput {
input:
winit::KeyboardInput {
winit::event::KeyboardInput {
state,
virtual_keycode: Some(keycode),
modifiers,
@ -164,34 +167,29 @@ impl Context {
winit_event::ElementState::Released => false,
};
self.keyboard_context
.set_modifiers(keyboard::KeyMods::from(modifiers));
self.keyboard_context.set_key(keycode, pressed);
}
winit_event::WindowEvent::HiDpiFactorChanged(_) => {
// Nope.
.set_modifiers(keyboard::KeyMods::from(*modifiers));
self.keyboard_context.set_key(*keycode, pressed);
}
_ => (),
},
winit_event::Event::DeviceEvent { event, .. } => {
if let winit_event::DeviceEvent::MouseMotion { delta: (x, y) } = event {
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).
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone)]
pub struct ContextBuilder {
pub(crate) game_id: String,
pub(crate) conf: conf::Conf,
pub(crate) paths: Vec<path::PathBuf>,
pub(crate) memory_zip_files: Vec<Cow<'static, [u8]>>,
pub(crate) load_conf_file: bool,
}
impl ContextBuilder {
@ -202,7 +200,6 @@ impl ContextBuilder {
conf: conf::Conf::default(),
paths: vec![],
memory_zip_files: vec![],
load_conf_file: true,
}
}
@ -249,30 +246,15 @@ impl ContextBuilder {
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`.
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())?;
for path in &self.paths {
fs.mount(path, true);
}
let config = if self.load_conf_file {
fs.read_config().unwrap_or(self.conf)
} else {
self.conf
};
Context::from_conf(config, fs)
Context::from_conf(self.conf, event_loop, fs)
}
}

View file

@ -181,8 +181,8 @@ impl From<gfx::shade::ProgramError> for GameError {
}
}
impl From<winit::EventsLoopClosed> for GameError {
fn from(_: glutin::EventsLoopClosed) -> GameError {
impl<T> From<winit::event_loop::EventLoopClosed<T>> for 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."
.to_owned();
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 {
fn from(s: lyon::lyon_tessellation::TessellationError) -> GameError {
let errstr = format!(

View file

@ -11,36 +11,35 @@
//! example](https://github.com/ggez/ggez/blob/master/examples/eventloop.rs).
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};
/// 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
// 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.
/// 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.
pub mod winit_event {
pub use super::winit::{
pub use winit::event::{
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, MouseScrollDelta,
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
/// `ggez`'s event loop. Implement this trait for a type and
@ -71,8 +70,7 @@ pub trait EventHandler {
_button: MouseButton,
_x: f32,
_y: f32,
) {
}
) {}
/// A mouse button was released
fn mouse_button_up_event(
@ -81,8 +79,7 @@ pub trait EventHandler {
_button: MouseButton,
_x: f32,
_y: f32,
) {
}
) {}
/// 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.
@ -130,8 +127,7 @@ pub trait EventHandler {
/// A gamepad axis moved; `id` identifies which gamepad.
/// Use [`input::gamepad()`](../input/fn.gamepad.html) to get more info about
/// 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.
fn focus_event(&mut self, _ctx: &mut Context, _gained: bool) {}
@ -155,24 +151,25 @@ pub fn quit(ctx: &mut Context) {
ctx.continuing = false;
}
/*
/// Runs the game's main loop, calling event callbacks on the given state
/// object as events occur.
///
/// It does not try to do any type of framerate limiting. See the
/// 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
where
S: EventHandler,
pub fn run<S>(ctx: &'static mut Context, events_loop: &mut EventLoop<()>, state: &'static mut S) -> GameResult
where
S: EventHandler,
{
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 {
// 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.
ctx.timer_context.tick();
events_loop.poll_events(|event| {
events_loop.run_return(|event, _target, _flow| {
ctx.timer_context.tick();
ctx.process_event(&event);
match event {
Event::WindowEvent { event, .. } => match event {
@ -197,12 +194,12 @@ where
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Pressed,
virtual_keycode: Some(keycode),
modifiers,
..
},
KeyboardInput {
state: ElementState::Pressed,
virtual_keycode: Some(keycode),
modifiers,
..
},
..
} => {
let repeat = keyboard::is_key_repeated(ctx);
@ -210,12 +207,12 @@ where
}
WindowEvent::KeyboardInput {
input:
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(keycode),
modifiers,
..
},
KeyboardInput {
state: ElementState::Released,
virtual_keycode: Some(keycode),
modifiers,
..
},
..
} => {
state.key_up_event(ctx, keycode, modifiers.into());
@ -256,30 +253,38 @@ where
Event::DeviceEvent { event, .. } => match event {
_ => (),
},
Event::Awakened => (),
Event::Suspended(_) => (),
Event::Resumed => (),
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(())
}
}*/

View file

@ -1,18 +1,20 @@
use std::cell::RefCell;
use std::path::Path;
use std::rc::Rc;
use gfx::traits::FactoryExt;
use gfx::Factory;
use gfx::traits::FactoryExt;
use glutin;
use glyph_brush::{GlyphBrush, GlyphBrushBuilder};
use glutin::PossiblyCurrent;
use winit::{self, dpi};
use winit::window::Fullscreen;
use crate::ggez::conf::{FullscreenType, WindowMode, WindowSetup};
use crate::ggez::context::DebugId;
use crate::ggez::filesystem::Filesystem;
use crate::ggez::graphics::*;
use crate::ggez::error::GameResult;
use crate::ggez::filesystem::Filesystem;
use crate::ggez::GameError;
use crate::ggez::graphics::*;
/// A structure that contains graphics state.
/// For instance,
@ -20,8 +22,8 @@ use crate::ggez::error::GameResult;
///
/// As an end-user you shouldn't ever have to touch this.
pub(crate) struct GraphicsContextGeneric<B>
where
B: BackendSpec,
where
B: BackendSpec,
{
shader_globals: Globals,
pub(crate) projection: Matrix4,
@ -30,10 +32,9 @@ where
pub(crate) screen_rect: Rect,
color_format: gfx::format::Format,
depth_format: gfx::format::Format,
srgb: bool,
pub(crate) backend_spec: B,
pub(crate) window: glutin::WindowedContext,
pub(crate) window: glutin::WindowedContext<PossiblyCurrent>,
pub(crate) multisample_samples: u8,
pub(crate) device: Box<B::Device>,
pub(crate) factory: Box<B::Factory>,
@ -52,15 +53,11 @@ where
default_shader: ShaderId,
pub(crate) current_shader: Rc<RefCell<Option<ShaderId>>>,
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>
where
B: BackendSpec,
where
B: BackendSpec,
{
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "<GraphicsContext: {:p}>", self)
@ -74,24 +71,16 @@ impl GraphicsContextGeneric<GlBackendSpec> {
/// Create a new GraphicsContext
pub(crate) fn new(
filesystem: &mut Filesystem,
events_loop: &winit::EventsLoop,
events_loop: &winit::event_loop::EventLoopWindowTarget<()>,
window_setup: &WindowSetup,
window_mode: WindowMode,
backend: GlBackendSpec,
debug_id: DebugId,
) -> GameResult<Self> {
let srgb = window_setup.srgb;
let color_format = if srgb {
gfx::format::Format(
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 color_format = gfx::format::Format(
gfx::format::SurfaceType::R8_G8_B8_A8,
gfx::format::ChannelType::Unorm,
);
let depth_format = gfx::format::Format(
gfx::format::SurfaceType::D24_S8,
gfx::format::ChannelType::Unorm,
@ -110,10 +99,10 @@ impl GraphicsContextGeneric<GlBackendSpec> {
.with_vsync(window_setup.vsync);
let window_size =
dpi::LogicalSize::from((f64::from(window_mode.width), f64::from(window_mode.height)));
let mut window_builder = winit::WindowBuilder::new()
dpi::LogicalSize::<f64>::from((f64::from(window_mode.width), f64::from(window_mode.height)));
let mut window_builder = glutin::window::WindowBuilder::new()
.with_title(window_setup.title.clone())
.with_dimensions(window_size)
.with_inner_size(window_size)
.with_resizable(window_mode.resizable);
window_builder = if !window_setup.icon.is_empty() {
@ -136,19 +125,15 @@ impl GraphicsContextGeneric<GlBackendSpec> {
// since we have no good control over it.
{
// Log a bunch of OpenGL state info pulled out of winit and gfx
let dpi::LogicalSize {
let dpi::PhysicalSize {
width: w,
height: h,
} = window
.get_outer_size()
.ok_or_else(|| GameError::VideoError("Window doesn't exist!".to_owned()))?;
let dpi::LogicalSize {
} = window.window().outer_size();
let dpi::PhysicalSize {
width: dw,
height: dh,
} = window
.get_inner_size()
.ok_or_else(|| GameError::VideoError("Window doesn't exist!".to_owned()))?;
let hidpi_factor = window.get_hidpi_factor();
} = window.window().inner_size();
let hidpi_factor = window.window().scale_factor();
debug!(
"Window created, desired size {}x{}, hidpi factor {}.",
window_mode.width, window_mode.height, hidpi_factor
@ -226,33 +211,6 @@ impl GraphicsContextGeneric<GlBackendSpec> {
let texture = white_image.texture.clone();
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
let left = 0.0;
let right = window_mode.width;
@ -264,6 +222,15 @@ impl GraphicsContextGeneric<GlBackendSpec> {
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 {
shader_globals: globals,
projection: initial_projection,
@ -272,7 +239,6 @@ impl GraphicsContextGeneric<GlBackendSpec> {
screen_rect: Rect::new(left, top, right - left, bottom - top),
color_format,
depth_format,
srgb,
backend_spec: backend,
window,
@ -293,10 +259,6 @@ impl GraphicsContextGeneric<GlBackendSpec> {
default_shader: shader.shader_id(),
current_shader: Rc::new(RefCell::new(None)),
shaders: vec![draw],
glyph_brush,
glyph_cache,
glyph_state,
};
gfx.set_window_mode(window_mode)?;
@ -320,11 +282,11 @@ impl GraphicsContextGeneric<GlBackendSpec> {
// but still better than
// having `winit` try to do the image loading for us.
// 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::GenericImageView;
use std::io::Read;
use winit::Icon;
use winit::window::Icon;
let mut buf = Vec::new();
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>
where
B: BackendSpec + 'static,
where
B: BackendSpec + 'static,
{
/// Sends the current value of the graphics context's shader globals
/// to the graphics card.
pub(crate) fn update_globals(&mut self) -> GameResult {
self.encoder
.update_buffer(&self.data.globals, &[self.shader_globals], 0)?;
self.encoder.update_constant_buffer(&self.data.globals, &self.shader_globals);
Ok(())
}
@ -358,6 +319,7 @@ where
.last()
.expect("Transform stack empty; should never happen");
let mvp = self.projection * modelview;
self.data.mvp = 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 {
let mut new_draw_params = draw_params;
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
.update_buffer(&self.data.rect_instance_properties, &[properties], 0)?;
Ok(())
@ -509,7 +471,7 @@ where
/// Sets window mode from a WindowMode object.
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);
@ -522,7 +484,7 @@ where
} else {
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 {
Some(dpi::LogicalSize {
@ -532,9 +494,10 @@ where
} else {
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 {
FullscreenType::Windowed => {
window.set_fullscreen(None);
@ -546,20 +509,23 @@ where
window.set_resizable(mode.resizable);
}
FullscreenType::True => {
window.set_fullscreen(Some(monitor));
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
window.set_inner_size(dpi::LogicalSize {
width: f64::from(mode.width),
height: f64::from(mode.height),
});
}
FullscreenType::Desktop => {
let position = monitor.get_position();
let dimensions = monitor.get_dimensions();
let hidpi_factor = window.get_hidpi_factor();
window.set_fullscreen(None);
window.set_decorations(false);
window.set_inner_size(dimensions.to_logical(hidpi_factor));
window.set_position(position.to_logical(hidpi_factor));
if let Some(monitor) = monitor {
let position = monitor.position();
let dimensions = monitor.size();
let hidpi_factor = window.scale_factor();
window.set_fullscreen(None);
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(())

View file

@ -244,14 +244,9 @@ impl From<DrawParam> for 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 color: [f32; 4] = if srgb {
let linear_color: types::LinearColor = self.color.into();
linear_color.into()
} else {
self.color.into()
};
let color: [f32; 4] = self.color.into();
InstanceProperties {
src: self.src.into(),
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 gfx;
use gfx::texture;
use gfx::Device;
use gfx::Factory;
use gfx::texture;
use gfx_device_gl;
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::WindowMode;
@ -37,29 +43,24 @@ use crate::ggez::context::Context;
use crate::ggez::context::DebugId;
use crate::ggez::GameError;
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::drawparam::*;
pub use crate::ggez::graphics::image::*;
pub use crate::ggez::graphics::mesh::*;
pub use crate::ggez::graphics::shader::*;
pub use crate::ggez::graphics::text::*;
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
// 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
@ -68,9 +69,12 @@ pub use crate::ggez::graphics::types::*;
// 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
// 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 ColorFormat = gfx::format::Rgba8;
type DepthFormat = gfx::format::DepthStencil;
/// 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
/// 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
type CommandBuffer: gfx::CommandBuffer<Self::Resources>;
/// 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
/// the surface type defined in a `BackendSpec`.
@ -104,27 +108,6 @@ pub trait BackendSpec: fmt::Debug {
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)`.
///
/// So for instance if the backend is using OpenGL version 3.2,
@ -144,14 +127,14 @@ pub trait BackendSpec: fmt::Debug {
/// Creates the window.
fn init<'a>(
&self,
window_builder: glutin::WindowBuilder,
gl_builder: glutin::ContextBuilder<'a>,
events_loop: &glutin::EventsLoop,
window_builder: glutin::window::WindowBuilder,
gl_builder: glutin::ContextBuilder<'a, NotCurrent>,
events_loop: &winit::event_loop::EventLoopWindowTarget<()>,
color_format: gfx::format::Format,
depth_format: gfx::format::Format,
) -> Result<
(
glutin::WindowedContext,
glutin::WindowedContext<PossiblyCurrent>,
Self::Device,
Self::Factory,
gfx::handle::RawRenderTargetView<Self::Resources>,
@ -170,7 +153,7 @@ pub trait BackendSpec: fmt::Debug {
depth_view: &gfx::handle::RawDepthStencilView<Self::Resources>,
color_format: gfx::format::Format,
depth_format: gfx::format::Format,
window: &glutin::WindowedContext,
window: &glutin::WindowedContext<PossiblyCurrent>,
) -> Option<(
gfx::handle::RawRenderTargetView<Self::Resources>,
gfx::handle::RawDepthStencilView<Self::Resources>,
@ -231,12 +214,12 @@ impl BackendSpec for GlBackendSpec {
fn shaders(&self) -> (&'static [u8], &'static [u8]) {
match self.api {
glutin::Api::OpenGl => (
include_bytes!("shader/basic_150.glslv"),
include_bytes!("shader/basic_150.glslf"),
include_bytes!("shader/basic_150.vert.glsl"),
include_bytes!("shader/basic_150.frag.glsl"),
),
glutin::Api::OpenGlEs => (
include_bytes!("shader/basic_es300.glslv"),
include_bytes!("shader/basic_es300.glslf"),
include_bytes!("shader/basic_es100.vert.glsl"),
include_bytes!("shader/basic_es100.frag.glsl"),
),
a => panic!("Unsupported API: {:?}, should never happen", a),
}
@ -244,14 +227,14 @@ impl BackendSpec for GlBackendSpec {
fn init<'a>(
&self,
window_builder: glutin::WindowBuilder,
gl_builder: glutin::ContextBuilder<'a>,
events_loop: &glutin::EventsLoop,
window_builder: glutin::window::WindowBuilder,
gl_builder: glutin::ContextBuilder<'a, NotCurrent>,
events_loop: &EventLoopWindowTarget<()>,
color_format: gfx::format::Format,
depth_format: gfx::format::Format,
) -> Result<
(
glutin::WindowedContext,
glutin::WindowedContext<PossiblyCurrent>,
Self::Device,
Self::Factory,
gfx::handle::RawRenderTargetView<Self::Resources>,
@ -259,13 +242,10 @@ impl BackendSpec for GlBackendSpec {
),
glutin::CreationError,
> {
gfx_window_glutin::init_raw(
window_builder,
gl_builder,
events_loop,
color_format,
depth_format,
)
Ok(gl_builder
.with_gfx_color_depth::<ColorFormat, DepthFormat>()
.build_windowed(window_builder, &events_loop)?
.init_gfx_raw(color_format, depth_format))
}
fn info(&self, device: &Self::Device) -> String {
@ -289,7 +269,7 @@ impl BackendSpec for GlBackendSpec {
depth_view: &gfx::handle::RawDepthStencilView<Self::Resources>,
color_format: gfx::format::Format,
depth_format: gfx::format::Format,
window: &glutin::WindowedContext,
window: &glutin::WindowedContext<PossiblyCurrent>,
) -> Option<(
gfx::handle::RawRenderTargetView<Self::Resources>,
gfx::handle::RawDepthStencilView<Self::Resources>,
@ -298,8 +278,7 @@ impl BackendSpec for GlBackendSpec {
// gfx_window_glutin::update_views()
let dim = color_view.get_dimensions();
assert_eq!(dim, depth_view.get_dimensions());
if let Some((cv, dv)) =
gfx_window_glutin::update_views_raw(window, dim, color_format, depth_format)
if let Some((cv, dv)) = window.updated_views_raw(dim, color_format, depth_format)
{
Some((cv, dv))
} else {
@ -366,6 +345,7 @@ gfx_defines! {
// breaks the gfx_defines! macro though. :-(
pipeline pipe {
vbuf: gfx::VertexBuffer<Vertex> = (),
mvp: gfx::Global<[[f32; 4]; 4]> = "u_MVP",
tex: gfx::TextureSampler<[f32; 4]> = "t_Texture",
globals: gfx::ConstantBuffer<Globals> = "Globals",
rect_instance_properties: gfx::InstanceBuffer<InstanceProperties> = (),
@ -373,7 +353,7 @@ gfx_defines! {
// pipeline init values in `shader::create_shader()`.
out: gfx::RawRenderTarget =
("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)
),
}
@ -409,18 +389,19 @@ impl Default for InstanceProperties {
}
}
}
/// A structure for conveniently storing `Sampler`'s, based off
/// their `SamplerInfo`.
pub(crate) struct SamplerCache<B>
where
B: BackendSpec,
where
B: BackendSpec,
{
samplers: HashMap<texture::SamplerInfo, gfx::handle::Sampler<B::Resources>>,
}
impl<B> SamplerCache<B>
where
B: BackendSpec,
where
B: BackendSpec,
{
fn new() -> Self {
SamplerCache {
@ -467,17 +448,16 @@ impl From<gfx::buffer::CreationError> for GameError {
/// Clear the screen to the background color.
pub fn clear(ctx: &mut Context, color: Color) {
let gfx = &mut ctx.gfx_context;
let linear_color: types::LinearColor = color.into();
let c: [f32; 4] = linear_color.into();
let c: [f32; 4] = color.into();
gfx.encoder.clear_raw(&gfx.data.out, c.into());
}
/// Draws the given `Drawable` object to the screen by calling its
/// [`draw()`](trait.Drawable.html#tymethod.draw) method.
pub fn draw<D, T>(ctx: &mut Context, drawable: &D, params: T) -> GameResult
where
D: Drawable,
T: Into<DrawParam>,
where
D: Drawable,
T: Into<DrawParam>,
{
let params = params.into();
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
/// underlying MVP matrix.
pub fn set_projection<M>(context: &mut Context, proj: M)
where
M: Into<mint::ColumnMatrix4<f32>>,
where
M: Into<mint::ColumnMatrix4<f32>>,
{
let proj = Matrix4::from(proj.into());
let gfx = &mut context.gfx_context;
@ -660,8 +640,8 @@ where
/// after calling this to apply these changes and recalculate the
/// underlying MVP matrix.
pub fn mul_projection<M>(context: &mut Context, transform: M)
where
M: Into<mint::ColumnMatrix4<f32>>,
where
M: Into<mint::ColumnMatrix4<f32>>,
{
let transform = Matrix4::from(transform.into());
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>)
where
M: Into<mint::ColumnMatrix4<f32>>,
where
M: Into<mint::ColumnMatrix4<f32>>,
{
let transform = transform.map(|transform| Matrix4::from(transform.into()));
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)
where
M: Into<mint::ColumnMatrix4<f32>>,
where
M: Into<mint::ColumnMatrix4<f32>>,
{
let transform = transform.into();
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)
where
M: Into<mint::ColumnMatrix4<f32>>,
where
M: Into<mint::ColumnMatrix4<f32>>,
{
let transform = Matrix4::from(transform.into());
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,
};
context.gfx_context.window.set_window_icon(icon);
context.gfx_context.window.window().set_window_icon(icon);
Ok(())
}
/// Sets the window title.
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.
/// Ideally you should not need to use this because ggez
/// would provide all the functions you need without having
/// 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;
&gfx.window
}
@ -886,20 +867,16 @@ pub fn window(context: &Context) -> &glutin::WindowedContext {
/// Returns zeros if the window doesn't exist.
pub fn size(context: &Context) -> (f32, f32) {
let gfx = &context.gfx_context;
gfx.window
.get_outer_size()
.map(|logical_size| (logical_size.width as f32, logical_size.height as f32))
.unwrap_or((0.0, 0.0))
let size = gfx.window.window().outer_size();
(size.width as f32, size.height as f32)
}
/// Returns the size of the window's underlying drawable in pixels as (width, height).
/// Returns zeros if window doesn't exist.
pub fn drawable_size(context: &Context) -> (f32, f32) {
let gfx = &context.gfx_context;
gfx.window
.get_inner_size()
.map(|logical_size| (logical_size.width as f32, logical_size.height as f32))
.unwrap_or((0.0, 0.0))
let size = gfx.window.window().inner_size();
(size.width as f32, size.height as f32)
}
/// 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)]
mod tests {
use crate::graphics::{transform_rect, DrawParam, Rect};
use approx::assert_relative_eq;
use std::f32::consts::PI;
use approx::assert_relative_eq;
use crate::graphics::{DrawParam, Rect, transform_rect};
#[test]
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.color = new_param.color;
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<_>>();

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

View file

@ -7,7 +7,7 @@ use crate::ggez::graphics;
use crate::ggez::graphics::Point2;
use std::collections::HashMap;
use winit::dpi;
pub use winit::{MouseButton, MouseCursor};
pub use winit::event::{MouseButton};
/// Stores state information for the mouse.
#[derive(Clone, Debug)]
@ -15,7 +15,6 @@ pub struct MouseContext {
last_position: Point2,
last_delta: Point2,
buttons_pressed: HashMap<MouseButton, bool>,
cursor_type: MouseCursor,
cursor_grabbed: bool,
cursor_hidden: bool,
}
@ -25,7 +24,6 @@ impl MouseContext {
Self {
last_position: Point2::origin(),
last_delta: Point2::origin(),
cursor_type: MouseCursor::Default,
buttons_pressed: HashMap::new(),
cursor_grabbed: 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)
pub fn cursor_grabbed(ctx: &Context) -> bool {
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)
pub fn set_cursor_grabbed(ctx: &mut Context, grabbed: bool) -> GameResult<()> {
ctx.mouse_context.cursor_grabbed = grabbed;
graphics::window(ctx)
.grab_cursor(grabbed)
graphics::window(ctx).window()
.set_cursor_grab(grabbed)
.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).
pub fn set_cursor_hidden(ctx: &mut Context, hidden: bool) {
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.
@ -105,7 +92,7 @@ where
{
let mintpoint = point.into();
ctx.mouse_context.last_position = Point2::from(mintpoint);
graphics::window(ctx)
graphics::window(ctx).window()
.set_cursor_position(dpi::LogicalPosition {
x: f64::from(mintpoint.x),
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]
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::{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(())
fn main() {
doukutsu_rs::init().unwrap();
}

View file

@ -197,14 +197,15 @@ impl StageData {
// todo: refactor to make it less repetitive.
pub fn load_stage_table(ctx: &mut Context, root: &str) -> GameResult<Vec<Self>> {
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 stage_dat_path = [root, "stage.dat"].join("");
if filesystem::exists(ctx, &stage_tbl_path) {
// Cave Story+ stage table.
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();
filesystem::open(ctx, stage_tbl_path)?.read_to_end(&mut data)?;
@ -250,6 +251,59 @@ impl StageData {
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);
} else if filesystem::exists(ctx, &mrmap_bin_path) {
// CSE2E stage table

View file

@ -1124,8 +1124,6 @@ impl TextScriptVM {
} else if let Some(dir) = Direction::from_int(direction) {
npc.direction = dir;
}
break;
}
}
}
@ -1182,8 +1180,6 @@ impl TextScriptVM {
}
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];
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 shaders = {
@ -161,23 +161,25 @@ impl UI {
})
}
pub fn handle_events(&mut self, ctx: &mut Context, event: &winit::Event) {
self.platform.handle_event(self.imgui.io_mut(), graphics::window(ctx), &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).window(), &event);
}
pub fn draw(&mut self, state: &mut SharedGameState, ctx: &mut Context, scene: &mut Box<dyn Scene>) -> GameResult {
{
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);
self.last_frame = Instant::now();
let now = Instant::now();
io.update_delta_time(now - self.last_frame);
self.last_frame = now;
}
let mut ui = self.imgui.frame();
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 (factory, dev, encoder, _, _) = graphics::gfx_objects(ctx);
self.renderer