pub mod audio;
pub mod camera;
pub(crate) mod extensions;
pub mod font;
#[doc(hidden)]
pub mod load;
pub mod sprite;
pub mod text;
use std::{cell::RefCell, rc::Rc, sync::Arc};
use gilrs::GamepadId;
use kira::{AudioManager, AudioManagerSettings, DefaultBackend};
use smallvec::SmallVec;
use winit::window::{Fullscreen, Window};
use crate::{
GamepadAxis, GamepadButton, KeyCode, MouseButton,
assets::{
AssetManager, CustomAssetManager, Id,
loadable::{Loadable, audio::Audio, font::Font, shader::Shader, sprite::Sprite},
source::AssetSource,
},
camera::Camera,
config::Config,
graphics::Graphics,
input::Input,
};
#[derive(Clone)]
pub struct Context {
inner: Rc<RefCell<ContextInner>>,
}
impl Context {
#[inline]
#[must_use]
pub fn width(&self) -> f32 {
self.read(|ctx| ctx.graphics.buffer_width)
}
#[inline]
#[must_use]
pub fn height(&self) -> f32 {
self.read(|ctx| ctx.graphics.buffer_height)
}
#[inline]
#[must_use]
pub fn size(&self) -> (f32, f32) {
self.read(|ctx| (ctx.graphics.buffer_width, ctx.graphics.buffer_height))
}
#[inline]
#[must_use]
pub fn aspect_ratio(&self) -> (f32, f32) {
let (width, height) = self.size();
let ratio = num_integer::gcd(width as u32, height as u32) as f32;
(width / ratio, height / ratio)
}
#[inline]
#[must_use]
pub fn is_minimized(&self) -> bool {
self.read(|ctx| {
ctx.window.is_minimized().unwrap_or(
ctx.graphics.surface_config.height <= 1 || ctx.graphics.surface_config.width <= 1,
)
})
}
#[inline]
#[must_use]
pub fn is_maximized(&self) -> bool {
self.read(|ctx| ctx.window.is_maximized())
}
#[inline]
pub fn set_cursor_visible(&self, visible: bool) {
self.write(|ctx| ctx.window.set_cursor_visible(visible));
}
#[inline]
pub fn toggle_fullscreen(&self) {
self.write(|ctx| {
let is_fullscreen = ctx.window.fullscreen().is_some();
ctx.window.set_fullscreen(if is_fullscreen {
None
} else {
Some(Fullscreen::Borderless(None))
});
});
}
#[inline]
pub fn exit(&self) {
self.write(|ctx| ctx.exit = true);
}
}
impl Context {
#[inline]
#[must_use]
pub fn delta_time(&self) -> f32 {
self.read(|ctx| ctx.config.update_delta_time)
}
#[inline]
#[must_use]
pub fn frames_per_second(&self) -> f32 {
self.read(|ctx| ctx.frames_per_second)
}
#[inline]
#[must_use]
pub fn blending_factor(&self) -> f32 {
self.read(|ctx| ctx.blending_factor)
}
}
impl Context {
#[inline]
#[must_use]
pub fn mouse(&self) -> Option<(f32, f32)> {
self.read(|ctx| ctx.input.mouse())
}
#[inline]
#[must_use]
pub fn mouse_x(&self) -> Option<f32> {
self.read(|ctx| ctx.input.mouse().map(|(x, _y)| x))
}
#[inline]
#[must_use]
pub fn mouse_y(&self) -> Option<f32> {
self.read(|ctx| ctx.input.mouse().map(|(_x, y)| y))
}
#[inline]
#[must_use]
pub fn mouse_pressed(&self, mouse_button: MouseButton) -> bool {
self.read(|ctx| ctx.input.mouse_pressed(mouse_button))
}
#[inline]
#[must_use]
pub fn mouse_released(&self, mouse_button: MouseButton) -> bool {
self.read(|ctx| ctx.input.mouse_released(mouse_button))
}
#[inline]
#[must_use]
pub fn mouse_held(&self, mouse_button: MouseButton) -> bool {
self.read(|ctx| ctx.input.mouse_held(mouse_button))
}
#[inline]
#[must_use]
pub fn scroll_delta(&self) -> (f32, f32) {
self.read(|ctx| ctx.input.scroll_diff())
}
}
impl Context {
#[inline]
#[must_use]
pub fn key_pressed(&self, keycode: KeyCode) -> bool {
self.read(|ctx| ctx.input.key_pressed(keycode))
}
#[inline]
#[must_use]
pub fn key_released(&self, keycode: KeyCode) -> bool {
self.read(|ctx| ctx.input.key_released(keycode))
}
#[inline]
#[must_use]
pub fn key_held(&self, keycode: KeyCode) -> bool {
self.read(|ctx| ctx.input.key_held(keycode))
}
}
impl Context {
#[inline]
#[must_use]
pub fn gamepad_ids(&self) -> SmallVec<[GamepadId; 4]> {
self.read(|ctx| ctx.input.gamepads_ids())
}
#[inline]
#[must_use]
pub fn gamepad_button_pressed(
&self,
gamepad_id: GamepadId,
button: GamepadButton,
) -> Option<bool> {
self.read(|ctx| ctx.input.gamepad_button_pressed(gamepad_id, button))
}
#[inline]
#[must_use]
pub fn gamepad_button_released(
&self,
gamepad_id: GamepadId,
button: GamepadButton,
) -> Option<bool> {
self.read(|ctx| ctx.input.gamepad_button_released(gamepad_id, button))
}
#[inline]
#[must_use]
pub fn gamepad_button_held(
&self,
gamepad_id: GamepadId,
button: GamepadButton,
) -> Option<bool> {
self.read(|ctx| ctx.input.gamepad_button_held(gamepad_id, button))
}
#[inline]
#[must_use]
pub fn gamepad_button_value(
&self,
gamepad_id: GamepadId,
button: GamepadButton,
) -> Option<f32> {
self.read(|ctx| ctx.input.gamepad_button_value(gamepad_id, button))
}
#[inline]
#[must_use]
pub fn gamepad_axis(&self, gamepad_id: GamepadId, axis: GamepadAxis) -> Option<f32> {
self.read(|ctx| ctx.input.gamepad_axis(gamepad_id, axis))
}
}
impl Context {
#[inline]
pub fn asset<T>(&self, path: impl AsRef<str>) -> Rc<T>
where
T: Loadable,
{
fn inner<T>(this: &Context, path: &str) -> Rc<T>
where
T: Loadable,
{
this.write(|ctx| ctx.custom(path))
}
inner(self, path.as_ref())
}
#[inline]
pub fn asset_owned<T>(&self, path: impl AsRef<str>) -> T
where
T: Loadable + Clone,
{
fn inner<T>(this: &Context, path: &str) -> T
where
T: Loadable + Clone,
{
this.write(|ctx| ctx.custom_owned(path))
}
inner(self, path.as_ref())
}
}
impl Context {
#[inline]
pub(crate) async fn new(
config: Config,
asset_source: Box<AssetSource>,
window: Window,
) -> Self {
let context_inner = ContextInner::new(config, asset_source, window).await;
let inner = Rc::new(RefCell::new(context_inner));
Self { inner }
}
#[inline]
pub(crate) fn read<R>(&self, reader: impl FnOnce(&ContextInner) -> R) -> R {
reader(&self.inner.borrow())
}
#[inline]
pub(crate) fn write<R>(&self, writer: impl FnOnce(&mut ContextInner) -> R) -> R {
writer(&mut self.inner.borrow_mut())
}
}
pub struct ContextInner {
pub asset_source: Box<AssetSource>,
pub(crate) window: Arc<Window>,
pub(crate) graphics: Graphics,
pub(crate) main_camera: Camera,
pub(crate) ui_camera: Camera,
pub(crate) frames_per_second: f32,
pub(crate) blending_factor: f32,
pub(crate) input: Input,
pub(crate) audio_manager: AudioManager<DefaultBackend>,
pub(crate) config: Config,
pub(crate) sprites: AssetManager<Sprite>,
pub(crate) fonts: AssetManager<Font>,
pub(crate) audio: AssetManager<Audio>,
pub(crate) shaders: AssetManager<Shader>,
pub(crate) custom: CustomAssetManager,
pub(crate) exit: bool,
}
impl ContextInner {
pub(crate) async fn new(
config: Config,
asset_source: Box<AssetSource>,
window: Window,
) -> Self {
let window = Arc::new(window);
let graphics = Graphics::new(config.clone(), Arc::clone(&window), &asset_source).await;
let mut main_camera = Camera::default();
main_camera.center(config.buffer_width, config.buffer_height);
let ui_camera = Camera::default();
let audio_manager = AudioManager::new(AudioManagerSettings::default()).unwrap();
let sprites = AssetManager::default();
let fonts = AssetManager::default();
let audio = AssetManager::default();
let shaders = AssetManager::default();
let custom = CustomAssetManager::default();
let frames_per_second = 0.0;
let blending_factor = 0.0;
let input = Input::new();
let exit = false;
Self {
asset_source,
window,
graphics,
main_camera,
ui_camera,
frames_per_second,
blending_factor,
input,
audio_manager,
config,
sprites,
fonts,
audio,
shaders,
custom,
exit,
}
}
#[inline]
pub(crate) fn sprite(&mut self, id: &str) -> Rc<Sprite> {
let id = Id::new(id);
if let Some(asset) = self.sprites.get(&id) {
return asset;
}
let asset = Sprite::load(&id, self);
self.sprites.insert(id, asset)
}
#[inline]
pub(crate) fn font(&mut self, id: &str) -> Rc<Font> {
let id = Id::new(id);
if let Some(asset) = self.fonts.get(&id) {
return asset;
}
let asset = Font::load(&id, self);
self.fonts.insert(id, asset)
}
#[inline]
pub(crate) fn audio(&mut self, id: &str) -> Rc<Audio> {
let id = Id::new(id);
if let Some(asset) = self.audio.get(&id) {
return asset;
}
let asset = Audio::load(&id, self);
self.audio.insert(id, asset)
}
#[inline]
pub(crate) fn custom<T>(&mut self, id: &str) -> Rc<T>
where
T: Loadable,
{
let id = Id::new(id);
if let Some(asset) = self.custom.get(&id) {
return asset;
}
let asset = T::load(&id, self);
self.custom.insert(id, asset)
}
#[inline]
pub(crate) fn custom_owned<T>(&mut self, id: &str) -> T
where
T: Loadable + Clone,
{
Rc::<T>::unwrap_or_clone(self.custom(id))
}
#[inline]
pub(crate) fn load_shader(&mut self, id: &str) {
let id = Id::new(id);
if self.shaders.contains(&id) {
return;
}
let asset = Shader::load(&id, self);
self.shaders.insert(id, asset);
}
#[cfg(not(target_arch = "wasm32"))]
#[inline]
pub(crate) fn remove(&mut self, id: &Id) {
self.sprites.remove(id);
self.fonts.remove(id);
self.audio.remove(id);
self.custom.remove(id);
}
#[inline]
pub(crate) const fn camera_mut(&mut self, is_ui_camera: bool) -> &mut Camera {
if is_ui_camera {
&mut self.ui_camera
} else {
&mut self.main_camera
}
}
#[inline]
pub(crate) const fn camera(&self, is_ui_camera: bool) -> &Camera {
if is_ui_camera {
&self.ui_camera
} else {
&self.main_camera
}
}
}