├── crates ├── notan_audio │ ├── README.md │ ├── src │ │ ├── prelude.rs │ │ ├── lib.rs │ │ ├── tracker.rs │ │ └── backend.rs │ └── Cargo.toml ├── notan_extra │ ├── README.md │ ├── src │ │ ├── lib.rs │ │ └── fps_limit.rs │ └── Cargo.toml ├── notan_math │ ├── README.md │ ├── src │ │ ├── lib.rs │ │ └── rect.rs │ └── Cargo.toml ├── notan_utils │ ├── README.md │ ├── src │ │ ├── lib.rs │ │ └── save_file.rs │ └── Cargo.toml ├── notan_random │ ├── README.md │ ├── src │ │ ├── prelude.rs │ │ ├── lib.rs │ │ └── utils.rs │ └── Cargo.toml ├── notan_web │ ├── src │ │ ├── prelude.rs │ │ ├── lib.rs │ │ └── clipboard.rs │ ├── README.md │ └── Cargo.toml ├── notan_winit │ ├── src │ │ ├── prelude.rs │ │ ├── lib.rs │ │ └── touch.rs │ ├── README.md │ └── Cargo.toml ├── notan_draw │ ├── src │ │ ├── images │ │ │ └── instanced.rs │ │ ├── lib.rs │ │ ├── patterns.rs │ │ ├── texts.rs │ │ ├── config.rs │ │ ├── shapes │ │ │ ├── geometry.rs │ │ │ ├── line.rs │ │ │ └── tess.rs │ │ ├── images.rs │ │ ├── extension.rs │ │ ├── atlas.rs │ │ ├── batch.rs │ │ └── shapes.rs │ ├── README.md │ └── Cargo.toml ├── notan_graphics │ ├── README.md │ ├── src │ │ ├── prelude.rs │ │ ├── shader.rs │ │ ├── limits.rs │ │ ├── lib.rs │ │ ├── commands.rs │ │ └── to_file.rs │ └── Cargo.toml ├── notan_app │ ├── README.md │ ├── src │ │ ├── parsers.rs │ │ ├── assets.rs │ │ ├── prelude.rs │ │ ├── lib.rs │ │ ├── parsers │ │ │ ├── audio.rs │ │ │ └── texture.rs │ │ ├── handlers.rs │ │ ├── assets │ │ │ └── waker.rs │ │ ├── app.rs │ │ └── timer.rs │ └── Cargo.toml ├── notan_core │ ├── src │ │ ├── lib.rs │ │ └── mouse.rs │ ├── README.md │ └── Cargo.toml ├── notan_glow │ ├── README.md │ ├── src │ │ ├── prelude.rs │ │ └── utils.rs │ └── Cargo.toml ├── notan_text │ ├── README.md │ ├── Cargo.toml │ └── src │ │ └── config.rs ├── notan_input │ ├── src │ │ ├── prelude.rs │ │ ├── lib.rs │ │ └── internals.rs │ ├── README.md │ └── Cargo.toml ├── notan_macro │ ├── README.md │ ├── src │ │ └── state.rs │ ├── build.rs │ └── Cargo.toml ├── notan_oddio │ ├── README.md │ ├── src │ │ └── lib.rs │ └── Cargo.toml ├── notan_log │ ├── README.md │ ├── src │ │ ├── lib.rs │ │ └── console_error.rs │ └── Cargo.toml ├── notan_backend │ ├── src │ │ ├── prelude.rs │ │ └── lib.rs │ ├── README.md │ └── Cargo.toml ├── notan_egui │ ├── src │ │ ├── lib.rs │ │ └── config.rs │ ├── README.md │ └── Cargo.toml └── notan_glyph │ ├── README.md │ ├── Cargo.toml │ └── src │ ├── cache.rs │ ├── instance.rs │ └── builder.rs ├── scripts ├── build_web_examples.sh ├── upgrade.sh ├── publish.sh └── take_screenshots.sh ├── .cargo └── config.toml ├── examples ├── assets │ ├── bunny.png │ ├── cube.png │ ├── rust.ico │ ├── rust.png │ ├── Ubuntu-B.ttf │ ├── click1.ogg │ ├── ferris.png │ ├── pattern.png │ ├── golem-walk.png │ ├── sunnyland.png │ ├── green_panel.png │ ├── grey_button.png │ ├── jingles_NES00.ogg │ ├── jingles_PIZZI01.ogg │ ├── pixelExplosion00.png │ ├── pixelExplosion01.png │ ├── pixelExplosion02.png │ ├── pixelExplosion03.png │ ├── pixelExplosion04.png │ ├── pixelExplosion05.png │ ├── pixelExplosion06.png │ ├── pixelExplosion07.png │ ├── pixelExplosion08.png │ ├── rust-logo-256x256.png │ ├── rust-logo-512x512.png │ ├── kenney_pixel-webfont.ttf │ └── lorem.txt ├── window_open.rs ├── window_initial_position.rs ├── draw_rect.rs ├── window_exit.rs ├── log_basic.rs ├── window_icon_from_raw.rs ├── draw_triangle.rs ├── draw_path.rs ├── window_transparent.rs ├── renderer_clear.rs ├── window_config.rs ├── draw_blend_mode.rs ├── draw_image.rs ├── egui_demo.rs ├── draw_text.rs ├── draw_mask.rs ├── draw_mask_animated.rs ├── egui_basic.rs ├── window_focus.rs ├── draw_image_crop.rs ├── text_hello.rs ├── egui_texture.rs ├── app_open_links.rs ├── draw_pattern.rs ├── window_fullscreen.rs ├── input_touches.rs ├── input_mouse_wheel.rs ├── draw_mask_texture.rs ├── input_keyboard_char.rs ├── draw_shapes.rs ├── input_mouse_local_position.rs ├── assets_load_texture.rs ├── draw_transform_local.rs ├── draw_nine_slice.rs ├── draw_transform.rs ├── draw_text_bounds.rs ├── input_keyboard.rs ├── input_mouse_events.rs ├── draw_animation_list.rs ├── assets_custom_loader.rs ├── egui_custom_font.rs ├── draw_animation_grid.rs ├── graphics_update_texture.rs ├── draw_projection.rs ├── draw_text_max_width.rs ├── renderer_triangle.rs ├── input_mouse.rs ├── renderer_quad_wireframe.rs ├── assets_try_unwrap.rs ├── draw_blend_mode_object.rs ├── glyph_hello_raw.rs ├── draw_shapes_shader.rs ├── renderer_quad.rs ├── app_drop_file.rs ├── draw_image_shader.rs └── draw_path_flower.rs ├── xtask ├── README.md ├── src │ ├── cli_example.rs │ ├── cli_docs.rs │ ├── cli_examples_msvc.rs │ ├── cli_examples.rs │ ├── cli_example_msvc.rs │ ├── cli.rs │ └── cli_examples_web.rs ├── Cargo.toml └── res │ ├── example.html │ └── docs.html ├── src ├── prelude.rs ├── notan.rs └── lib.rs ├── .gitignore ├── CONTRIBUTING.md ├── .github └── workflows │ ├── stale.yml │ └── rust.yml └── LICENSE-MIT /crates/notan_audio/README.md: -------------------------------------------------------------------------------- 1 | notan_audio 2 | === -------------------------------------------------------------------------------- /crates/notan_extra/README.md: -------------------------------------------------------------------------------- 1 | notan_extra 2 | === -------------------------------------------------------------------------------- /crates/notan_math/README.md: -------------------------------------------------------------------------------- 1 | notan_math 2 | === -------------------------------------------------------------------------------- /crates/notan_utils/README.md: -------------------------------------------------------------------------------- 1 | notan_utils 2 | === -------------------------------------------------------------------------------- /crates/notan_random/README.md: -------------------------------------------------------------------------------- 1 | notan_random 2 | === -------------------------------------------------------------------------------- /scripts/build_web_examples.sh: -------------------------------------------------------------------------------- 1 | cargo xtask examples web --release -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --" 3 | -------------------------------------------------------------------------------- /crates/notan_web/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use notan_glow::prelude::*; 2 | -------------------------------------------------------------------------------- /crates/notan_winit/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use notan_glow::prelude::*; 2 | -------------------------------------------------------------------------------- /crates/notan_draw/src/images/instanced.rs: -------------------------------------------------------------------------------- 1 | //pub struct InstancedImage {} 2 | -------------------------------------------------------------------------------- /crates/notan_web/README.md: -------------------------------------------------------------------------------- 1 | notan_web 2 | === 3 | 4 | Web backend for notan. -------------------------------------------------------------------------------- /crates/notan_extra/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod fps_limit; 2 | 3 | pub use fps_limit::*; 4 | -------------------------------------------------------------------------------- /crates/notan_graphics/README.md: -------------------------------------------------------------------------------- 1 | notan_graphics 2 | === 3 | 4 | Graphics API for notan. -------------------------------------------------------------------------------- /crates/notan_random/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::utils::*; 2 | pub use rand::Rng; 3 | -------------------------------------------------------------------------------- /crates/notan_app/README.md: -------------------------------------------------------------------------------- 1 | notan_app 2 | === 3 | 4 | This crate is the foundation of notan -------------------------------------------------------------------------------- /crates/notan_core/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod events; 2 | pub mod keyboard; 3 | pub mod mouse; 4 | -------------------------------------------------------------------------------- /crates/notan_draw/README.md: -------------------------------------------------------------------------------- 1 | notan_draw 2 | === 3 | 4 | Simple 2D draw API with auto batching. -------------------------------------------------------------------------------- /crates/notan_glow/README.md: -------------------------------------------------------------------------------- 1 | notan_glow 2 | === 3 | 4 | Graphics backend based on glow.rs -------------------------------------------------------------------------------- /crates/notan_math/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod rect; 2 | 3 | pub use glam::*; 4 | pub use rect::*; 5 | -------------------------------------------------------------------------------- /crates/notan_random/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod prelude; 2 | pub mod utils; 3 | 4 | pub use rand; 5 | -------------------------------------------------------------------------------- /crates/notan_text/README.md: -------------------------------------------------------------------------------- 1 | notan_text 2 | === 3 | 4 | Simple Text API on top of notan_glyph -------------------------------------------------------------------------------- /crates/notan_input/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::keyboard::*; 2 | pub use crate::mouse::*; 3 | -------------------------------------------------------------------------------- /crates/notan_winit/README.md: -------------------------------------------------------------------------------- 1 | notan_winit 2 | === 3 | 4 | Native backend using winit for notan. -------------------------------------------------------------------------------- /crates/notan_audio/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::backend::*; 2 | pub use crate::manager::Audio; 3 | -------------------------------------------------------------------------------- /crates/notan_macro/README.md: -------------------------------------------------------------------------------- 1 | notan_macro 2 | === 3 | 4 | Different set of marcos and utils for notan. -------------------------------------------------------------------------------- /examples/assets/bunny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/bunny.png -------------------------------------------------------------------------------- /examples/assets/cube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/cube.png -------------------------------------------------------------------------------- /examples/assets/rust.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/rust.ico -------------------------------------------------------------------------------- /examples/assets/rust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/rust.png -------------------------------------------------------------------------------- /crates/notan_oddio/README.md: -------------------------------------------------------------------------------- 1 | notan_oddio 2 | === 3 | 4 | Audio backend implementation for Notan using Oddio -------------------------------------------------------------------------------- /examples/assets/Ubuntu-B.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/Ubuntu-B.ttf -------------------------------------------------------------------------------- /examples/assets/click1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/click1.ogg -------------------------------------------------------------------------------- /examples/assets/ferris.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/ferris.png -------------------------------------------------------------------------------- /examples/assets/pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/pattern.png -------------------------------------------------------------------------------- /crates/notan_core/README.md: -------------------------------------------------------------------------------- 1 | notan_core 2 | ==== 3 | 4 | Basic types and struct used as a foundation for Notan -------------------------------------------------------------------------------- /examples/assets/golem-walk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/golem-walk.png -------------------------------------------------------------------------------- /examples/assets/sunnyland.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/sunnyland.png -------------------------------------------------------------------------------- /examples/assets/green_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/green_panel.png -------------------------------------------------------------------------------- /examples/assets/grey_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/grey_button.png -------------------------------------------------------------------------------- /examples/assets/jingles_NES00.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/jingles_NES00.ogg -------------------------------------------------------------------------------- /examples/assets/jingles_PIZZI01.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/jingles_PIZZI01.ogg -------------------------------------------------------------------------------- /crates/notan_log/README.md: -------------------------------------------------------------------------------- 1 | notan_log 2 | === 3 | 4 | Simple log API to support the crate log on native and web environments -------------------------------------------------------------------------------- /examples/assets/pixelExplosion00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/pixelExplosion00.png -------------------------------------------------------------------------------- /examples/assets/pixelExplosion01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/pixelExplosion01.png -------------------------------------------------------------------------------- /examples/assets/pixelExplosion02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/pixelExplosion02.png -------------------------------------------------------------------------------- /examples/assets/pixelExplosion03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/pixelExplosion03.png -------------------------------------------------------------------------------- /examples/assets/pixelExplosion04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/pixelExplosion04.png -------------------------------------------------------------------------------- /examples/assets/pixelExplosion05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/pixelExplosion05.png -------------------------------------------------------------------------------- /examples/assets/pixelExplosion06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/pixelExplosion06.png -------------------------------------------------------------------------------- /examples/assets/pixelExplosion07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/pixelExplosion07.png -------------------------------------------------------------------------------- /examples/assets/pixelExplosion08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/pixelExplosion08.png -------------------------------------------------------------------------------- /examples/assets/rust-logo-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/rust-logo-256x256.png -------------------------------------------------------------------------------- /examples/assets/rust-logo-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/rust-logo-512x512.png -------------------------------------------------------------------------------- /crates/notan_input/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod internals; 2 | pub mod keyboard; 3 | pub mod mouse; 4 | pub mod prelude; 5 | pub mod touch; 6 | -------------------------------------------------------------------------------- /examples/assets/kenney_pixel-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nazariglez/notan/HEAD/examples/assets/kenney_pixel-webfont.ttf -------------------------------------------------------------------------------- /crates/notan_glow/src/prelude.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_arch = "wasm32")] 2 | pub use crate::html_image::*; 3 | 4 | pub use crate::GlowBackend; 5 | -------------------------------------------------------------------------------- /xtask/README.md: -------------------------------------------------------------------------------- 1 | xtask 2 | === 3 | 4 | Adds useful development commands using xtask. Use the command `cargo xtask` to show the help. 5 | -------------------------------------------------------------------------------- /examples/window_open.rs: -------------------------------------------------------------------------------- 1 | use notan::prelude::*; 2 | 3 | #[notan_main] 4 | fn main() -> Result<(), String> { 5 | notan::init().build() 6 | } 7 | -------------------------------------------------------------------------------- /crates/notan_audio/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod backend; 2 | mod manager; 3 | pub mod prelude; 4 | mod tracker; 5 | 6 | pub use backend::*; 7 | pub use manager::Audio; 8 | -------------------------------------------------------------------------------- /crates/notan_input/README.md: -------------------------------------------------------------------------------- 1 | notan_input 2 | === 3 | 4 | Provide a set of APIs to notan, as: 5 | * keyboard 6 | * input 7 | * touch (wip) 8 | * gamepad (wip) -------------------------------------------------------------------------------- /crates/notan_oddio/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod backend; 2 | mod decoder; 3 | 4 | #[cfg(target_arch = "wasm32")] 5 | mod webaudio; 6 | 7 | pub use backend::OddioBackend; 8 | -------------------------------------------------------------------------------- /crates/notan_app/src/parsers.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "audio")] 2 | mod audio; 3 | mod texture; 4 | 5 | #[cfg(feature = "audio")] 6 | pub use audio::*; 7 | pub use texture::*; 8 | -------------------------------------------------------------------------------- /crates/notan_backend/src/prelude.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_arch = "wasm32")] 2 | pub use notan_web::prelude::*; 3 | 4 | #[cfg(not(target_arch = "wasm32"))] 5 | pub use notan_winit::prelude::*; 6 | -------------------------------------------------------------------------------- /crates/notan_utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "save_file")] 2 | mod save_file; 3 | 4 | #[cfg(feature = "save_file")] 5 | pub use save_file::*; 6 | 7 | pub use instant::{Duration, Instant}; 8 | -------------------------------------------------------------------------------- /crates/notan_log/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use ::log::{debug, error, info, trace, warn, LevelFilter}; 2 | 3 | #[cfg(target_arch = "wasm32")] 4 | mod console_error; 5 | 6 | mod config; 7 | pub use config::LogConfig; 8 | -------------------------------------------------------------------------------- /crates/notan_winit/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod backend; 2 | mod clipboard; 3 | mod keyboard; 4 | mod mouse; 5 | mod touch; 6 | mod window; 7 | 8 | mod gl_manager; 9 | pub mod prelude; 10 | 11 | pub use backend::*; 12 | -------------------------------------------------------------------------------- /examples/window_initial_position.rs: -------------------------------------------------------------------------------- 1 | use notan::prelude::*; 2 | 3 | #[notan_main] 4 | fn main() -> Result<(), String> { 5 | let win = WindowConfig::default().set_position(100, 100); 6 | notan::init().add_config(win).build() 7 | } 8 | -------------------------------------------------------------------------------- /crates/notan_backend/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod prelude; 2 | 3 | #[cfg(target_arch = "wasm32")] 4 | pub use notan_web::{WebBackend as DefaultBackend, *}; 5 | 6 | #[cfg(not(target_arch = "wasm32"))] 7 | pub use notan_winit::{WinitBackend as DefaultBackend, *}; 8 | -------------------------------------------------------------------------------- /crates/notan_app/src/assets.rs: -------------------------------------------------------------------------------- 1 | mod asset; 2 | mod list; 3 | mod loader; 4 | mod manager; 5 | mod storage; 6 | mod utils; 7 | mod waker; 8 | 9 | pub use asset::*; 10 | pub use list::*; 11 | pub use loader::*; 12 | pub use manager::*; 13 | pub use storage::*; 14 | -------------------------------------------------------------------------------- /crates/notan_log/src/console_error.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::unused_unit)] 2 | 3 | use wasm_bindgen::prelude::*; 4 | 5 | #[wasm_bindgen] 6 | extern "C" { 7 | #[wasm_bindgen(js_namespace = console, js_name = error)] 8 | pub fn console_error(s: &str); 9 | } 10 | -------------------------------------------------------------------------------- /crates/notan_app/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::app::{App, AppState}; 2 | pub use crate::assets::{Asset, AssetList, AssetLoader, Assets}; 3 | pub use crate::backend::*; 4 | pub use crate::config::*; 5 | pub use crate::graphics::Graphics; 6 | pub use crate::plugins::{Plugin, Plugins}; 7 | -------------------------------------------------------------------------------- /crates/notan_core/src/mouse.rs: -------------------------------------------------------------------------------- 1 | /// Represents a button of a mouse 2 | #[derive(Clone, Copy, Hash, Debug, Eq, PartialEq, Ord, PartialOrd)] 3 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 4 | pub enum MouseButton { 5 | Left, 6 | Right, 7 | Middle, 8 | Other(u8), 9 | } 10 | -------------------------------------------------------------------------------- /crates/notan_egui/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | mod extension; 3 | mod input; 4 | mod plugin; 5 | 6 | pub use config::EguiConfig; 7 | pub use extension::{EguiCallbackFn, EguiExtension, EguiRegisterTexture}; 8 | pub use plugin::{EguiPlugin, EguiPluginSugar}; 9 | 10 | pub use egui::load::SizedTexture; 11 | pub use egui::*; 12 | -------------------------------------------------------------------------------- /scripts/upgrade.sh: -------------------------------------------------------------------------------- 1 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 2 | 3 | for crate in crates/*; do 4 | current="$SCRIPT_DIR/../$crate" 5 | echo "$current" 6 | cd $current 7 | cargo upgrade 8 | done 9 | 10 | current="$SCRIPT_DIR/../" 11 | echo $current 12 | cd $current 13 | cargo upgrade 14 | -------------------------------------------------------------------------------- /crates/notan_graphics/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::buffer::*; 2 | pub use crate::color::*; 3 | pub use crate::commands::*; 4 | pub use crate::device::*; 5 | pub use crate::limits::*; 6 | pub use crate::pipeline::*; 7 | pub use crate::render_texture::*; 8 | pub use crate::renderer::*; 9 | pub use crate::shader::*; 10 | pub use crate::texture::*; 11 | -------------------------------------------------------------------------------- /xtask/src/cli_example.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::{Example, TargetType}; 2 | use crate::DynError; 3 | 4 | impl Example { 5 | pub(crate) fn run(self) -> Result<(), DynError> { 6 | match self.target { 7 | TargetType::Msvc => self.run_msvc()?, 8 | TargetType::Web => self.run_web()?, 9 | } 10 | 11 | Ok(()) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /crates/notan_audio/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_audio" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Provides simple audio API for Notan" 11 | 12 | [dependencies] 13 | parking_lot.workspace = true 14 | -------------------------------------------------------------------------------- /crates/notan_graphics/src/shader.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone)] 2 | pub struct ShaderSource<'a> { 3 | pub sources: &'a [(&'a str, &'a [u8])], 4 | } 5 | 6 | impl ShaderSource<'_> { 7 | pub fn get_source(&self, api: &str) -> Option<&[u8]> { 8 | self.sources 9 | .iter() 10 | .find(|&&(id, _)| id == api) 11 | .map(|(_, data)| *data) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/draw_rect.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[notan_main] 5 | fn main() -> Result<(), String> { 6 | notan::init().add_config(DrawConfig).draw(draw).build() 7 | } 8 | 9 | fn draw(gfx: &mut Graphics) { 10 | let mut draw = gfx.create_draw(); 11 | draw.clear(Color::BLACK); 12 | draw.rect((100.0, 100.0), (600.0, 400.0)); 13 | gfx.render(&draw); 14 | } 15 | -------------------------------------------------------------------------------- /crates/notan_backend/README.md: -------------------------------------------------------------------------------- 1 | Notan Backend 2 | === 3 | 4 | This crate is used by default for the `AppBuilder` to set a backend depending on the build platform. 5 | 6 | For `web` it will use the `notan_web` backend based on `web-sys` and for desktop platforms it will use `notan_winit` currently using `glutin` to give the OpenGL context. 7 | 8 | Both backends (web and winit) will use `notan_glow` to manage the OpenGL/WebGL context. -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::app::prelude::*; 2 | pub use crate::graphics::prelude::*; 3 | pub use crate::input::prelude::*; 4 | pub use crate::Event; 5 | pub use notan_macro::{notan_main, uniform, AppState}; 6 | 7 | #[cfg(feature = "audio")] 8 | pub use crate::audio::prelude::*; 9 | 10 | #[cfg(feature = "random")] 11 | pub use crate::random::prelude::*; 12 | 13 | #[cfg(feature = "backend")] 14 | pub use notan_backend::prelude::*; 15 | -------------------------------------------------------------------------------- /examples/window_exit.rs: -------------------------------------------------------------------------------- 1 | use notan::prelude::*; 2 | 3 | #[notan_main] 4 | fn main() -> Result<(), String> { 5 | notan::init().update(update).build() 6 | } 7 | 8 | fn update(app: &mut App) { 9 | // Closes the App pressing the Escape key. 10 | // On browsers the requestAnimationFrame will stop but the canvas will still be visible 11 | if app.keyboard.was_pressed(KeyCode::Escape) { 12 | app.exit(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /crates/notan_app/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | pub mod empty; 3 | pub mod prelude; 4 | 5 | mod app; 6 | mod backend; 7 | mod builder; 8 | pub mod graphics; 9 | mod handlers; 10 | mod parsers; 11 | mod timer; 12 | 13 | pub mod assets; 14 | mod plugins; 15 | 16 | pub use app::*; 17 | pub use backend::*; 18 | pub use notan_core::events::*; 19 | 20 | pub use builder::*; 21 | pub use plugins::*; 22 | 23 | pub use graphics::*; 24 | 25 | pub use config::WindowConfig; 26 | -------------------------------------------------------------------------------- /examples/log_basic.rs: -------------------------------------------------------------------------------- 1 | use notan::log; 2 | use notan::prelude::*; 3 | 4 | #[notan_main] 5 | fn main() -> Result<(), String> { 6 | notan::init() 7 | .add_config(log::LogConfig::debug()) 8 | .initialize(start) 9 | .build() 10 | } 11 | 12 | fn start() { 13 | log::debug!("Hello, this is a debug log..."); 14 | log::info!("And this is a info log!"); 15 | log::warn!("I'm warning you"); 16 | log::error!("I'm an error, I told you..."); 17 | } 18 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | publish = false 5 | authors.workspace = true 6 | edition.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | license.workspace = true 10 | readme = "README.md" 11 | description = "" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | flate2 = "1.0" 17 | fs_extra = "1.3.0" 18 | xflags = "0.3.2" 19 | -------------------------------------------------------------------------------- /examples/window_icon_from_raw.rs: -------------------------------------------------------------------------------- 1 | use notan::prelude::*; 2 | 3 | #[notan_main] 4 | fn main() -> Result<(), String> { 5 | // Check the documentation for more options 6 | let window_config = WindowConfig::new() 7 | .set_title("Window Icon Data Demo") 8 | .set_window_icon_data(Some(include_bytes!("./assets/rust.ico"))) 9 | .set_taskbar_icon_data(Some(include_bytes!("./assets/rust.ico"))); 10 | 11 | notan::init().add_config(window_config).build() 12 | } 13 | -------------------------------------------------------------------------------- /crates/notan_graphics/src/limits.rs: -------------------------------------------------------------------------------- 1 | // check this https://docs.rs/wgpu/0.8.1/wgpu/struct.Limits.html 2 | 3 | /// Limit are overridden by the graphic implementation 4 | #[derive(Debug, Clone, Copy)] 5 | pub struct Limits { 6 | pub max_texture_size: u32, 7 | pub max_uniform_blocks: u32, 8 | } 9 | 10 | impl Default for Limits { 11 | fn default() -> Self { 12 | Self { 13 | max_texture_size: 8192, 14 | max_uniform_blocks: 8, 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/draw_triangle.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[notan_main] 5 | fn main() -> Result<(), String> { 6 | notan::init() 7 | .add_config(DrawConfig) // Simple way to add the draw extension 8 | .draw(draw) 9 | .build() 10 | } 11 | 12 | fn draw(gfx: &mut Graphics) { 13 | let mut draw = gfx.create_draw(); 14 | draw.clear(Color::BLACK); 15 | draw.triangle((400.0, 100.0), (100.0, 500.0), (700.0, 500.0)); 16 | gfx.render(&draw); 17 | } 18 | -------------------------------------------------------------------------------- /crates/notan_macro/src/state.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | use proc_macro::TokenStream; 3 | use quote::quote; 4 | 5 | pub(crate) fn impl_state_derive(ast: &syn::DeriveInput) -> TokenStream { 6 | let name = &ast.ident; 7 | let generics = &ast.generics; 8 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 9 | 10 | let gen = quote! { 11 | impl #impl_generics notan::app::AppState for #name #ty_generics #where_clause {} 12 | }; 13 | gen.into() 14 | } 15 | -------------------------------------------------------------------------------- /crates/notan_graphics/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod buffer; 2 | pub mod color; 3 | pub mod commands; 4 | pub mod device; 5 | mod limits; 6 | pub mod pipeline; 7 | mod render_texture; 8 | pub mod renderer; 9 | mod shader; 10 | pub mod texture; 11 | 12 | pub mod prelude; 13 | 14 | pub use crevice_notan as crevice; 15 | 16 | #[cfg(feature = "texture_to_file")] 17 | mod to_file; 18 | 19 | pub use device::*; 20 | pub use limits::*; 21 | pub use render_texture::*; 22 | pub use renderer::*; 23 | pub use shader::*; 24 | pub use texture::*; 25 | -------------------------------------------------------------------------------- /crates/notan_app/src/parsers/audio.rs: -------------------------------------------------------------------------------- 1 | use crate::assets::AssetLoader; 2 | use crate::App; 3 | use notan_audio::AudioSource; 4 | 5 | pub fn create_audio_parser() -> AssetLoader { 6 | AssetLoader::new() 7 | .use_parser(parse_audio) 8 | .extensions(&["mp3", "ogg", "wav", "flac"]) 9 | } 10 | 11 | fn parse_audio(id: &str, data: Vec, app: &mut App) -> Result { 12 | let source = app.audio.create_source(&data)?; 13 | log::debug!("Asset '{}' parsed as AudioSource", id); 14 | Ok(source) 15 | } 16 | -------------------------------------------------------------------------------- /crates/notan_audio/src/tracker.rs: -------------------------------------------------------------------------------- 1 | use parking_lot::RwLock; 2 | 3 | #[derive(Copy, Clone, Debug)] 4 | pub(crate) enum ResourceId { 5 | Source(u64), 6 | Sound(u64), 7 | } 8 | 9 | #[derive(Debug, Default)] 10 | pub(crate) struct ResourceTracker { 11 | pub(crate) dropped: RwLock>, 12 | } 13 | 14 | impl ResourceTracker { 15 | pub fn push(&self, id: ResourceId) { 16 | self.dropped.write().push(id); 17 | } 18 | 19 | pub fn clean(&self) { 20 | self.dropped.write().clear(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /crates/notan_egui/src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::{EguiExtension, EguiPlugin}; 2 | use notan_app::{AppBuilder, AppState, BackendSystem, BuildConfig, Graphics}; 3 | 4 | pub struct EguiConfig; 5 | impl BuildConfig for EguiConfig 6 | where 7 | S: AppState + 'static, 8 | B: BackendSystem, 9 | { 10 | fn apply(&self, builder: AppBuilder) -> AppBuilder { 11 | builder 12 | .add_plugin(EguiPlugin::default()) 13 | .add_graphic_ext(move |gfx: &mut Graphics| EguiExtension::new(gfx).unwrap()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /crates/notan_draw/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod batch; 2 | mod builder; 3 | mod config; 4 | mod custom_pipeline; 5 | mod draw; 6 | mod extension; 7 | mod images; 8 | mod manager; 9 | mod patterns; 10 | mod shapes; 11 | mod texts; 12 | mod transform; 13 | 14 | mod atlas; 15 | 16 | pub use atlas::*; 17 | pub use builder::*; 18 | pub use config::*; 19 | pub use custom_pipeline::*; 20 | pub use draw::*; 21 | pub use extension::*; 22 | pub use images::*; 23 | pub use manager::*; 24 | pub use patterns::*; 25 | pub use shapes::*; 26 | pub use texts::*; 27 | pub use transform::*; 28 | -------------------------------------------------------------------------------- /crates/notan_app/src/parsers/texture.rs: -------------------------------------------------------------------------------- 1 | use crate::assets::AssetLoader; 2 | use crate::graphics::Graphics; 3 | use notan_graphics::Texture; 4 | 5 | pub fn create_texture_parser() -> AssetLoader { 6 | AssetLoader::new() 7 | .use_parser(parse_image) 8 | .extensions(&["png", "jpg", "jpeg"]) 9 | } 10 | 11 | fn parse_image(id: &str, data: Vec, gfx: &mut Graphics) -> Result { 12 | let texture = gfx.create_texture().from_image(&data).build()?; 13 | log::debug!("Asset '{}' parsed as Texture", id); 14 | Ok(texture) 15 | } 16 | -------------------------------------------------------------------------------- /crates/notan_web/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg(target_arch = "wasm32")] 2 | 3 | mod backend; 4 | mod clipboard; 5 | mod keyboard; 6 | mod mouse; 7 | mod touch; 8 | mod utils; 9 | mod window; 10 | 11 | #[cfg(feature = "drop_files")] 12 | mod files; 13 | 14 | #[cfg(feature = "audio")] 15 | mod audio; 16 | 17 | #[cfg(all(feature = "clipboard", not(web_sys_unstable_apis)))] 18 | compile_error!("feature \"clipboard\" requires web_sys_unstable_apis to be enabled\nsee https://rustwasm.github.io/wasm-bindgen/web-sys/unstable-apis.html"); 19 | 20 | pub mod prelude; 21 | 22 | pub use backend::*; 23 | -------------------------------------------------------------------------------- /crates/notan_draw/src/patterns.rs: -------------------------------------------------------------------------------- 1 | mod painter; 2 | mod pattern; 3 | 4 | use crate::builder::DrawBuilder; 5 | use crate::draw::Draw; 6 | use notan_graphics::Texture; 7 | pub use painter::create_pattern_pipeline; 8 | pub(crate) use painter::*; 9 | pub use pattern::*; 10 | 11 | pub trait DrawPattern { 12 | fn pattern<'a>(&mut self, texture: &'a Texture) -> DrawBuilder>; 13 | } 14 | 15 | impl DrawPattern for Draw { 16 | fn pattern<'a>(&mut self, texture: &'a Texture) -> DrawBuilder> { 17 | DrawBuilder::new(self, Pattern::new(texture)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /crates/notan_extra/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_extra" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Provides extra features or plugins for Notan" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | notan_app.workspace = true 16 | 17 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 18 | spin_sleep = "1.3.0" 19 | 20 | -------------------------------------------------------------------------------- /crates/notan_glyph/README.md: -------------------------------------------------------------------------------- 1 | notan_glyph 2 | ==== 3 | 4 | A fast text renderer for [notan](https://github.com/Nazariglez/notan), powered by 5 | [glyph_brush](https://github.com/alexheretic/glyph-brush/tree/master/glyph-brush). 6 | 7 | ## Examples 8 | 9 | Have a loot at: 10 | * TODO add examples here 11 | 12 | ## Credits 13 | This implementation is almost a 1:1 copy of: 14 | * [wgpu_glyph](https://github.com/hecrj/wgpu_glyph) 15 | * [glow_glyph](https://github.com/hecrj/glow_glyph) 16 | 17 | Thanks to [hecrj](https://github.com/hecrj) and [alexheretic](https://github.com/alexheretic). 18 | -------------------------------------------------------------------------------- /crates/notan_oddio/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_oddio" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Provides support for Audio features using Oddio" 11 | 12 | [dependencies] 13 | notan_audio.workspace = true 14 | 15 | log.workspace = true 16 | hashbrown.workspace = true 17 | 18 | cpal = { version = "0.15.3", features = ["wasm-bindgen"] } 19 | oddio = "0.6.2" 20 | symphonia = { version = "0.5.4", features = ["mp3"] } 21 | -------------------------------------------------------------------------------- /crates/notan_core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_core" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Basic types and structs used in Notan" 11 | 12 | [dependencies] 13 | serde = { workspace = true, optional = true } 14 | 15 | [target.'cfg(target_arch = "wasm32")'.dependencies] 16 | web-sys = { workspace = true, optional = true } 17 | 18 | [features] 19 | links = [] 20 | drop_files = ["web-sys", "web-sys/File"] 21 | clipboard = [] 22 | -------------------------------------------------------------------------------- /crates/notan_draw/src/texts.rs: -------------------------------------------------------------------------------- 1 | mod painter; 2 | mod text; 3 | 4 | use crate::builder::DrawBuilder; 5 | use crate::draw::Draw; 6 | pub use notan_text::{CreateFont, Font}; 7 | pub use painter::create_text_pipeline; 8 | pub(crate) use painter::*; 9 | pub use text::*; 10 | 11 | pub trait DrawTextSection { 12 | fn text<'a>(&mut self, font: &'a Font, text: &'a str) -> DrawBuilder>; 13 | } 14 | 15 | impl DrawTextSection for Draw { 16 | fn text<'a>(&mut self, font: &'a Font, text: &'a str) -> DrawBuilder> { 17 | DrawBuilder::new(self, TextSection::new(font, text)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /crates/notan_math/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_math" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Provides simple set of math's utils for Notan" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | glam = { version = "0.29", features = ["bytemuck"] } 16 | serde = { workspace = true, optional = true } 17 | 18 | [features] 19 | serde = ["dep:serde", "glam/serde"] 20 | -------------------------------------------------------------------------------- /crates/notan_random/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_random" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Provides a simple set of RNG utils for Notan" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | rand = "0.8.5" 16 | rand_pcg = "0.3.1" 17 | 18 | [target.'cfg(target_arch = "wasm32")'.dependencies] 19 | getrandom = { version = "0.2.15", features = ["js"] } 20 | -------------------------------------------------------------------------------- /crates/notan_input/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_input" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Provides a set of API to manage user's input" 11 | 12 | [dependencies] 13 | notan_core.workspace = true 14 | notan_math.workspace = true 15 | 16 | hashbrown.workspace = true 17 | log.workspace = true 18 | serde = { workspace = true, optional = true } 19 | 20 | [features] 21 | serde = ["dep:serde", "notan_core/serde", "notan_math/serde", "hashbrown/serde"] 22 | -------------------------------------------------------------------------------- /crates/notan_macro/build.rs: -------------------------------------------------------------------------------- 1 | use cfg_aliases::cfg_aliases; 2 | 3 | fn main() { 4 | // We're defining features here to make it easy to swap between 5 | // naga, glsl-to-spirv and shaderc keeping a priority order 6 | // without emit compile errors that can mess with compilations 7 | // wit the --all-features flag enabled. 8 | 9 | // TODO: add naga once the PR lands 10 | cfg_aliases! { 11 | use_glsl_to_spirv: { all(feature = "glsl-to-spirv", not(feature = "shaderc")) }, 12 | use_shaderc: { feature = "shaderc" }, 13 | shader_compilation: { any(use_glsl_to_spirv, use_shaderc) } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/draw_path.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[notan_main] 5 | fn main() -> Result<(), String> { 6 | notan::init().add_config(DrawConfig).draw(draw).build() 7 | } 8 | 9 | fn draw(gfx: &mut Graphics) { 10 | let mut draw = gfx.create_draw(); 11 | draw.clear(Color::BLACK); 12 | 13 | draw.path() 14 | .move_to(10.0, 10.0) 15 | .line_to(100.0, 100.0) 16 | .line_to(400.0, 500.0) 17 | .quadratic_bezier_to((440.0, 440.0), (310.0, 210.0)) 18 | .line_to(790.0, 590.0) 19 | .round_join() 20 | .color(Color::ORANGE) 21 | .stroke(10.0); 22 | 23 | gfx.render(&draw); 24 | } 25 | -------------------------------------------------------------------------------- /examples/window_transparent.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[notan_main] 5 | fn main() -> Result<(), String> { 6 | let win = WindowConfig::default() 7 | .set_transparent(true) 8 | .set_decorations(false); 9 | notan::init() 10 | .add_config(win) 11 | .add_config(DrawConfig) // Simple way to add the draw extension 12 | .draw(draw) 13 | .build() 14 | } 15 | 16 | fn draw(gfx: &mut Graphics) { 17 | let mut draw = gfx.create_draw(); 18 | draw.clear(Color::TRANSPARENT); 19 | draw.triangle((400.0, 100.0), (100.0, 500.0), (700.0, 500.0)) 20 | .color(Color::MAGENTA); 21 | gfx.render(&draw); 22 | } 23 | -------------------------------------------------------------------------------- /examples/renderer_clear.rs: -------------------------------------------------------------------------------- 1 | use notan::prelude::*; 2 | 3 | #[notan_main] 4 | fn main() -> Result<(), String> { 5 | notan::init().draw(draw).build() 6 | } 7 | 8 | fn draw(app: &mut App, gfx: &mut Graphics) { 9 | // "Random" color bases on the app's time 10 | let color = Color::from_rgb( 11 | app.timer.elapsed_f32().cos(), 12 | app.timer.elapsed_f32().sin(), 13 | 1.0, 14 | ); 15 | 16 | // create a renderer object 17 | let mut renderer = gfx.create_renderer(); 18 | 19 | // begin a pass to clear the screen 20 | renderer.begin(Some(ClearOptions::color(color))); 21 | renderer.end(); 22 | 23 | // render to screen 24 | gfx.render(&renderer); 25 | } 26 | -------------------------------------------------------------------------------- /examples/window_config.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use notan::prelude::*; 4 | 5 | #[notan_main] 6 | fn main() -> Result<(), String> { 7 | // Check the documentation for more options 8 | let window_config = WindowConfig::new() 9 | .set_title("Window Config Demo") 10 | .set_size(1026, 600) // window's size 11 | .set_vsync(true) // enable vsync 12 | .set_resizable(true) // window can be resized 13 | .set_min_size(600, 400) // Set a minimum window size 14 | .set_window_icon(Some(PathBuf::from("./examples/assets/rust.ico"))) 15 | .set_taskbar_icon(Some(PathBuf::from("./examples/assets/rust.ico"))); 16 | 17 | notan::init().add_config(window_config).build() 18 | } 19 | -------------------------------------------------------------------------------- /examples/draw_blend_mode.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[notan_main] 5 | fn main() -> Result<(), String> { 6 | notan::init().add_config(DrawConfig).draw(draw).build() 7 | } 8 | 9 | fn draw(gfx: &mut Graphics) { 10 | let mut draw = gfx.create_draw(); 11 | draw.clear(Color::new(0.1, 0.2, 0.3, 1.0)); 12 | 13 | // set and additive color blend for everything draw from here 14 | draw.set_blend_mode(Some(BlendMode::ADD)); 15 | 16 | draw.circle(150.0) 17 | .position(400.0, 225.0) 18 | .color(Color::GREEN); 19 | 20 | draw.circle(150.0).position(325.0, 375.0).color(Color::RED); 21 | 22 | draw.circle(150.0).position(475.0, 375.0).color(Color::BLUE); 23 | 24 | gfx.render(&draw); 25 | } 26 | -------------------------------------------------------------------------------- /scripts/publish.sh: -------------------------------------------------------------------------------- 1 | # Script taken from BevyEngine https://github.com/bevyengine/bevy/blob/main/tools/publish.sh 2 | 3 | # if crate A depends on crate B, B must come before A in this list 4 | crates=( 5 | notan_core 6 | notan_math 7 | notan_input 8 | notan_audio 9 | notan_random 10 | notan_utils 11 | notan_macro 12 | notan_graphics 13 | notan_app 14 | notan_log 15 | notan_glow 16 | notan_oddio 17 | notan_glyph 18 | notan_egui 19 | notan_text 20 | notan_draw 21 | notan_web 22 | notan_winit 23 | notan_backend 24 | notan_extra 25 | ) 26 | 27 | cd crates 28 | for crate in "${crates[@]}" 29 | do 30 | echo "Publishing ${crate}" 31 | (cd "$crate"; cargo publish --no-verify) 32 | sleep 30 33 | done 34 | 35 | cd .. 36 | cargo publish 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | **/target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | **/Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | #Added by cargo 14 | # 15 | #already existing elements are commented out 16 | 17 | #**/*.rs.bk 18 | 19 | **/generated 20 | **/output 21 | **/.idea 22 | **/.vscode 23 | pkg 24 | **/notes.txt 25 | 26 | 27 | # webpage generated 28 | **/docs/**/* 29 | 30 | # allow static images 31 | !**/docs/examples/images/**/*.jpg 32 | 33 | # discard test examples 34 | examples/test_*.rs 35 | 36 | .DS_Store 37 | -------------------------------------------------------------------------------- /scripts/take_screenshots.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # this script needs `scrot` installed on a linux machine 4 | run() { 5 | features=glyph,egui,text,extra,audio,links,drop_files,clipboard,save_file,texture_to_file 6 | cargo run --example "$f" --features=$features 7 | } 8 | 9 | finish() { 10 | scrot "./docs/examples/images/$f.jpg" -u -d 30 11 | path="target/debug/examples/$f" 12 | p=$(ps -ef | awk -v path="$path" '$8==path {print $2}') 13 | kill "$p" 14 | } 15 | 16 | mkdir -p ./docs/examples/images 17 | for f in ./examples/*.rs; do 18 | f=$f 19 | f=${f/\.\/examples\//""} 20 | f=${f/.rs/""} 21 | # take a screenshot if it doesn't exists 22 | if [ ! -f ./docs/examples/images/"$f".jpg ]; then 23 | run "$f" & 24 | finish "$f" 25 | fi 26 | done 27 | 28 | -------------------------------------------------------------------------------- /crates/notan_draw/src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::DrawExtension; 2 | use notan_app::{AppBuilder, AppState, BackendSystem, BuildConfig, Graphics}; 3 | use notan_text::*; 4 | 5 | pub struct DrawConfig; 6 | impl BuildConfig for DrawConfig 7 | where 8 | S: AppState + 'static, 9 | B: BackendSystem, 10 | { 11 | fn apply(&self, builder: AppBuilder) -> AppBuilder { 12 | builder.add_graphic_ext(|gfx: &mut Graphics| { 13 | // Add text extension if necessary 14 | if gfx.extension::().is_none() { 15 | let text_ext = TextExtension::new(gfx).unwrap(); 16 | gfx.add_extension(text_ext); 17 | } 18 | 19 | DrawExtension::new(gfx).unwrap() 20 | }) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /crates/notan_glyph/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_glyph" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Provides glyph's support for Notan" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | notan_app.workspace = true 16 | notan_graphics.workspace = true 17 | notan_math.workspace = true 18 | notan_macro.workspace = true 19 | 20 | log.workspace = true 21 | bytemuck.workspace = true 22 | 23 | glyph_brush = "0.7.11" 24 | 25 | [features] 26 | glsl-to-spirv = ["notan_macro/glsl-to-spirv"] 27 | shaderc = ["notan_macro/shaderc"] 28 | -------------------------------------------------------------------------------- /examples/draw_image.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[derive(AppState)] 5 | struct State { 6 | img: Texture, 7 | } 8 | 9 | #[notan_main] 10 | fn main() -> Result<(), String> { 11 | notan::init_with(init) 12 | .add_config(DrawConfig) 13 | .draw(draw) 14 | .build() 15 | } 16 | 17 | fn init(gfx: &mut Graphics) -> State { 18 | let texture = gfx 19 | .create_texture() 20 | .from_image(include_bytes!("assets/ferris.png")) 21 | .build() 22 | .unwrap(); 23 | State { img: texture } 24 | } 25 | 26 | fn draw(gfx: &mut Graphics, state: &mut State) { 27 | let mut draw = gfx.create_draw(); 28 | draw.clear(Color::BLACK); 29 | draw.image(&state.img).position(250.0, 200.0); 30 | gfx.render(&draw); 31 | } 32 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Notan 2 | 3 | ## Linux 4 | > TODO 5 | 6 | ## MacOS 7 | > TODO 8 | 9 | ## Windows (MSVC) 10 | 11 | To be able to compile the Notan examples you will need to have the following pre-requirements in your system: 12 | 13 | * [CMake](https://cmake.org/download/) 14 | * [Python](https://www.python.org/downloads/) 15 | * [Ninja](https://github.com/ninja-build/ninja/wiki/Pre-built-Ninja-packages) 16 | 17 | If you want to use the wasm32 target you will need to execute some additional commands for it: 18 | ````cmd 19 | rustup target add wasm32-unknown-unknown 20 | cargo install -f wasm-bindgen-cli --version 0.2.87 21 | cargo install wasm-opt 22 | ```` 23 | 24 | After this you will be able to use the PowerShell scripts in the `scripts` folder to compile the examples or the doc. 25 | -------------------------------------------------------------------------------- /crates/notan_egui/README.md: -------------------------------------------------------------------------------- 1 | EGUI 2 | === 3 | 4 | This is the implementation of [egui 0.27.2](https://github.com/emilk/egui) for notan. 5 | 6 | It should support all the features that __egui__ uses. 7 | 8 | You can check some examples at `/examples` or check the demos online: 9 | * [egui_basic](https://nazariglez.github.io/notan-web/examples/egui_basic.html) 10 | * [egui_render_texture](https://nazariglez.github.io/notan-web/examples/egui_render_texture.html) 11 | * [egui_shape_widget](https://nazariglez.github.io/notan-web/examples/egui_shape_widget.html) 12 | * [egui_texture](https://nazariglez.github.io/notan-web/examples/egui_texture.html) 13 | * [egui_demo](https://nazariglez.github.io/notan-web/examples/egui_demo.html) 14 | * [egui_paint](https://nazariglez.github.io/notan-web/examples/egui_paint.html) 15 | -------------------------------------------------------------------------------- /crates/notan_egui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_egui" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Provides EGUI support for Notan" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | notan_core.workspace = true 16 | notan_app.workspace = true 17 | notan_macro.workspace = true 18 | 19 | log.workspace = true 20 | bytemuck.workspace = true 21 | 22 | egui = { version = "0.31.0", features = ["bytemuck"] } 23 | 24 | [features] 25 | links = [] 26 | drop_files = [] 27 | glsl-to-spirv = ["notan_macro/glsl-to-spirv"] 28 | shaderc = ["notan_macro/shaderc"] 29 | -------------------------------------------------------------------------------- /crates/notan_text/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_text" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Provides a simple Text API for Notan" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | notan_app.workspace = true 16 | notan_graphics.workspace = true 17 | notan_glyph.workspace = true 18 | notan_math.workspace = true 19 | 20 | log.workspace = true 21 | parking_lot.workspace = true 22 | hashbrown.workspace = true 23 | 24 | lazy_static = "1.5.0" 25 | 26 | [features] 27 | glsl-to-spirv = ["notan_glyph/glsl-to-spirv"] 28 | shaderc = ["notan_glyph/shaderc"] 29 | -------------------------------------------------------------------------------- /crates/notan_graphics/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_graphics" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Provides simple graphics API for Notan" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | notan_math.workspace = true 16 | notan_utils.workspace = true 17 | 18 | log.workspace = true 19 | bytemuck.workspace = true 20 | parking_lot.workspace = true 21 | image = { workspace = true, optional = true } 22 | 23 | crevice_notan = { version = "0.14.1" } 24 | serde = { workspace = true, optional = true } 25 | 26 | [features] 27 | texture_to_file = ["notan_utils/save_file", "image/png"] 28 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | on: 3 | schedule: 4 | - cron: "30 11 * * *" 5 | 6 | jobs: 7 | close-issues: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/stale@v5 14 | with: 15 | days-before-issue-stale: 1095 16 | days-before-issue-close: 30 17 | stale-issue-label: "stale" 18 | stale-issue-message: "This issue is stale because it has been open for 3 year with no activity." 19 | close-issue-message: "This issue was closed because it has been inactive for 30 days since being marked as stale. Do not hesitate to open a new issue if you need it." 20 | days-before-pr-stale: -1 21 | days-before-pr-close: -1 22 | repo-token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /crates/notan_backend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_backend" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Provides a default backend for Notan" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [target.'cfg(target_arch = "wasm32")'.dependencies] 15 | notan_web.workspace = true 16 | 17 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 18 | notan_winit.workspace = true 19 | 20 | [features] 21 | audio = ["notan_web/audio", "notan_winit/audio"] 22 | links = ["notan_winit/links"] 23 | drop_files = ["notan_winit/drop_files", "notan_web/drop_files"] 24 | clipboard = ["notan_winit/clipboard", "notan_web/clipboard"] 25 | -------------------------------------------------------------------------------- /crates/notan_log/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_log" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Provides a multipatform log support for Notan" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | notan_app.workspace = true 16 | 17 | log.workspace = true 18 | 19 | fern = { version = "0.7.1", features = ["colored"] } 20 | 21 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 22 | time = { version = "0.3.37", features = ["formatting", "local-offset"] } 23 | 24 | [target.'cfg(target_arch = "wasm32")'.dependencies] 25 | wasm-bindgen.workspace = true 26 | js-sys.workspace = true 27 | 28 | console_log = "1.0.0" 29 | -------------------------------------------------------------------------------- /examples/egui_demo.rs: -------------------------------------------------------------------------------- 1 | use notan::egui::*; 2 | use notan::prelude::*; 3 | 4 | #[derive(Default, AppState)] 5 | struct State { 6 | demo: egui_demo_lib::DemoWindows, 7 | } 8 | 9 | #[notan_main] 10 | fn main() -> Result<(), String> { 11 | let win = WindowConfig::default() 12 | .set_resizable(true) 13 | .set_size(1280, 1024) 14 | .set_vsync(true) 15 | .set_high_dpi(true) 16 | // enable lazy mode to only draw after an input 17 | .set_lazy_loop(true); 18 | 19 | notan::init_with(State::default) 20 | .add_config(win) 21 | .add_config(EguiConfig) 22 | .draw(draw) 23 | .build() 24 | } 25 | 26 | fn draw(gfx: &mut Graphics, plugins: &mut Plugins, state: &mut State) { 27 | let mut output = plugins.egui(|ctx| state.demo.ui(ctx)); 28 | output.clear_color(Color::BLACK); 29 | gfx.render(&output); 30 | } 31 | -------------------------------------------------------------------------------- /examples/draw_text.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[derive(AppState)] 5 | struct State { 6 | font: Font, 7 | } 8 | 9 | #[notan_main] 10 | fn main() -> Result<(), String> { 11 | notan::init_with(setup) 12 | .add_config(DrawConfig) 13 | .draw(draw) 14 | .build() 15 | } 16 | 17 | fn setup(gfx: &mut Graphics) -> State { 18 | let font = gfx 19 | .create_font(include_bytes!("assets/Ubuntu-B.ttf")) 20 | .unwrap(); 21 | State { font } 22 | } 23 | 24 | fn draw(gfx: &mut Graphics, state: &mut State) { 25 | let mut draw = gfx.create_draw(); 26 | draw.clear(Color::BLACK); 27 | 28 | draw.text(&state.font, "Hello World!") 29 | .position(400.0, 300.0) 30 | .size(60.0) 31 | .color(Color::ORANGE) 32 | .h_align_center() 33 | .v_align_middle(); 34 | 35 | gfx.render(&draw); 36 | } 37 | -------------------------------------------------------------------------------- /crates/notan_glow/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_glow" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Provides support for OpenGL, OpenGL ES and WebGL for Notan" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | notan_graphics.workspace = true 16 | 17 | log.workspace = true 18 | bytemuck.workspace = true 19 | hashbrown.workspace = true 20 | image.workspace = true 21 | 22 | glow = "0.16.0" 23 | 24 | [target.'cfg(target_arch = "wasm32")'.dependencies] 25 | wasm-bindgen.workspace = true 26 | js-sys.workspace = true 27 | web-sys = { workspace = true, features = ["Window", "WebGlContextAttributes", "WebGlPowerPreference", "HtmlCanvasElement","HtmlImageElement"] } 28 | -------------------------------------------------------------------------------- /crates/notan_winit/src/touch.rs: -------------------------------------------------------------------------------- 1 | use notan_core::events::Event; 2 | use winit::event::{Touch, TouchPhase, WindowEvent}; 3 | 4 | pub fn process_events(event: &WindowEvent, scale_factor: f64) -> Option { 5 | match event { 6 | WindowEvent::Touch(Touch { 7 | phase, 8 | location, 9 | id, 10 | .. 11 | }) => { 12 | let pos = location.to_logical(scale_factor); 13 | let id = *id; 14 | let x = pos.x; 15 | let y = pos.y; 16 | Some(match phase { 17 | TouchPhase::Started => Event::TouchStart { id, x, y }, 18 | TouchPhase::Moved => Event::TouchMove { id, x, y }, 19 | TouchPhase::Ended => Event::TouchEnd { id, x, y }, 20 | TouchPhase::Cancelled => Event::TouchCancel { id, x, y }, 21 | }) 22 | } 23 | _ => None, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/draw_mask.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[notan_main] 5 | fn main() -> Result<(), String> { 6 | notan::init().add_config(DrawConfig).draw(draw).build() 7 | } 8 | 9 | fn draw(gfx: &mut Graphics) { 10 | // Draw a rectangle as mask 11 | let mut mask = gfx.create_draw(); 12 | mask.rect((180.0, 180.0), (440.0, 240.0)); 13 | 14 | let mut draw = gfx.create_draw(); 15 | draw.clear(Color::BLACK); 16 | 17 | // Draw a triangle inside the mask, it will be displayed trimmed by the mask rectangle 18 | draw.mask(Some(&mask)); 19 | draw.triangle((400.0, 100.0), (100.0, 500.0), (700.0, 500.0)) 20 | .color(Color::RED); 21 | 22 | draw.mask(None); 23 | 24 | // Draw a normal triangle outside the mask 25 | draw.triangle((400.0, 100.0), (100.0, 500.0), (700.0, 500.0)) 26 | .color(Color::YELLOW) 27 | .scale(0.5, 0.5); 28 | 29 | gfx.render(&draw); 30 | } 31 | -------------------------------------------------------------------------------- /crates/notan_macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_macro" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Provides a set of utils as macros for Notan" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | syn = { version = "2.0.98", features = ["full", "extra-traits"] } 16 | quote = "1.0.38" 17 | num = "0.4.3" 18 | glsl-to-spirv = { version = "0.1.7", optional = true } 19 | shaderc = { version = "0.8.3", optional = true } 20 | proc-macro2 = "1.0.93" 21 | spirv_cross = { version = "0.23.1", features = ["glsl"] } 22 | 23 | [build-dependencies] 24 | cfg_aliases = "0.2.1" 25 | 26 | [features] 27 | glsl-to-spirv = ["dep:glsl-to-spirv"] 28 | shaderc = ["dep:shaderc"] 29 | 30 | [lib] 31 | proc-macro = true 32 | 33 | -------------------------------------------------------------------------------- /crates/notan_math/src/rect.rs: -------------------------------------------------------------------------------- 1 | #[derive(Default, Clone, Copy, Debug, PartialEq)] 2 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 3 | pub struct Rect { 4 | pub x: f32, 5 | pub y: f32, 6 | pub width: f32, 7 | pub height: f32, 8 | } 9 | 10 | impl Rect { 11 | pub fn max_x(&self) -> f32 { 12 | self.x + self.width 13 | } 14 | 15 | pub fn max_y(&self) -> f32 { 16 | self.y + self.height 17 | } 18 | 19 | pub fn min_x(&self) -> f32 { 20 | self.x 21 | } 22 | 23 | pub fn min_y(&self) -> f32 { 24 | self.y 25 | } 26 | 27 | pub fn center_x(&self) -> f32 { 28 | self.x + self.width * 0.5 29 | } 30 | 31 | pub fn center_y(&self) -> f32 { 32 | self.y + self.height * 0.5 33 | } 34 | 35 | pub fn contains(&self, x: f32, y: f32) -> bool { 36 | x >= self.min_x() && x <= self.max_x() && y >= self.min_y() && y <= self.max_y() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/draw_mask_animated.rs: -------------------------------------------------------------------------------- 1 | use notan::{draw::*, prelude::*}; 2 | 3 | fn main() { 4 | notan::init() 5 | .add_config(WindowConfig::new().set_size(500, 500)) 6 | .add_config(DrawConfig) 7 | .draw(|app: &mut App, gfx: &mut Graphics| { 8 | let mut draw = gfx.create_draw(); 9 | draw.clear(Color::BLACK); 10 | 11 | let elapsed = app.timer.elapsed_f32(); 12 | let pulse_progress = ((elapsed % 4.) - 2.).abs() / 2.; // 4s roundtrip pulse 13 | let radius = 200. * (1. - pulse_progress); 14 | 15 | let mut mask = gfx.create_draw(); 16 | mask.clear(Color::BLACK); 17 | mask.circle(radius).position(250., 250.); 18 | 19 | draw.mask(Some(&mask)); 20 | draw.rect((0., 0.), (500., 500.)).color(Color::GREEN); 21 | draw.mask(None); 22 | 23 | gfx.render(&draw); 24 | }) 25 | .build() 26 | .unwrap(); 27 | } 28 | -------------------------------------------------------------------------------- /crates/notan_draw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_draw" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Provides a simple 2D API for Notan" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | log.workspace = true 16 | notan_app.workspace = true 17 | notan_graphics.workspace = true 18 | notan_macro.workspace = true 19 | notan_math.workspace = true 20 | notan_glyph.workspace = true 21 | notan_text.workspace = true 22 | serde = { workspace = true, features = ["derive"] } 23 | 24 | lyon = "1.0.1" 25 | serde_json = "1.0.138" 26 | 27 | [features] 28 | glsl-to-spirv = ["notan_macro/glsl-to-spirv", "notan_glyph/glsl-to-spirv", "notan_text/glsl-to-spirv"] 29 | shaderc = ["notan_macro/shaderc", "notan_glyph/shaderc", "notan_text/shaderc"] 30 | -------------------------------------------------------------------------------- /crates/notan_app/src/handlers.rs: -------------------------------------------------------------------------------- 1 | use crate::app::{App, AppState}; 2 | use crate::assets::Assets; 3 | use crate::graphics::{GfxExtension, GfxRenderer, Graphics}; 4 | use crate::plugins::{Plugin, Plugins}; 5 | use notan_core::events::Event; 6 | 7 | // Order of params App, AssetManager, Graphics, GlyphManager, Plugins, S, Event 8 | notan_macro::handler_once!(Setup<&mut App, &mut Assets, &mut Graphics, &mut Plugins> -> S); 9 | notan_macro::handler_once!(Init<&mut App, &mut Assets, &mut Plugins, &mut S>); 10 | notan_macro::handler!(App<&mut App, &mut Assets, &mut Plugins, &mut S>); 11 | notan_macro::handler!(Event<&mut App, &mut Assets, &mut Plugins, &mut S, Event>); 12 | notan_macro::handler!(Draw<&mut App, &mut Assets, &mut Graphics, &mut Plugins, &mut S>); 13 | notan_macro::handler!(Plugin<&mut App, &mut Assets, &mut Graphics, &mut Plugins> -> !S); // !S stands for Plugin + 'static 14 | notan_macro::handler!(Extension<&mut App, &mut Assets, &mut Graphics, &mut Plugins> -> $S); // $S stands for GfxExtension + 'static 15 | -------------------------------------------------------------------------------- /xtask/src/cli_docs.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::process::Command; 3 | use std::{env, fs}; 4 | 5 | use crate::cli::Docs; 6 | use crate::{project_root, DynError}; 7 | 8 | impl Docs { 9 | pub(crate) fn run(self) -> Result<(), DynError> { 10 | docs_clean()?; 11 | docs_run()?; 12 | 13 | Ok(()) 14 | } 15 | } 16 | 17 | fn docs_clean() -> Result<(), DynError> { 18 | let _ = fs::remove_dir_all(dist_doc_dir()); 19 | fs::create_dir_all(dist_doc_dir())?; 20 | 21 | Ok(()) 22 | } 23 | 24 | fn docs_run() -> Result<(), DynError> { 25 | let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); 26 | let status = Command::new(cargo) 27 | .current_dir(project_root()) 28 | .args(["doc", "--all-features"]) 29 | .status()?; 30 | 31 | if !status.success() { 32 | Err("Command 'cargo doc --all-features' failed")?; 33 | } 34 | 35 | Ok(()) 36 | } 37 | 38 | fn dist_doc_dir() -> PathBuf { 39 | project_root().join("target/doc") 40 | } 41 | -------------------------------------------------------------------------------- /crates/notan_utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_utils" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Provides a simple set of utils Notan" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | instant = { version = "0.1.13", features = ["wasm-bindgen"] } 16 | log.workspace = true 17 | 18 | [target.'cfg(target_arch = "wasm32")'.dependencies] 19 | wasm-bindgen = { workspace = true, optional = true } 20 | js-sys = { workspace = true, optional = true } 21 | web-sys = { workspace = true, optional = true } 22 | 23 | mime_guess = { version = "2.0.5", optional = true } 24 | 25 | [features] 26 | save_file = ["mime_guess", "wasm-bindgen", "js-sys", "web-sys", "web-sys?/Window", "web-sys?/Blob", "web-sys?/BlobPropertyBag", "web-sys?/Url", "web-sys?/Element", "web-sys?/HtmlAnchorElement"] 27 | -------------------------------------------------------------------------------- /crates/notan_text/src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::{Font, Text, TextExtension}; 2 | use notan_app::assets::AssetLoader; 3 | use notan_app::{AppBuilder, AppState, BackendSystem, BuildConfig, Graphics}; 4 | 5 | pub struct TextConfig; 6 | impl BuildConfig for TextConfig 7 | where 8 | S: AppState + 'static, 9 | B: BackendSystem, 10 | { 11 | fn apply(&self, builder: AppBuilder) -> AppBuilder { 12 | builder 13 | .add_graphic_ext(|gfx: &mut Graphics| TextExtension::new(gfx).unwrap()) 14 | .add_loader( 15 | AssetLoader::new() 16 | .use_parser(parse_font) 17 | .extensions(&["ttf"]), 18 | ) 19 | } 20 | } 21 | 22 | fn parse_font(id: &str, data: Vec, gfx: &mut Graphics) -> Result { 23 | let font = gfx 24 | .extension_mut::() 25 | .ok_or("TextExtension is not added to Graphics")? 26 | .create_font(&data)?; 27 | log::debug!("Asset '{}' parsed as TextExtension Font", id); 28 | Ok(font) 29 | } 30 | -------------------------------------------------------------------------------- /crates/notan_input/src/internals.rs: -------------------------------------------------------------------------------- 1 | // functions or utils meant to be used for another notan_Crates but not to be used by the users 2 | // nothing here should be re-exported by notan or the preludes 3 | 4 | use crate::keyboard::Keyboard; 5 | use crate::mouse::Mouse; 6 | use crate::touch::Touch; 7 | use notan_core::events::Event; 8 | 9 | #[inline] 10 | pub fn clear_mouse(mouse: &mut Mouse) { 11 | mouse.clear(); 12 | } 13 | 14 | #[inline] 15 | pub fn process_mouse_events(mouse: &mut Mouse, event: &Event, delta: f32) { 16 | mouse.process_events(event, delta); 17 | } 18 | 19 | #[inline] 20 | pub fn clear_keyboard(keyboard: &mut Keyboard) { 21 | keyboard.clear(); 22 | } 23 | 24 | #[inline] 25 | pub fn process_keyboard_events(keyboard: &mut Keyboard, event: &Event, delta: f32) { 26 | keyboard.process_events(event, delta); 27 | } 28 | 29 | #[inline] 30 | pub fn clear_touch(touch: &mut Touch) { 31 | touch.clear(); 32 | } 33 | 34 | #[inline] 35 | pub fn process_touch_events(touch: &mut Touch, event: &Event, delta: f32) { 36 | touch.process_events(event, delta); 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /examples/egui_basic.rs: -------------------------------------------------------------------------------- 1 | use notan::egui::{self, *}; 2 | use notan::prelude::*; 3 | 4 | #[notan_main] 5 | fn main() -> Result<(), String> { 6 | let win = WindowConfig::new() 7 | .set_vsync(true) 8 | .set_lazy_loop(true) 9 | .set_high_dpi(true); 10 | 11 | notan::init() 12 | .add_config(win) 13 | .add_config(EguiConfig) 14 | .draw(draw) 15 | .build() 16 | } 17 | 18 | fn draw(app: &mut App, gfx: &mut Graphics, plugins: &mut Plugins) { 19 | let mut output = plugins.egui(|ctx| { 20 | egui::SidePanel::left("side_panel").show(ctx, |ui| { 21 | ui.heading("Egui Plugin Example"); 22 | 23 | ui.separator(); 24 | if ui.button("Quit").clicked() { 25 | app.exit(); 26 | } 27 | 28 | ui.separator(); 29 | ui.label("Welcome to a basic example of how to use Egui with notan."); 30 | 31 | ui.separator(); 32 | ui.label("Check the source code to learn more about how it works"); 33 | }); 34 | }); 35 | 36 | output.clear_color(Color::BLACK); 37 | gfx.render(&output); 38 | } 39 | -------------------------------------------------------------------------------- /crates/notan_graphics/src/commands.rs: -------------------------------------------------------------------------------- 1 | use crate::color::Color; 2 | use crate::pipeline::*; 3 | 4 | #[allow(unused)] 5 | #[derive(Debug, Clone)] 6 | pub enum Commands { 7 | Size { 8 | width: u32, 9 | height: u32, 10 | }, 11 | Viewport { 12 | x: f32, 13 | y: f32, 14 | width: f32, 15 | height: f32, 16 | }, 17 | Begin { 18 | color: Option, 19 | depth: Option, 20 | stencil: Option, 21 | }, 22 | End, 23 | Pipeline { 24 | id: u64, 25 | options: PipelineOptions, 26 | }, 27 | BindBuffer { 28 | id: u64, 29 | }, 30 | BindTexture { 31 | id: u64, 32 | slot: u32, 33 | location: u32, 34 | }, 35 | Scissors { 36 | x: f32, 37 | y: f32, 38 | width: f32, 39 | height: f32, 40 | }, 41 | Draw { 42 | primitive: DrawPrimitive, 43 | offset: i32, 44 | count: i32, 45 | }, 46 | DrawInstanced { 47 | primitive: DrawPrimitive, 48 | offset: i32, 49 | count: i32, 50 | length: i32, 51 | }, 52 | } 53 | -------------------------------------------------------------------------------- /examples/window_focus.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[derive(AppState)] 5 | struct State { 6 | font: Font, 7 | } 8 | 9 | #[notan_main] 10 | fn main() -> Result<(), String> { 11 | notan::init_with(setup) 12 | .add_config(DrawConfig) 13 | .draw(draw) 14 | .build() 15 | } 16 | 17 | fn setup(gfx: &mut Graphics) -> State { 18 | let font = gfx 19 | .create_font(include_bytes!("assets/Ubuntu-B.ttf")) 20 | .unwrap(); 21 | State { font } 22 | } 23 | 24 | fn draw(app: &mut App, gfx: &mut Graphics, state: &mut State) { 25 | let window = app.window(); 26 | let ww = window.width() as f32; 27 | let hh = window.height() as f32; 28 | 29 | let text = if window.is_focused() { 30 | "Window is currently focused" 31 | } else { 32 | "Window is no longer focused" 33 | }; 34 | 35 | let mut draw = gfx.create_draw(); 36 | draw.clear(Color::BLACK); 37 | draw.text(&state.font, text) 38 | .position(ww * 0.5, hh * 0.5) 39 | .size(40.0) 40 | .h_align_center() 41 | .v_align_middle(); 42 | 43 | gfx.render(&draw); 44 | } 45 | -------------------------------------------------------------------------------- /xtask/src/cli_examples_msvc.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::{Example, Examples}; 2 | use crate::cli_example_msvc::docs_msvc_dir; 3 | use crate::{copy_assets, project_root, DynError}; 4 | 5 | impl Examples { 6 | pub(crate) fn run_msvc(self) -> Result<(), DynError> { 7 | copy_assets(docs_msvc_dir(self.release).join("assets")); 8 | 9 | let examples_path = project_root() 10 | .join("examples") 11 | .to_string_lossy() 12 | .into_owned(); 13 | 14 | let target = self.target; 15 | let release = self.release; 16 | let gzip = self.gzip; 17 | 18 | self.list_files(examples_path.as_str())? 19 | .for_each(|example| { 20 | let name = example.file_stem().unwrap().to_str().unwrap().to_owned(); 21 | // eprintln!("{}", name); 22 | let example = Example { 23 | name, 24 | target, 25 | release, 26 | no_assets: true, 27 | gzip, 28 | }; 29 | 30 | let _ = example.run(); 31 | }); 32 | 33 | Ok(()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/draw_image_crop.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[derive(AppState)] 5 | struct State { 6 | img: Texture, 7 | } 8 | 9 | #[notan_main] 10 | fn main() -> Result<(), String> { 11 | notan::init_with(init) 12 | .add_config(DrawConfig) 13 | .draw(draw) 14 | .build() 15 | } 16 | 17 | fn init(gfx: &mut Graphics) -> State { 18 | let texture = gfx 19 | .create_texture() 20 | .from_image(include_bytes!("assets/rust-logo-512x512.png")) 21 | .build() 22 | .unwrap(); 23 | State { img: texture } 24 | } 25 | 26 | fn draw(gfx: &mut Graphics, state: &mut State) { 27 | let (ww, hh) = state.img.size(); 28 | 29 | let mut draw = gfx.create_draw(); 30 | draw.clear(Color::WHITE); 31 | 32 | // Right side of the logo 33 | draw.image(&state.img) 34 | .position(100.0, 50.0) 35 | .size(ww * 0.5, hh) 36 | .crop((ww * 0.5, 0.0), (ww * 0.5, hh)); 37 | 38 | // Left side of the logo 39 | draw.image(&state.img) 40 | .position(450.0, 50.0) 41 | .size(ww * 0.5, hh) 42 | .crop((0.0, 0.0), (ww * 0.5, hh)); 43 | 44 | gfx.render(&draw); 45 | } 46 | -------------------------------------------------------------------------------- /crates/notan_app/src/assets/waker.rs: -------------------------------------------------------------------------------- 1 | use futures::task::{RawWaker, RawWakerVTable, Waker}; 2 | 3 | // This is a non operative Waker to simulate an future context 4 | // To load asset we don't need an executor, on wasm32 web-sys will do the trick, no native the assets are loaded sync 5 | // But we want to expose a future API to be compatible between platforms, and to do that 6 | // the poll API forces us to pass a context. 7 | // This code is based on this example https://github.com/jkarneges/rust-executor-example/blob/master/async.rs 8 | static VTABLE: RawWakerVTable = RawWakerVTable::new(vt_clone, vt_dummy, vt_dummy, vt_dummy); 9 | pub(crate) struct DummyWaker; 10 | impl DummyWaker { 11 | //Noop 12 | pub fn into_task_waker(self) -> Waker { 13 | unsafe { 14 | let w = Box::new(self); 15 | let rw = RawWaker::new(Box::into_raw(w) as *mut (), &VTABLE); 16 | Waker::from_raw(rw) 17 | } 18 | } 19 | } 20 | 21 | unsafe fn vt_clone(data: *const ()) -> RawWaker { 22 | let w = (data as *const DummyWaker).as_ref().unwrap(); 23 | let new_w = Box::new(<&DummyWaker>::clone(&w)); 24 | RawWaker::new(Box::into_raw(new_w) as *mut (), &VTABLE) 25 | } 26 | 27 | unsafe fn vt_dummy(_data: *const ()) {} 28 | -------------------------------------------------------------------------------- /src/notan.rs: -------------------------------------------------------------------------------- 1 | use crate::app::{AppBuilder, BackendSystem, SetupHandler}; 2 | 3 | #[cfg(not(feature = "backend"))] 4 | use crate::app::empty::EmptyBackend as DefaultBackend; 5 | 6 | #[cfg(feature = "backend")] 7 | use notan_backend::DefaultBackend; 8 | #[cfg(feature = "log")] 9 | use notan_log::LogConfig; 10 | 11 | /// Initialize the app with the default backend and with an empty state 12 | pub fn init() -> AppBuilder<(), DefaultBackend> { 13 | init_with::<(), fn() -> (), ()>(|| {}) 14 | } 15 | 16 | /// Initialize the app with a custom state and the default backend 17 | pub fn init_with(setup: H) -> AppBuilder 18 | where 19 | S: 'static, 20 | H: SetupHandler, 21 | { 22 | init_with_backend(setup, DefaultBackend::new().unwrap()) 23 | } 24 | 25 | /// Initialize the app using a custom state and a custom backend implementation 26 | pub fn init_with_backend(setup: H, backend: B) -> AppBuilder 27 | where 28 | S: 'static, 29 | B: BackendSystem + 'static, 30 | H: SetupHandler, 31 | { 32 | let builder = AppBuilder::new(setup, backend); 33 | #[cfg(feature = "log")] 34 | let builder = builder.add_config(LogConfig::default()); 35 | builder 36 | } 37 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod notan; 2 | pub mod prelude; 3 | 4 | pub use crate::notan::*; 5 | #[doc(inline)] 6 | pub use notan_app as app; 7 | #[doc(inline)] 8 | pub use notan_graphics as graphics; 9 | #[doc(inline)] 10 | pub use notan_input as input; 11 | #[doc(inline)] 12 | pub use notan_macro::*; 13 | #[doc(inline)] 14 | pub use notan_math as math; 15 | #[doc(inline)] 16 | pub use notan_utils as utils; 17 | 18 | #[doc(inline)] 19 | pub use notan_core::events::Event; 20 | 21 | #[doc(inline)] 22 | #[cfg(feature = "backend")] 23 | pub use notan_backend as backend; 24 | 25 | #[doc(inline)] 26 | #[cfg(feature = "audio")] 27 | pub use notan_audio as audio; 28 | 29 | #[doc(inline)] 30 | #[cfg(feature = "log")] 31 | pub use notan_log as log; 32 | 33 | #[doc(inline)] 34 | #[cfg(feature = "glyph")] 35 | pub use notan_glyph as glyph; 36 | 37 | #[doc(inline)] 38 | #[cfg(feature = "draw")] 39 | pub use notan_draw as draw; 40 | 41 | #[doc(inline)] 42 | #[cfg(feature = "egui")] 43 | pub use notan_egui as egui; 44 | 45 | #[doc(inline)] 46 | #[cfg(feature = "text")] 47 | pub use notan_text as text; 48 | 49 | #[doc(inline)] 50 | #[cfg(feature = "extra")] 51 | pub use notan_extra as extra; 52 | 53 | #[doc(inline)] 54 | #[cfg(feature = "random")] 55 | pub use notan_random as random; 56 | -------------------------------------------------------------------------------- /crates/notan_winit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_winit" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Provides a native backend using winit for Notan" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | notan_core.workspace = true 16 | notan_app.workspace = true 17 | notan_glow.workspace = true 18 | notan_audio = { workspace = true, optional = true } 19 | notan_oddio = { workspace = true, optional = true } 20 | notan_input = { workspace = true, optional = true } 21 | 22 | image.workspace = true 23 | log.workspace = true 24 | 25 | glutin = "0.30.10" 26 | glutin-winit = "0.3.0" 27 | winit = "0.28.7" 28 | raw-window-handle = "0.5.2" 29 | arboard = { version = "3.4.1", optional = true, default-features = false } 30 | webbrowser = { version = "0.8.15", optional = true } 31 | mime_guess = { version = "2.0.5", optional = true } 32 | 33 | [features] 34 | audio = ["notan_app/audio", "notan_audio", "notan_oddio"] 35 | links = ["webbrowser"] 36 | drop_files = ["mime_guess"] 37 | clipboard = ["arboard", "notan_input"] 38 | -------------------------------------------------------------------------------- /examples/text_hello.rs: -------------------------------------------------------------------------------- 1 | use notan::prelude::*; 2 | use notan::text::*; 3 | 4 | #[derive(AppState)] 5 | struct State { 6 | font: Font, 7 | font2: Font, 8 | } 9 | 10 | #[notan_main] 11 | fn main() -> Result<(), String> { 12 | notan::init_with(setup) 13 | .add_config(TextConfig) 14 | .draw(draw) 15 | .build() 16 | } 17 | 18 | fn setup(gfx: &mut Graphics) -> State { 19 | let font = gfx 20 | .create_font(include_bytes!("./assets/Ubuntu-B.ttf")) 21 | .unwrap(); 22 | 23 | let font2 = gfx 24 | .create_font(include_bytes!("./assets/kenney_pixel-webfont.ttf")) 25 | .unwrap(); 26 | 27 | State { font, font2 } 28 | } 29 | 30 | fn draw(gfx: &mut Graphics, state: &mut State) { 31 | let mut text = gfx.create_text(); 32 | text.clear_color(Color::BLACK); 33 | 34 | text.add("Hello ") 35 | .font(&state.font) 36 | .position(400.0, 30.0) 37 | .h_align_center() 38 | .color(Color::ORANGE) 39 | .size(30.0); 40 | 41 | text.chain("Notan! ").size(50.0).color(Color::RED); 42 | 43 | text.chain("(Using TextExtension)") 44 | .font(&state.font2) 45 | .size(20.0) 46 | .color(Color::GRAY.with_alpha(0.5)); 47 | 48 | gfx.render(&text); 49 | } 50 | -------------------------------------------------------------------------------- /examples/assets/lorem.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse malesuada nisl non mi pharetra, a euismod mi volutpat. Pellentesque dictum turpis id lorem ornare, quis commodo ipsum tempus. Ut a nulla sed leo ullamcorper dignissim. Nullam in dolor nunc. Phasellus vitae neque malesuada, ultrices elit vel, dapibus turpis. Aenean sodales nulla ac mauris rutrum, vel fringilla metus tincidunt. Proin ultricies ultricies posuere. Sed cursus, mauris at maximus mollis, enim nisl sodales est, sed porta justo nisi quis tortor. Aenean ornare, sem dignissim scelerisque posuere, ligula quam eleifend diam, sit amet suscipit nibh lacus at purus. Nunc vel rhoncus purus, in accumsan magna. Proin diam sem, dapibus et felis nec, vestibulum varius turpis. Donec condimentum justo nec ipsum laoreet, ac consectetur sapien luctus. 2 | 3 | Sed sit amet elit placerat, efficitur ligula sit amet, sagittis erat. Nam dapibus risus et quam fringilla rutrum. Nullam malesuada pulvinar arcu, quis iaculis enim ultricies non. Proin vel eleifend eros. Nam iaculis lacus arcu, id malesuada dui gravida eu. Cras interdum efficitur mauris, vel suscipit ipsum iaculis et. Aenean vel elementum nunc. Maecenas erat urna, rhoncus dignissim egestas facilisis, tincidunt sed ipsum. Ut pulvinar nisl a rutrum tincidunt. 4 | -------------------------------------------------------------------------------- /crates/notan_graphics/src/to_file.rs: -------------------------------------------------------------------------------- 1 | use crate::Device; 2 | use crate::Texture; 3 | use image::ColorType; 4 | use notan_utils::save_file; 5 | 6 | pub(crate) fn save_to_png_file>( 7 | gfx: &mut Device, 8 | texture: &Texture, 9 | inverse: bool, 10 | path: P, 11 | ) -> Result<(), String> { 12 | use image::ImageEncoder; 13 | 14 | let bpp = texture.format().bytes_per_pixel() as usize; 15 | let width = texture.width() as usize; 16 | let height = texture.height() as usize; 17 | let len = width * height * bpp; 18 | 19 | let mut bytes = vec![0; len]; 20 | gfx.read_pixels(texture).read_to(&mut bytes)?; 21 | 22 | if inverse { 23 | bytes = bytes.chunks(width * bpp).rev().flatten().cloned().collect(); 24 | } 25 | 26 | let p = path.as_ref(); 27 | p.with_extension(".png"); 28 | 29 | let typ = match bpp { 30 | 4 => ColorType::Rgba8, 31 | 1 => ColorType::L8, 32 | _ => return Err("Invalid type format".to_string()), 33 | }; 34 | 35 | let mut data = vec![]; 36 | let encoder = image::codecs::png::PngEncoder::new(&mut data); 37 | encoder 38 | .write_image(&bytes, width as _, height as _, typ.into()) 39 | .map_err(|e| e.to_string())?; 40 | 41 | save_file(p, &data) 42 | } 43 | -------------------------------------------------------------------------------- /examples/egui_texture.rs: -------------------------------------------------------------------------------- 1 | use notan::egui::{self, *}; 2 | use notan::prelude::*; 3 | 4 | #[derive(AppState)] 5 | struct State { 6 | sized_texture: egui::SizedTexture, 7 | } 8 | 9 | impl State { 10 | fn new(gfx: &mut Graphics) -> State { 11 | let texture = gfx 12 | .create_texture() 13 | .from_image(include_bytes!("assets/rust-logo-256x256.png")) 14 | .with_premultiplied_alpha() 15 | .build() 16 | .unwrap(); 17 | 18 | let sized_texture = gfx.egui_register_texture(&texture); 19 | 20 | Self { sized_texture } 21 | } 22 | } 23 | 24 | #[notan_main] 25 | fn main() -> Result<(), String> { 26 | let win = WindowConfig::default() 27 | .set_lazy_loop(true) 28 | .set_vsync(true) 29 | .set_high_dpi(true); 30 | 31 | notan::init_with(State::new) 32 | .add_config(win) 33 | .add_config(EguiConfig) 34 | .draw(draw) 35 | .build() 36 | } 37 | 38 | fn draw(gfx: &mut Graphics, plugins: &mut Plugins, state: &mut State) { 39 | let mut output = plugins.egui(|ctx| { 40 | egui::Window::new("Notan Texture").show(ctx, |ui| { 41 | ui.image(state.sized_texture); 42 | }); 43 | }); 44 | 45 | output.clear_color(Color::BLACK); 46 | gfx.render(&output); 47 | } 48 | -------------------------------------------------------------------------------- /examples/app_open_links.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[derive(AppState)] 5 | struct State { 6 | font: Font, 7 | } 8 | 9 | #[notan_main] 10 | fn main() -> Result<(), String> { 11 | notan::init_with(setup) 12 | .add_config(DrawConfig) 13 | .draw(draw) 14 | .update(update) 15 | .build() 16 | } 17 | 18 | fn setup(gfx: &mut Graphics) -> State { 19 | let font = gfx 20 | .create_font(include_bytes!("assets/Ubuntu-B.ttf")) 21 | .unwrap(); 22 | State { font } 23 | } 24 | 25 | fn update(app: &mut App) { 26 | if app.keyboard.was_pressed(KeyCode::N) { 27 | app.open_link("https://github.com/Nazariglez/notan"); 28 | } else if app.keyboard.was_pressed(KeyCode::R) { 29 | app.open_link("https://www.rust-lang.org"); 30 | } 31 | } 32 | 33 | fn draw(gfx: &mut Graphics, state: &mut State) { 34 | let mut draw = gfx.create_draw(); 35 | draw.clear(Color::BLACK); 36 | 37 | draw.text(&state.font, "Press 'n' to open Notan's website") 38 | .position(20.0, 20.0) 39 | .size(40.0) 40 | .color(Color::WHITE); 41 | 42 | draw.text(&state.font, "Press 'r' to open rust-lang.org") 43 | .position(20.0, 120.0) 44 | .size(40.0) 45 | .color(Color::WHITE); 46 | 47 | gfx.render(&draw); 48 | } 49 | -------------------------------------------------------------------------------- /examples/draw_pattern.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[derive(AppState)] 5 | struct State { 6 | img: Texture, 7 | count: f32, 8 | multi: f32, 9 | } 10 | 11 | impl State { 12 | pub fn count(&mut self, value: f32) { 13 | if self.count >= 200.0 || self.count <= 0.0 { 14 | self.multi *= -1.0; 15 | } 16 | 17 | self.count += value * self.multi; 18 | } 19 | } 20 | 21 | #[notan_main] 22 | fn main() -> Result<(), String> { 23 | notan::init_with(init) 24 | .add_config(DrawConfig) 25 | .update(|app: &mut App, state: &mut State| state.count(60.0 * app.timer.delta_f32())) 26 | .draw(draw) 27 | .build() 28 | } 29 | 30 | fn init(gfx: &mut Graphics) -> State { 31 | let texture = gfx 32 | .create_texture() 33 | .from_image(include_bytes!("assets/pattern.png")) 34 | .build() 35 | .unwrap(); 36 | 37 | State { 38 | img: texture, 39 | count: 1.0, 40 | multi: 1.0, 41 | } 42 | } 43 | 44 | fn draw(gfx: &mut Graphics, state: &mut State) { 45 | let mut draw = gfx.create_draw(); 46 | draw.clear(Color::BLACK); 47 | 48 | draw.pattern(&state.img) 49 | .size(800.0, 600.0) 50 | .image_offset(-state.count, -state.count); 51 | 52 | gfx.render(&draw); 53 | } 54 | -------------------------------------------------------------------------------- /crates/notan_glyph/src/cache.rs: -------------------------------------------------------------------------------- 1 | use notan_app::{Texture, TextureFilter, TextureFormat}; 2 | use notan_graphics::Device; 3 | 4 | pub struct Cache { 5 | texture: Texture, 6 | } 7 | 8 | impl Cache { 9 | pub fn new( 10 | device: &mut Device, 11 | texture_width: u32, 12 | texture_height: u32, 13 | ) -> Result { 14 | let texture = device 15 | .create_texture() 16 | .with_size(texture_width as _, texture_height as _) 17 | .with_filter(TextureFilter::Linear, TextureFilter::Linear) 18 | .with_format(TextureFormat::R8) 19 | .build()?; 20 | 21 | Ok(Self { texture }) 22 | } 23 | 24 | pub fn update( 25 | &mut self, 26 | device: &mut Device, 27 | offset: [u16; 2], 28 | size: [u16; 2], 29 | data: &[u8], 30 | ) -> Result<(), String> { 31 | let [ox, oy] = offset; 32 | let [w, h] = size; 33 | 34 | device 35 | .update_texture(&mut self.texture) 36 | .with_x_offset(ox as _) 37 | .with_y_offset(oy as _) 38 | .with_width(w as _) 39 | .with_height(h as _) 40 | .with_data(data) 41 | .update() 42 | } 43 | 44 | pub fn texture(&self) -> &Texture { 45 | &self.texture 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /xtask/src/cli_examples.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::PathBuf; 3 | 4 | use crate::cli::{Examples, TargetType}; 5 | use crate::DynError; 6 | 7 | impl Examples { 8 | pub(crate) fn run(self) -> Result<(), DynError> { 9 | match self.target { 10 | TargetType::Msvc => self.run_msvc()?, 11 | TargetType::Web => self.run_web()?, 12 | } 13 | 14 | Ok(()) 15 | } 16 | 17 | pub(crate) fn list_files(self, path: &str) -> Result, DynError> { 18 | let entries = fs::read_dir(path)?; 19 | let dir_entries: Vec<_> = entries 20 | .filter_map(Result::ok) 21 | .filter_map(|entry| { 22 | let metadata = entry.metadata().ok()?; 23 | let is_file = metadata.is_file(); 24 | let is_hidden = entry 25 | .file_name() 26 | .to_str() 27 | .map(|name| name.starts_with('.')) 28 | .unwrap_or_default(); 29 | let is_valid = is_file && !is_hidden; 30 | if is_valid { 31 | Some(entry) 32 | } else { 33 | None 34 | } 35 | }) 36 | .map(|entry| entry.path()) 37 | .collect(); 38 | 39 | Ok(dir_entries.into_iter()) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/window_fullscreen.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[derive(AppState)] 5 | struct State { 6 | font: Font, 7 | } 8 | 9 | #[notan_main] 10 | fn main() -> Result<(), String> { 11 | notan::init_with(setup) 12 | .add_config(DrawConfig) 13 | .update(update) 14 | .draw(draw) 15 | .build() 16 | } 17 | 18 | fn setup(gfx: &mut Graphics) -> State { 19 | let font = gfx 20 | .create_font(include_bytes!("assets/Ubuntu-B.ttf")) 21 | .unwrap(); 22 | State { font } 23 | } 24 | 25 | fn update(app: &mut App) { 26 | if app.keyboard.was_pressed(KeyCode::Space) { 27 | let full = !app.window().is_fullscreen(); 28 | app.window().set_fullscreen(full); 29 | } 30 | } 31 | 32 | fn draw(app: &mut App, gfx: &mut Graphics, state: &mut State) { 33 | let window = app.window(); 34 | let ww = window.width() as f32; 35 | let hh = window.height() as f32; 36 | 37 | let text = if window.is_fullscreen() { 38 | "Press Space to disable the fullscreen mode" 39 | } else { 40 | "Press Space to enable the fullscreen mode" 41 | }; 42 | 43 | let mut draw = gfx.create_draw(); 44 | draw.clear(Color::BLACK); 45 | draw.text(&state.font, text) 46 | .position(ww * 0.5, hh * 0.5) 47 | .size(40.0) 48 | .h_align_center() 49 | .v_align_middle(); 50 | 51 | gfx.render(&draw); 52 | } 53 | -------------------------------------------------------------------------------- /examples/input_touches.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | use std::ops::Rem; 4 | 5 | const COLORS: [Color; 8] = [ 6 | Color::RED, 7 | Color::ORANGE, 8 | Color::OLIVE, 9 | Color::BLUE, 10 | Color::GREEN, 11 | Color::SILVER, 12 | Color::PURPLE, 13 | Color::PINK, 14 | ]; 15 | 16 | #[derive(AppState)] 17 | struct State { 18 | font: Font, 19 | } 20 | 21 | #[notan_main] 22 | fn main() -> Result<(), String> { 23 | notan::init_with(setup) 24 | .add_config(DrawConfig) 25 | .draw(draw) 26 | .build() 27 | } 28 | 29 | fn setup(gfx: &mut Graphics) -> State { 30 | let font = gfx 31 | .create_font(include_bytes!("assets/Ubuntu-B.ttf")) 32 | .unwrap(); 33 | 34 | State { font } 35 | } 36 | 37 | fn draw(app: &mut App, gfx: &mut Graphics, state: &mut State) { 38 | let mut draw = gfx.create_draw(); 39 | draw.clear(Color::BLACK); 40 | 41 | app.touch.down.iter().for_each(|(&index, _)| { 42 | if let Some((x, y)) = app.touch.position(index) { 43 | draw.circle(12.0) 44 | .position(x, y) 45 | .color(COLORS[(index as usize).rem(COLORS.len())]); 46 | 47 | draw.text(&state.font, &format!("{index} -> {x:.0}x{y:.0}")) 48 | .position(x, y - 16.0) 49 | .h_align_center() 50 | .v_align_bottom(); 51 | } 52 | }); 53 | 54 | gfx.render(&draw); 55 | } 56 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: CI 4 | 5 | jobs: 6 | fmt-clippy-check-test: 7 | name: Format + clippy + check + test 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout sources 11 | uses: actions/checkout@v2 12 | - name: Update apt 13 | run: sudo apt update 14 | - name: Install alsa 15 | run: sudo apt-get install libasound2-dev 16 | 17 | - name: Install rust stable toolchain 18 | uses: actions-rs/toolchain@v1 19 | with: 20 | profile: default 21 | toolchain: stable 22 | override: true 23 | components: rustfmt, clippy 24 | - name: Set up cargo cache 25 | uses: Swatinem/rust-cache@v2 26 | 27 | - name: Run cargo fmt 28 | uses: actions-rs/cargo@v1 29 | with: 30 | command: fmt 31 | args: --all -- --check 32 | 33 | - name: Run cargo clippy 34 | uses: actions-rs/cargo@v1 35 | with: 36 | command: clippy 37 | args: -- -D warnings 38 | 39 | - name: Run cargo check 40 | uses: actions-rs/cargo@v1 41 | with: 42 | command: check 43 | args: --locked 44 | 45 | - name: Test 46 | uses: actions-rs/cargo@v1 47 | with: 48 | command: test 49 | args: --workspace --exclude "notan_web" --features=glyph,egui,text,extra,audio,links,drop_files,clipboard,save_file,texture_to_file -------------------------------------------------------------------------------- /examples/input_mouse_wheel.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[derive(AppState)] 5 | struct State { 6 | font: Font, 7 | x: f32, 8 | y: f32, 9 | } 10 | 11 | #[notan_main] 12 | fn main() -> Result<(), String> { 13 | notan::init_with(setup) 14 | .add_config(DrawConfig) 15 | .update(update) 16 | .draw(draw) 17 | .build() 18 | } 19 | 20 | fn setup(gfx: &mut Graphics) -> State { 21 | let font = gfx 22 | .create_font(include_bytes!("assets/Ubuntu-B.ttf")) 23 | .unwrap(); 24 | 25 | State { 26 | font, 27 | x: 400.0, 28 | y: 300.0, 29 | } 30 | } 31 | 32 | fn update(app: &mut App, state: &mut State) { 33 | if app.mouse.is_scrolling() { 34 | let delta_x = app.mouse.wheel_delta.x; 35 | let delta_y = app.mouse.wheel_delta.y; 36 | 37 | state.x = (state.x + delta_x).max(0.0).min(800.0); 38 | state.y = (state.y + delta_y).max(0.0).min(600.0); 39 | } 40 | } 41 | 42 | fn draw(gfx: &mut Graphics, state: &mut State) { 43 | let mut draw = gfx.create_draw(); 44 | draw.clear(Color::BLACK); 45 | 46 | draw.text(&state.font, "Scroll with your mouse's wheel or touchpad") 47 | .position(400.0, 300.0) 48 | .size(40.0) 49 | .h_align_center() 50 | .v_align_middle(); 51 | 52 | draw.circle(30.0) 53 | .position(state.x, state.y) 54 | .color(Color::RED); 55 | 56 | gfx.render(&draw); 57 | } 58 | -------------------------------------------------------------------------------- /examples/draw_mask_texture.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[derive(AppState)] 5 | struct State { 6 | img: Texture, 7 | count: f32, 8 | } 9 | 10 | #[notan_main] 11 | fn main() -> Result<(), String> { 12 | notan::init_with(setup) 13 | .add_config(DrawConfig) 14 | .update(|app: &mut App, state: &mut State| state.count += 60.0 * app.timer.delta_f32()) 15 | .draw(draw) 16 | .build() 17 | } 18 | 19 | fn setup(gfx: &mut Graphics) -> State { 20 | let texture = gfx 21 | .create_texture() 22 | .from_image(include_bytes!("assets/pattern.png")) 23 | .build() 24 | .unwrap(); 25 | State { 26 | img: texture, 27 | count: 1.0, 28 | } 29 | } 30 | 31 | fn draw(gfx: &mut Graphics, state: &mut State) { 32 | // Draw some geometry as mask 33 | let mut mask = gfx.create_draw(); 34 | mask.triangle((400.0, 100.0), (100.0, 500.0), (700.0, 500.0)) 35 | .stroke(20.0); 36 | mask.circle(30.0).position(400.0, 350.0); 37 | mask.circle(50.0).position(400.0, 350.0).stroke(10.0); 38 | mask.circle(70.0).position(400.0, 350.0).stroke(10.0); 39 | 40 | let mut draw = gfx.create_draw(); 41 | draw.clear(Color::new(0.1, 0.2, 0.3, 1.0)); 42 | 43 | // Draw a pattern with the mask 44 | draw.mask(Some(&mask)); 45 | draw.pattern(&state.img) 46 | .size(800.0, 600.0) 47 | .image_offset(-state.count, -state.count); 48 | draw.mask(None); 49 | 50 | gfx.render(&draw); 51 | } 52 | -------------------------------------------------------------------------------- /crates/notan_app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_app" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Provides the core API for Notan" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | notan_core.workspace = true 16 | notan_input.workspace = true 17 | notan_macro.workspace = true 18 | notan_graphics.workspace = true 19 | notan_utils.workspace = true 20 | notan_audio = { workspace = true, optional = true } 21 | 22 | log.workspace = true 23 | hashbrown.workspace = true 24 | parking_lot.workspace = true 25 | serde = { workspace = true, optional = true } 26 | 27 | downcast-rs = "2.0.1" 28 | indexmap = "2.7.1" 29 | futures = "0.3.31" 30 | 31 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 32 | platter2 = "0.1.6" 33 | 34 | [target.'cfg(target_arch = "wasm32")'.dependencies] 35 | platter2 = { version = "0.1.6", features = ["web-sys"] } 36 | web-sys = { workspace = true, optional = true } 37 | wasm-bindgen-futures = { workspace = true, optional = true } 38 | futures-util = { workspace = true, optional = true } 39 | js-sys = { workspace = true, optional = true } 40 | 41 | [features] 42 | audio = ["notan_audio"] 43 | links = ["notan_core/links"] 44 | drop_files = ["notan_core/drop_files", "wasm-bindgen-futures", "js-sys", "futures-util", "web-sys", "web-sys/File", "web-sys/Blob"] 45 | clipboard = ["notan_core/clipboard"] 46 | -------------------------------------------------------------------------------- /examples/input_keyboard_char.rs: -------------------------------------------------------------------------------- 1 | use notan::app::Event; 2 | use notan::draw::*; 3 | use notan::prelude::*; 4 | 5 | #[derive(AppState)] 6 | struct State { 7 | font: Font, 8 | msg: String, 9 | } 10 | 11 | #[notan_main] 12 | fn main() -> Result<(), String> { 13 | notan::init_with(setup) 14 | .add_config(DrawConfig) 15 | .event(event) 16 | .update(update) 17 | .draw(draw) 18 | .build() 19 | } 20 | 21 | fn setup(gfx: &mut Graphics) -> State { 22 | let font = gfx 23 | .create_font(include_bytes!("assets/Ubuntu-B.ttf")) 24 | .unwrap(); 25 | 26 | State { 27 | font, 28 | msg: String::from(""), 29 | } 30 | } 31 | 32 | fn event(state: &mut State, event: Event) { 33 | match event { 34 | Event::ReceivedCharacter(c) if c != '\u{7f}' => { 35 | state.msg.push(c); 36 | } 37 | _ => {} 38 | } 39 | } 40 | 41 | fn update(app: &mut App, state: &mut State) { 42 | if app.keyboard.was_pressed(KeyCode::Back) && !state.msg.is_empty() { 43 | state.msg.pop(); 44 | } 45 | } 46 | 47 | fn draw(gfx: &mut Graphics, state: &mut State) { 48 | let mut draw = gfx.create_draw(); 49 | draw.clear(Color::BLACK); 50 | 51 | draw.text(&state.font, "Type anything:") 52 | .position(10.0, 10.0) 53 | .color(Color::YELLOW) 54 | .size(20.0); 55 | 56 | draw.text(&state.font, &state.msg) 57 | .position(20.0, 50.0) 58 | .max_width(760.0) 59 | .color(Color::WHITE) 60 | .size(20.0); 61 | 62 | gfx.render(&draw); 63 | } 64 | -------------------------------------------------------------------------------- /xtask/src/cli_example_msvc.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::PathBuf; 3 | 4 | use crate::cli::{Example, TargetType}; 5 | use crate::{cargo_build, copy_assets, project_root, DynError}; 6 | 7 | impl Example { 8 | pub(crate) fn run_msvc(self) -> Result<(), DynError> { 9 | let status = match self.release { 10 | true => cargo_build(TargetType::Msvc, "release", self.name.as_str())?, 11 | false => cargo_build(TargetType::Msvc, "dev", self.name.as_str())?, 12 | }; 13 | 14 | if !status.success() { 15 | Err("Command 'cargo build' failed")?; 16 | } 17 | 18 | let name_str = self.name.as_str(); 19 | let executable = format!("{name_str}.exe"); 20 | 21 | fs::create_dir_all(docs_msvc_dir(self.release))?; 22 | let _ = fs::copy( 23 | dist_msvc_dir(self.release).join(executable.as_str()), 24 | docs_msvc_dir(self.release).join(executable.as_str()), 25 | ); 26 | 27 | if !self.no_assets { 28 | copy_assets(docs_msvc_dir(self.release)); 29 | } 30 | 31 | Ok(()) 32 | } 33 | } 34 | 35 | pub fn docs_msvc_dir(release: bool) -> PathBuf { 36 | project_root() 37 | .join("docs/msvc_examples/") 38 | .join(match release { 39 | true => "release", 40 | false => "debug", 41 | }) 42 | } 43 | 44 | fn dist_msvc_dir(release: bool) -> PathBuf { 45 | project_root() 46 | .join("target/x86_64-pc-windows-msvc") 47 | .join(match release { 48 | true => "release", 49 | false => "debug", 50 | }) 51 | .join("examples") 52 | } 53 | -------------------------------------------------------------------------------- /examples/draw_shapes.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[notan_main] 5 | fn main() -> Result<(), String> { 6 | notan::init().add_config(DrawConfig).draw(draw).build() 7 | } 8 | 9 | fn draw(gfx: &mut Graphics) { 10 | let mut draw = gfx.create_draw(); 11 | draw.clear(Color::BLACK); 12 | 13 | draw.line((20.0, 30.0), (780.0, 30.0)).width(4.0); 14 | 15 | draw.triangle((100.0, 100.0), (150.0, 200.0), (200.0, 100.0)) 16 | .color(Color::YELLOW); 17 | 18 | draw.rect((500.0, 100.0), (200.0, 150.0)) 19 | .fill_color(Color::GREEN) 20 | .fill() 21 | .stroke_color(Color::WHITE) 22 | .stroke(15.0); 23 | 24 | draw.ellipse((400.0, 300.0), (50.0, 100.0)) 25 | .color(Color::RED) 26 | .rotate_degrees(-45.0); 27 | 28 | draw.circle(40.0) 29 | .position(600.0, 450.0) 30 | .fill_color(Color::BLUE) 31 | .fill() 32 | .stroke_color(Color::WHITE) 33 | .stroke(5.0); 34 | 35 | draw.rect((100.0, 250.0), (150.0, 100.0)) 36 | .corner_radius(20.0) 37 | .color(Color::ORANGE) 38 | .stroke(15.0); 39 | 40 | draw.star(10, 80.0, 40.0) 41 | .position(150.0, 480.0) 42 | .fill_color(Color::PINK) 43 | .fill() 44 | .stroke_color(Color::PURPLE) 45 | .stroke(6.0); 46 | 47 | draw.polygon(5, 50.0) 48 | .position(350.0, 150.0) 49 | .color(Color::WHITE) 50 | .stroke(8.0); 51 | 52 | draw.polygon(8, 80.0) 53 | .position(350.0, 450.0) 54 | .fill_color(Color::WHITE) 55 | .fill() 56 | .stroke_color(Color::ORANGE) 57 | .stroke(8.0); 58 | 59 | gfx.render(&draw); 60 | } 61 | -------------------------------------------------------------------------------- /examples/input_mouse_local_position.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::math::Vec2; 3 | use notan::prelude::*; 4 | 5 | #[derive(Default, AppState)] 6 | struct State { 7 | rot: f32, 8 | } 9 | 10 | #[notan_main] 11 | fn main() -> Result<(), String> { 12 | notan::init_with(State::default) 13 | .add_config(DrawConfig) 14 | .draw(draw) 15 | .build() 16 | } 17 | 18 | fn draw(app: &mut App, gfx: &mut Graphics, state: &mut State) { 19 | let mut draw = gfx.create_draw(); 20 | draw.clear(Color::BLACK); 21 | 22 | { 23 | // rectangle's size 24 | let size = (400.0, 300.0); 25 | 26 | // get the draw builder 27 | let mut rect = draw.rect((0.0, 0.0), size); 28 | 29 | // set the rectangle's transformation 30 | rect.rotate_from((size.0 * 0.5, size.1 * 0.5), state.rot) 31 | .translate(200.0, 150.0); 32 | 33 | // get the local position based on the current projection + matrix 34 | let local = rect.screen_to_local_position(app.mouse.x, app.mouse.y); 35 | 36 | // if it's in bound set color red otherwise white 37 | let color = rect_color(local, size); 38 | rect.color(color); 39 | } 40 | 41 | // rotate the rectangle 42 | state.rot += app.timer.delta_f32(); 43 | 44 | gfx.render(&draw); 45 | } 46 | 47 | // Set the color to red if the mouse is inside the bounds of the matrix 48 | fn rect_color(local: Vec2, size: (f32, f32)) -> Color { 49 | let (width, height) = size; 50 | 51 | let in_bounds = local.x >= 0.0 && local.x <= width && local.y >= 0.0 && local.y <= height; 52 | if in_bounds { 53 | Color::RED 54 | } else { 55 | Color::WHITE 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /crates/notan_draw/src/shapes/geometry.rs: -------------------------------------------------------------------------------- 1 | use lyon::math::{point, vector, Angle, Box2D}; 2 | use lyon::path::builder::BorderRadii; 3 | use lyon::path::Path; 4 | use lyon::path::Winding; 5 | 6 | pub(super) fn rectangle(x: f32, y: f32, width: f32, height: f32) -> Path { 7 | let mut builder = Path::builder(); 8 | builder.add_rectangle( 9 | &Box2D { 10 | min: point(x, y), 11 | max: point(x + width, y + height), 12 | }, 13 | Winding::Positive, 14 | ); 15 | builder.build() 16 | } 17 | 18 | pub(super) fn circle(x: f32, y: f32, radius: f32) -> Path { 19 | let mut builder = Path::builder(); 20 | builder.add_circle(point(x, y), radius, Winding::Positive); 21 | builder.build() 22 | } 23 | 24 | pub(super) fn rounded_rect( 25 | x: f32, 26 | y: f32, 27 | width: f32, 28 | height: f32, 29 | corner: (f32, f32, f32, f32), 30 | ) -> Path { 31 | let (tl, tr, bl, br) = corner; 32 | 33 | let mut builder = Path::builder(); 34 | builder.add_rounded_rectangle( 35 | &Box2D { 36 | min: point(x, y), 37 | max: point(x + width, y + height), 38 | }, 39 | &BorderRadii { 40 | top_left: tl, 41 | top_right: tr, 42 | bottom_left: bl, 43 | bottom_right: br, 44 | }, 45 | Winding::Positive, 46 | ); 47 | builder.build() 48 | } 49 | 50 | pub fn ellipse(x: f32, y: f32, width: f32, height: f32, rotation: f32) -> Path { 51 | let mut builder = Path::builder(); 52 | builder.add_ellipse( 53 | point(x, y), 54 | vector(width, height), 55 | Angle::radians(rotation), 56 | Winding::Positive, 57 | ); 58 | builder.build() 59 | } 60 | -------------------------------------------------------------------------------- /xtask/res/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Notan - {{ EXAMPLE }} 4 | 5 | 6 | 7 | 8 | 9 | 41 | 42 | 43 | 44 | 49 |
50 |

51 | {{ EXAMPLE }} 52 |

53 | Source Code 54 |
55 |
56 | 57 |
58 | 59 | 60 | -------------------------------------------------------------------------------- /examples/assets_load_texture.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[derive(AppState)] 5 | struct State { 6 | font: Font, 7 | texture: Asset, 8 | } 9 | 10 | impl State { 11 | fn new(assets: &mut Assets, gfx: &mut Graphics) -> Self { 12 | // Start loading the texture 13 | let texture = assets 14 | .load_asset(&asset_path("rust-logo-512x512.png")) 15 | .unwrap(); 16 | 17 | // Load a font only for debug info 18 | let font = gfx 19 | .create_font(include_bytes!("assets/Ubuntu-B.ttf")) 20 | .unwrap(); 21 | 22 | Self { font, texture } 23 | } 24 | } 25 | 26 | #[notan_main] 27 | fn main() -> Result<(), String> { 28 | notan::init_with(State::new) 29 | .add_config(DrawConfig) // Simple way to add the draw extension 30 | .draw(draw) 31 | .build() 32 | } 33 | 34 | fn draw(gfx: &mut Graphics, state: &mut State) { 35 | let mut draw = gfx.create_draw(); 36 | draw.clear(Color::BLACK); 37 | 38 | // Draw the image if the lock returns something or the "Loading" text otherwise 39 | if state.texture.is_loaded() { 40 | // To access the inner texture we need to lock the reference 41 | draw.image(&state.texture.lock().unwrap()) 42 | .position(150.0, 50.0); 43 | } else { 44 | draw.text(&state.font, "Loading...") 45 | .position(10.0, 10.0) 46 | .size(25.0); 47 | } 48 | 49 | gfx.render(&draw); 50 | } 51 | 52 | // The relative path for the example is different on browsers 53 | fn asset_path(path: &str) -> String { 54 | let base = if cfg!(target_arch = "wasm32") { 55 | "./assets" 56 | } else { 57 | "./examples/assets" 58 | }; 59 | 60 | format!("{base}/{path}") 61 | } 62 | -------------------------------------------------------------------------------- /examples/draw_transform_local.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::math::{Mat3, Vec2}; 3 | use notan::prelude::*; 4 | 5 | #[derive(AppState, Default)] 6 | struct State { 7 | rot: f32, 8 | } 9 | 10 | #[notan_main] 11 | fn main() -> Result<(), String> { 12 | notan::init_with(State::default) 13 | .add_config(DrawConfig) 14 | .draw(draw) 15 | .build() 16 | } 17 | 18 | fn draw(app: &mut App, gfx: &mut Graphics, state: &mut State) { 19 | let mut draw = gfx.create_draw(); 20 | draw.clear(Color::BLACK); 21 | 22 | let n = state.rot * 0.1; 23 | 24 | draw.rect((0.0, 0.0), (100.0, 100.0)) 25 | // Use a helper function to translate the matrix 26 | .translate(110.0 + n.sin() * 100.0, 10.0); 27 | 28 | draw.rect((0.0, 0.0), (100.0, 100.0)) 29 | .color(Color::AQUA) 30 | // Helper to pivot from a point using degrees 31 | .rotate_degrees_from((50.0, 50.0), state.rot) 32 | // Matrix translation 33 | .translate(220.0, 220.0); 34 | 35 | draw.circle(20.0) 36 | .color(Color::ORANGE) 37 | // Helper to scale from a point 38 | .scale_from((0.0, 0.0), (2.0 + n.sin(), 2.0 + n.cos())) 39 | // Matrix translation 40 | .translate(500.0, 320.0); 41 | 42 | // Create a matrix that we can set to the next paint 43 | let translation = Mat3::from_translation(Vec2::new(200.0, 400.0)); 44 | let rotation = Mat3::from_angle(state.rot * 0.5_f32.to_radians()); 45 | let matrix = translation * rotation; 46 | 47 | draw.rect((0.0, 0.0), (100.0, 100.0)) 48 | .color(Color::MAGENTA) 49 | // Set directly a matrix for this object 50 | .transform(matrix); 51 | 52 | // Render the frame 53 | gfx.render(&draw); 54 | 55 | state.rot += app.timer.delta_f32() * 25.0; 56 | } 57 | -------------------------------------------------------------------------------- /crates/notan_draw/src/images.rs: -------------------------------------------------------------------------------- 1 | mod animation; 2 | mod image; 3 | mod instanced; 4 | mod nine_slice; 5 | mod painter; 6 | 7 | //pub use instanced::*; 8 | use crate::builder::DrawBuilder; 9 | use crate::draw::Draw; 10 | pub use animation::*; 11 | pub use image::*; 12 | pub use nine_slice::*; 13 | use notan_graphics::Texture; 14 | pub use painter::create_image_pipeline; 15 | pub(crate) use painter::*; 16 | 17 | pub trait DrawImages { 18 | fn image<'a>(&mut self, texture: &'a Texture) -> DrawBuilder>; 19 | fn nine_slice<'a>(&mut self, texture: &'a Texture) -> DrawBuilder>; 20 | fn animation_grid<'a>( 21 | &mut self, 22 | texture: &'a Texture, 23 | cols: usize, 24 | rows: usize, 25 | ) -> DrawBuilder>; 26 | fn animation_list<'a>(&mut self, list: &'a [&'a Texture]) -> DrawBuilder>; 27 | //fn instanced_image<'a>(&mut self, texture: &'a Texture) -> DrawBuilder>; 28 | } 29 | 30 | impl DrawImages for Draw { 31 | fn image<'a>(&mut self, texture: &'a Texture) -> DrawBuilder> { 32 | DrawBuilder::new(self, Image::new(texture)) 33 | } 34 | 35 | fn nine_slice<'a>(&mut self, texture: &'a Texture) -> DrawBuilder> { 36 | DrawBuilder::new(self, NineSlice::new(texture)) 37 | } 38 | 39 | fn animation_grid<'a>( 40 | &mut self, 41 | texture: &'a Texture, 42 | cols: usize, 43 | rows: usize, 44 | ) -> DrawBuilder> { 45 | DrawBuilder::new(self, ImageAnimation::from_grid(texture, cols, rows)) 46 | } 47 | 48 | fn animation_list<'a>(&mut self, list: &'a [&'a Texture]) -> DrawBuilder> { 49 | DrawBuilder::new(self, ImageAnimation::from_list(list)) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/draw_nine_slice.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[derive(AppState)] 5 | struct State { 6 | img1: Texture, 7 | img2: Texture, 8 | count: f32, 9 | multi: f32, 10 | } 11 | 12 | impl State { 13 | pub fn count(&mut self, value: f32) { 14 | if self.count >= 200.0 || self.count <= 0.0 { 15 | self.multi *= -1.0; 16 | } 17 | 18 | self.count += value * self.multi; 19 | } 20 | } 21 | 22 | #[notan_main] 23 | fn main() -> Result<(), String> { 24 | notan::init_with(init) 25 | .add_config(DrawConfig) 26 | .update(update) 27 | .draw(draw) 28 | .build() 29 | } 30 | 31 | fn init(gfx: &mut Graphics) -> State { 32 | let texture1 = gfx 33 | .create_texture() 34 | .from_image(include_bytes!("assets/green_panel.png")) 35 | .build() 36 | .unwrap(); 37 | 38 | let texture2 = gfx 39 | .create_texture() 40 | .from_image(include_bytes!("assets/grey_button.png")) 41 | .build() 42 | .unwrap(); 43 | State { 44 | img1: texture1, 45 | img2: texture2, 46 | count: 1.0, 47 | multi: 1.0, 48 | } 49 | } 50 | 51 | fn update(app: &mut App, state: &mut State) { 52 | state.count(60.0 * app.timer.delta_f32()); 53 | } 54 | 55 | fn draw(gfx: &mut Graphics, state: &mut State) { 56 | let mut draw = gfx.create_draw(); 57 | draw.clear(Color::BLACK); 58 | 59 | draw.nine_slice(&state.img1).position(50.0, 50.0).size( 60 | state.img1.width() + state.count, 61 | state.img1.height() + state.count, 62 | ); 63 | 64 | draw.nine_slice(&state.img2).position(450.0, 50.0).size( 65 | state.img2.width() + state.count, 66 | state.img2.height() + state.count, 67 | ); 68 | 69 | gfx.render(&draw); 70 | } 71 | -------------------------------------------------------------------------------- /crates/notan_utils/src/save_file.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | #[cfg(not(target_arch = "wasm32"))] 4 | pub fn save_file>(path: P, bytes: &[u8]) -> Result<(), String> { 5 | use std::io::Write; 6 | 7 | let mut f = std::fs::File::create(path).map_err(|e| e.to_string())?; 8 | 9 | f.write_all(bytes).map_err(|e| e.to_string()) 10 | } 11 | 12 | #[cfg(target_arch = "wasm32")] 13 | pub fn save_file>(path: P, bytes: &[u8]) -> Result<(), String> { 14 | use js_sys::{Array, Uint8Array}; 15 | use wasm_bindgen::JsCast; 16 | use web_sys::Url; 17 | 18 | let mime = mime_guess::from_path(&path) 19 | .first_raw() 20 | .unwrap_or("") 21 | .to_string(); 22 | 23 | let u8_buff = Uint8Array::new_with_length(bytes.len() as _); 24 | u8_buff.copy_from(bytes); 25 | 26 | let array = Array::new(); 27 | array.push(&u8_buff.buffer()); 28 | 29 | let blob_property = web_sys::BlobPropertyBag::new(); 30 | blob_property.set_type(&mime); 31 | 32 | let blob = web_sys::Blob::new_with_u8_array_sequence_and_options(&array, &blob_property) 33 | .map_err(|_| "Cannot create a blob from file".to_string())?; 34 | 35 | let url = web_sys::Url::create_object_url_with_blob(&blob) 36 | .map_err(|_| "Cannot create a blob from file".to_string())?; 37 | 38 | let a = web_sys::window() 39 | .unwrap() 40 | .document() 41 | .unwrap() 42 | .create_element("a") 43 | .unwrap() 44 | .dyn_into::() 45 | .unwrap(); 46 | 47 | let p = path.as_ref(); 48 | 49 | a.set_href(&url); 50 | a.set_download(p.file_name().unwrap().to_str().unwrap()); 51 | a.click(); 52 | 53 | if let Err(e) = Url::revoke_object_url(&url) { 54 | log::error!("{:?}", e); 55 | } 56 | 57 | Ok(()) 58 | } 59 | -------------------------------------------------------------------------------- /xtask/res/docs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Notan - Examples 6 | 54 | 55 | 56 |

Notan - Examples

57 |

58 | Github 59 | - 60 | Crates.io 61 |

62 |
63 | {{ BODY }} 64 |
65 | 66 | -------------------------------------------------------------------------------- /examples/draw_transform.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::math::{Mat3, Vec2}; 3 | use notan::prelude::*; 4 | 5 | const COLORS: [Color; 8] = [ 6 | Color::WHITE, 7 | Color::MAGENTA, 8 | Color::ORANGE, 9 | Color::RED, 10 | Color::YELLOW, 11 | Color::AQUA, 12 | Color::MAROON, 13 | Color::PINK, 14 | ]; 15 | 16 | #[derive(AppState, Default)] 17 | struct State { 18 | rot: f32, 19 | } 20 | 21 | #[notan_main] 22 | fn main() -> Result<(), String> { 23 | notan::init_with(State::default) 24 | .add_config(DrawConfig) 25 | .draw(draw) 26 | .build() 27 | } 28 | 29 | fn draw(app: &mut App, gfx: &mut Graphics, state: &mut State) { 30 | let mut draw = gfx.create_draw(); 31 | draw.clear(Color::BLACK); 32 | 33 | // Push to the transformation stack a translation matrix 34 | draw.transform() 35 | .push(Mat3::from_translation(Vec2::new(350.0, 250.0))); 36 | 37 | // Calculate the matrix that we use for each object 38 | let translation = Mat3::from_translation(Vec2::new(30.0, 20.0)); 39 | let rotation = Mat3::from_angle(state.rot.to_radians()); 40 | let matrix = translation * rotation; 41 | 42 | for (i, c) in COLORS.iter().enumerate() { 43 | let n = (i * 7) as f32; 44 | let size = 100.0 - n; 45 | 46 | // Push to the stack the same matrix on each draw 47 | // The new matrices will be multiplied by the latest on the stack 48 | draw.transform().push(matrix); 49 | 50 | // Create a rect 51 | draw.rect((0.0, 0.0), (size, size)) 52 | // Using different color for each rect 53 | .color(*c); 54 | } 55 | 56 | // Reset the transformation stack 57 | draw.transform().clear(); 58 | 59 | // Render the frame 60 | gfx.render(&draw); 61 | 62 | state.rot = (state.rot + app.timer.delta_f32() * 25.0) % 360.0; 63 | } 64 | -------------------------------------------------------------------------------- /crates/notan_web/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notan_web" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | homepage.workspace = true 7 | repository.workspace = true 8 | license.workspace = true 9 | readme = "README.md" 10 | description = "Provides a web/wasm32 backend for Notan" 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | notan_core.workspace = true 16 | notan_app.workspace = true 17 | notan_glow.workspace = true 18 | notan_graphics.workspace = true 19 | notan_audio = { workspace = true, optional = true } 20 | notan_oddio = { workspace = true, optional = true } 21 | 22 | log.workspace = true 23 | wasm-bindgen.workspace = true 24 | js-sys.workspace = true 25 | wasm-bindgen-futures.workspace = true 26 | 27 | console_error_panic_hook = "0.1.7" 28 | 29 | [dependencies.web-sys] 30 | workspace = true 31 | features = [ 32 | "Screen", 33 | "Document", 34 | "Window", 35 | "Element", 36 | "HtmlElement", 37 | "Node", 38 | "DomRect", 39 | "DomRectReadOnly", 40 | "HtmlCanvasElement", 41 | "XmlHttpRequest", 42 | "XmlHttpRequestEventTarget", 43 | "XmlHttpRequestResponseType", 44 | "Event", 45 | "EventListener", 46 | "EventTarget", 47 | "MouseEvent", 48 | "WheelEvent", 49 | "KeyboardEvent", 50 | "PointerEvent", 51 | "CssStyleDeclaration", 52 | 53 | "Clipboard", 54 | "ClipboardEvent" 55 | ] 56 | 57 | [features] 58 | audio = ["notan_app/audio", "notan_audio", "notan_oddio"] 59 | drop_files = ["web-sys/DragEvent", "web-sys/DataTransfer", "web-sys/FileList", "web-sys/File", "web-sys/DataTransferItemList", "web-sys/DataTransferItem"] 60 | clipboard = ["web-sys/Navigator", "web-sys/DataTransfer"] 61 | 62 | [lints.rust] 63 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(web_sys_unstable_apis)'] } -------------------------------------------------------------------------------- /crates/notan_draw/src/extension.rs: -------------------------------------------------------------------------------- 1 | use crate::{Draw, DrawManager}; 2 | use notan_app::graphics::*; 3 | use notan_text::{Text, TextExtension}; 4 | 5 | pub trait CreateDraw { 6 | fn create_draw(&self) -> Draw; 7 | } 8 | 9 | impl CreateDraw for Graphics { 10 | fn create_draw(&self) -> Draw { 11 | let (width, height) = self.device.size(); 12 | Draw::new(width, height) 13 | } 14 | } 15 | 16 | impl CreateDraw for RenderTexture { 17 | fn create_draw(&self) -> Draw { 18 | let (width, height) = self.size(); 19 | Draw::new(width as _, height as _) 20 | } 21 | } 22 | 23 | pub struct DrawExtension { 24 | manager: DrawManager, 25 | } 26 | 27 | impl DrawExtension { 28 | pub fn new(gfx: &mut Graphics) -> Result { 29 | Ok(Self { 30 | manager: DrawManager::new(gfx)?, 31 | }) 32 | } 33 | } 34 | 35 | impl GfxExtension for DrawExtension {} 36 | 37 | impl GfxRenderer for Draw { 38 | fn render( 39 | &self, 40 | device: &mut Device, 41 | extensions: &mut ExtContainer, 42 | target: Option<&RenderTexture>, 43 | ) -> Result<(), String> { 44 | let mut text_ext = extensions.get_mut::().ok_or_else(|| { 45 | "Missing TextExtension. You may need to add 'DrawConfig' to notan.".to_string() 46 | })?; 47 | let mut ext = extensions.get_mut::().ok_or_else(|| { 48 | "Missing DrawExtension. You may need to add 'DrawConfig' to notan.".to_string() 49 | })?; 50 | 51 | let cmds = 52 | ext.manager 53 | .process_draw(self, device, text_ext.glyph_brush_mut(), target.is_some()); 54 | match target { 55 | None => device.render(cmds), 56 | Some(rt) => device.render_to(rt, cmds), 57 | } 58 | 59 | Ok(()) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /crates/notan_draw/src/shapes/line.rs: -------------------------------------------------------------------------------- 1 | use super::path::Path; 2 | use crate::builder::DrawProcess; 3 | use crate::draw::Draw; 4 | use crate::transform::DrawTransform; 5 | use notan_graphics::color::Color; 6 | use notan_math::Mat3; 7 | 8 | pub struct Line { 9 | p1: (f32, f32), 10 | p2: (f32, f32), 11 | color: Color, 12 | stroke_width: f32, 13 | alpha: f32, 14 | matrix: Option, 15 | } 16 | 17 | impl Line { 18 | pub fn new(p1: (f32, f32), p2: (f32, f32)) -> Self { 19 | Self { 20 | p1, 21 | p2, 22 | color: Color::WHITE, 23 | stroke_width: 1.0, 24 | alpha: 1.0, 25 | matrix: None, 26 | } 27 | } 28 | 29 | pub fn color(&mut self, color: Color) -> &mut Self { 30 | self.color = color; 31 | self 32 | } 33 | 34 | pub fn width(&mut self, width: f32) -> &mut Self { 35 | self.stroke_width = width; 36 | self 37 | } 38 | 39 | pub fn alpha(&mut self, alpha: f32) -> &mut Self { 40 | self.alpha = alpha; 41 | self 42 | } 43 | } 44 | 45 | impl DrawTransform for Line { 46 | fn matrix(&mut self) -> &mut Option { 47 | &mut self.matrix 48 | } 49 | } 50 | 51 | impl DrawProcess for Line { 52 | fn draw_process(self, draw: &mut Draw) { 53 | let Self { 54 | p1: (x1, y1), 55 | p2: (x2, y2), 56 | color, 57 | stroke_width, 58 | alpha, 59 | matrix, 60 | } = self; 61 | 62 | let mut path = Path::new(); 63 | path.move_to(x1, y1) 64 | .line_to(x2, y2) 65 | .stroke(stroke_width) 66 | .color(color.with_alpha(color.a * alpha)) 67 | .close(); 68 | 69 | if let Some(m) = matrix { 70 | path.transform(m); 71 | } 72 | 73 | path.draw_process(draw); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /examples/draw_text_bounds.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::math::Rect; 3 | use notan::prelude::*; 4 | 5 | #[derive(AppState)] 6 | struct State { 7 | font: Font, 8 | } 9 | 10 | #[notan_main] 11 | fn main() -> Result<(), String> { 12 | notan::init_with(setup) 13 | .add_config(DrawConfig) 14 | .draw(draw) 15 | .build() 16 | } 17 | 18 | fn setup(gfx: &mut Graphics) -> State { 19 | let font = gfx 20 | .create_font(include_bytes!("assets/Ubuntu-B.ttf")) 21 | .unwrap(); 22 | State { font } 23 | } 24 | 25 | fn draw(gfx: &mut Graphics, state: &mut State) { 26 | let mut draw = gfx.create_draw(); 27 | draw.clear(Color::BLACK); 28 | 29 | draw.text(&state.font, "Let's measure this text...") 30 | .position(400.0, 300.0) 31 | .size(50.0) 32 | .color(Color::ORANGE) 33 | .h_align_center() 34 | .v_align_middle(); 35 | 36 | // get text bounds 37 | let bounds = draw.last_text_bounds(); 38 | 39 | // draw the size 40 | draw_size(&mut draw, &state.font, bounds); 41 | 42 | gfx.render(&draw); 43 | } 44 | 45 | fn draw_size(draw: &mut Draw, font: &Font, bounds: Rect) { 46 | // show height 47 | draw.line( 48 | (bounds.max_x() + 10.0, bounds.y), 49 | (bounds.max_x() + 10.0, bounds.max_y()), 50 | ) 51 | .width(2.0) 52 | .color(Color::WHITE); 53 | 54 | draw.text(font, &format!("{}px", bounds.height)) 55 | .position(bounds.max_x() + 20.0, bounds.center_y()) 56 | .v_align_middle() 57 | .size(20.0); 58 | 59 | // show width 60 | draw.line( 61 | (bounds.x, bounds.max_y() + 10.0), 62 | (bounds.max_x(), bounds.max_y() + 10.0), 63 | ) 64 | .width(2.0) 65 | .color(Color::WHITE); 66 | 67 | draw.text(font, &format!("{:.2}px", bounds.width)) 68 | .position(bounds.center_x(), bounds.max_y() + 20.0) 69 | .h_align_center() 70 | .size(20.0); 71 | } 72 | -------------------------------------------------------------------------------- /examples/input_keyboard.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | const MOVE_SPEED: f32 = 100.0; 5 | 6 | #[derive(AppState)] 7 | struct State { 8 | font: Font, 9 | x: f32, 10 | y: f32, 11 | last_key: Option, 12 | } 13 | 14 | #[notan_main] 15 | fn main() -> Result<(), String> { 16 | notan::init_with(setup) 17 | .add_config(DrawConfig) 18 | .update(update) 19 | .draw(draw) 20 | .build() 21 | } 22 | 23 | fn setup(gfx: &mut Graphics) -> State { 24 | let font = gfx 25 | .create_font(include_bytes!("assets/Ubuntu-B.ttf")) 26 | .unwrap(); 27 | 28 | State { 29 | font, 30 | x: 400.0, 31 | y: 300.0, 32 | last_key: None, 33 | } 34 | } 35 | 36 | fn update(app: &mut App, state: &mut State) { 37 | state.last_key = app.keyboard.last_key_released(); 38 | 39 | if app.keyboard.is_down(KeyCode::W) { 40 | state.y -= MOVE_SPEED * app.timer.delta_f32(); 41 | } 42 | 43 | if app.keyboard.is_down(KeyCode::A) { 44 | state.x -= MOVE_SPEED * app.timer.delta_f32(); 45 | } 46 | 47 | if app.keyboard.is_down(KeyCode::S) { 48 | state.y += MOVE_SPEED * app.timer.delta_f32(); 49 | } 50 | 51 | if app.keyboard.is_down(KeyCode::D) { 52 | state.x += MOVE_SPEED * app.timer.delta_f32(); 53 | } 54 | } 55 | 56 | fn draw(gfx: &mut Graphics, state: &mut State) { 57 | let mut draw = gfx.create_draw(); 58 | draw.clear(Color::BLACK); 59 | 60 | draw.circle(50.0) 61 | .position(state.x, state.y) 62 | .color(Color::RED); 63 | 64 | draw.text(&state.font, "Use WASD to move the circle") 65 | .position(10.0, 10.0) 66 | .size(20.0); 67 | 68 | if let Some(key) = &state.last_key { 69 | draw.text(&state.font, &format!("Last key: {key:?}")) 70 | .position(10.0, 560.0) 71 | .size(20.0); 72 | } 73 | 74 | gfx.render(&draw); 75 | } 76 | -------------------------------------------------------------------------------- /examples/input_mouse_events.rs: -------------------------------------------------------------------------------- 1 | use notan::app::Event; 2 | use notan::draw::*; 3 | use notan::prelude::*; 4 | 5 | #[derive(AppState)] 6 | struct State { 7 | font: Font, 8 | text: String, 9 | color: Color, 10 | } 11 | 12 | #[notan_main] 13 | fn main() -> Result<(), String> { 14 | notan::init_with(setup) 15 | .add_config(DrawConfig) 16 | .event(event) 17 | .draw(draw) 18 | .build() 19 | } 20 | 21 | fn setup(gfx: &mut Graphics) -> State { 22 | let font = gfx 23 | .create_font(include_bytes!("assets/Ubuntu-B.ttf")) 24 | .unwrap(); 25 | 26 | State { 27 | font, 28 | color: Color::BLACK, 29 | text: String::from(""), 30 | } 31 | } 32 | 33 | fn event(state: &mut State, evt: Event) { 34 | match evt { 35 | Event::MouseMove { .. } => { 36 | state.text = "Moving...".to_string(); 37 | } 38 | Event::MouseDown { button, .. } => { 39 | state.text = format!("{button:?} pressed..."); 40 | } 41 | Event::MouseUp { button, .. } => { 42 | state.text = format!("{button:?} released..."); 43 | } 44 | Event::MouseEnter { .. } => { 45 | state.text = "Entered...".to_string(); 46 | state.color = Color::BLACK; 47 | } 48 | Event::MouseLeft { .. } => { 49 | state.text = "Outside...".to_string(); 50 | state.color = Color::ORANGE; 51 | } 52 | Event::MouseWheel { .. } => { 53 | state.text = "Using Wheel...".to_string(); 54 | } 55 | _ => {} 56 | } 57 | } 58 | 59 | fn draw(gfx: &mut Graphics, state: &mut State) { 60 | let mut draw = gfx.create_draw(); 61 | draw.clear(state.color); 62 | 63 | draw.text(&state.font, &state.text) 64 | .position(400.0, 300.0) 65 | .size(80.0) 66 | .h_align_center() 67 | .v_align_middle(); 68 | 69 | gfx.render(&draw); 70 | } 71 | -------------------------------------------------------------------------------- /examples/draw_animation_list.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[derive(AppState)] 5 | struct State { 6 | textures: Vec, 7 | time: f32, 8 | } 9 | 10 | impl State { 11 | fn new(gfx: &mut Graphics) -> Self { 12 | let texture_data = vec![ 13 | include_bytes!("assets/pixelExplosion00.png").to_vec(), 14 | include_bytes!("assets/pixelExplosion01.png").to_vec(), 15 | include_bytes!("assets/pixelExplosion02.png").to_vec(), 16 | include_bytes!("assets/pixelExplosion03.png").to_vec(), 17 | include_bytes!("assets/pixelExplosion04.png").to_vec(), 18 | include_bytes!("assets/pixelExplosion05.png").to_vec(), 19 | include_bytes!("assets/pixelExplosion06.png").to_vec(), 20 | include_bytes!("assets/pixelExplosion07.png").to_vec(), 21 | include_bytes!("assets/pixelExplosion08.png").to_vec(), 22 | ]; 23 | 24 | let textures = texture_data 25 | .iter() 26 | .map(|d| gfx.create_texture().from_image(d).build().unwrap()) 27 | .collect(); 28 | 29 | Self { 30 | textures, 31 | time: 0.0, 32 | } 33 | } 34 | } 35 | 36 | #[notan_main] 37 | fn main() -> Result<(), String> { 38 | notan::init_with(State::new) 39 | .add_config(DrawConfig) 40 | .draw(draw) 41 | .build() 42 | } 43 | 44 | fn draw(app: &mut App, gfx: &mut Graphics, state: &mut State) { 45 | let mut draw = gfx.create_draw(); 46 | draw.clear(Color::BLACK); 47 | 48 | // An animation list take a &[&Texture] as frames 49 | draw.animation_list(&state.textures.iter().collect::>()) 50 | // just a matrix scale 51 | .scale(2.0, 2.0) 52 | // just a matrix translation 53 | .translate(300.0, 200.0) 54 | // normalized frame time 55 | .time(state.time); 56 | 57 | gfx.render(&draw); 58 | state.time += app.timer.delta_f32() * 2.0; 59 | } 60 | -------------------------------------------------------------------------------- /examples/assets_custom_loader.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | // Create a new asset loaded to load .txt files as strings 5 | fn create_text_loader() -> AssetLoader { 6 | AssetLoader::new().use_parser(parse_text).extension("txt") 7 | } 8 | 9 | // This parses the &[u8] from the file to the type that we want, string in this case 10 | fn parse_text(_id: &str, data: Vec) -> Result { 11 | String::from_utf8(data).map_err(|e| e.to_string()) 12 | } 13 | 14 | #[derive(AppState)] 15 | struct State { 16 | font: Font, 17 | text: Asset, 18 | } 19 | 20 | impl State { 21 | fn new(assets: &mut Assets, gfx: &mut Graphics) -> Self { 22 | // Start loading the file 23 | let text = assets.load_asset(&asset_path("lorem.txt")).unwrap(); 24 | 25 | // Load a font only for debug info 26 | let font = gfx 27 | .create_font(include_bytes!("assets/Ubuntu-B.ttf")) 28 | .unwrap(); 29 | 30 | Self { font, text } 31 | } 32 | } 33 | 34 | #[notan_main] 35 | fn main() -> Result<(), String> { 36 | notan::init_with(State::new) 37 | // Add the new loaded here 38 | .add_loader(create_text_loader()) 39 | .add_config(DrawConfig) 40 | .draw(draw) 41 | .build() 42 | } 43 | 44 | fn draw(gfx: &mut Graphics, state: &mut State) { 45 | let mut draw = gfx.create_draw(); 46 | draw.clear(Color::BLACK); 47 | 48 | if state.text.is_loaded() { 49 | draw.text(&state.font, &state.text.lock().unwrap()) 50 | .max_width(750.0) 51 | .position(10.0, 10.0) 52 | .size(25.0); 53 | } else { 54 | draw.text(&state.font, "Loading...") 55 | .position(10.0, 10.0) 56 | .size(25.0); 57 | }; 58 | 59 | gfx.render(&draw); 60 | } 61 | 62 | // The relative path for the example is different on browsers 63 | fn asset_path(path: &str) -> String { 64 | let base = if cfg!(target_arch = "wasm32") { 65 | "./assets" 66 | } else { 67 | "./examples/assets" 68 | }; 69 | 70 | format!("{base}/{path}") 71 | } 72 | -------------------------------------------------------------------------------- /examples/egui_custom_font.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use notan::egui::{self, *}; 4 | use notan::prelude::*; 5 | 6 | #[notan_main] 7 | fn main() -> Result<(), String> { 8 | let win = WindowConfig::new() 9 | .set_vsync(true) 10 | .set_lazy_loop(true) 11 | .set_high_dpi(true); 12 | 13 | notan::init() 14 | .add_config(win) 15 | .add_config(EguiConfig) 16 | .initialize(initialize) 17 | .draw(draw) 18 | .build() 19 | } 20 | 21 | fn draw(gfx: &mut Graphics, plugins: &mut Plugins) { 22 | let mut output = plugins.egui(|ctx| { 23 | egui::CentralPanel::default().show(ctx, |ui| { 24 | ui.heading("egui using custom fonts"); 25 | ui.label("This text is using a custom font"); 26 | }); 27 | }); 28 | 29 | output.clear_color(Color::BLACK); 30 | gfx.render(&output); 31 | } 32 | 33 | // Initialize callback is called just once after setup and before the app's loop 34 | fn initialize(plugins: &mut Plugins) { 35 | plugins.egui(setup); 36 | } 37 | 38 | // from https://github.com/emilk/egui/blob/master/examples/custom_font/src/main.rs#L17-L46 39 | fn setup(ctx: &egui::Context) { 40 | // Start with the default fonts (we will be adding to them rather than replacing them). 41 | let mut fonts = egui::FontDefinitions::default(); 42 | 43 | // Install my own font (maybe supporting non-latin characters). 44 | // .ttf and .otf files supported. 45 | fonts.font_data.insert( 46 | "my_font".to_owned(), 47 | Arc::new(egui::FontData::from_static(include_bytes!( 48 | "./assets/Ubuntu-B.ttf" 49 | ))), 50 | ); 51 | 52 | // Put my font first (highest priority) for proportional text: 53 | fonts 54 | .families 55 | .entry(egui::FontFamily::Proportional) 56 | .or_default() 57 | .insert(0, "my_font".to_owned()); 58 | 59 | // Put my font as last fallback for monospace: 60 | fonts 61 | .families 62 | .entry(egui::FontFamily::Monospace) 63 | .or_default() 64 | .push("my_font".to_owned()); 65 | 66 | // Tell egui to use these fonts: 67 | ctx.set_fonts(fonts); 68 | } 69 | -------------------------------------------------------------------------------- /examples/draw_animation_grid.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[derive(AppState)] 5 | struct State { 6 | texture: Texture, 7 | time: f32, 8 | } 9 | 10 | impl State { 11 | fn new(gfx: &mut Graphics) -> Self { 12 | let texture = gfx 13 | .create_texture() 14 | .from_image(include_bytes!("assets/golem-walk.png")) 15 | .build() 16 | .unwrap(); 17 | Self { texture, time: 0.0 } 18 | } 19 | } 20 | 21 | #[notan_main] 22 | fn main() -> Result<(), String> { 23 | notan::init_with(State::new) 24 | .add_config(DrawConfig) 25 | .draw(draw) 26 | .build() 27 | } 28 | 29 | fn draw(app: &mut App, gfx: &mut Graphics, state: &mut State) { 30 | let mut draw = gfx.create_draw(); 31 | draw.clear(Color::BLACK); 32 | 33 | // Simple animations can be defined passing a texture, 34 | // the number of columns and rows that it has 35 | // and a normalized time to calculate the current frame 36 | let cols = 7; 37 | let rows = 4; 38 | 39 | // frames in the first row of the image 40 | draw.animation_grid(&state.texture, cols, rows) 41 | .position(50.0, 50.0) 42 | // Set he frames we want, if this is not set then all frames will be used 43 | .frames(&[0, 1, 2, 3, 4, 5, 6]) 44 | // Normalized time for the animation 0.0 is the start 1.0 is the last frame 45 | .time(state.time); 46 | 47 | // frame in the 2nd row of the image 48 | draw.animation_grid(&state.texture, cols, rows) 49 | .position(250.0, 180.0) 50 | .frames(&[7, 8, 9, 10, 11, 12, 13]) 51 | .time(state.time); 52 | 53 | // frames in the 3rd row of the image 54 | draw.animation_grid(&state.texture, cols, rows) 55 | .position(450.0, 310.0) 56 | .frames(&[14, 15, 16, 17, 18, 19, 20]) 57 | .time(state.time); 58 | 59 | // frames in the 4th row of the image 60 | draw.animation_grid(&state.texture, cols, rows) 61 | .position(650.0, 440.0) 62 | .frames(&[21, 22, 23, 24, 25, 26, 27]) 63 | .time(state.time); 64 | 65 | gfx.render(&draw); 66 | 67 | state.time += app.timer.delta_f32(); 68 | } 69 | -------------------------------------------------------------------------------- /crates/notan_draw/src/shapes/tess.rs: -------------------------------------------------------------------------------- 1 | use lyon::path::Path; 2 | use lyon::tessellation::*; 3 | use notan_graphics::color::Color; 4 | use std::cell::RefCell; 5 | 6 | thread_local! { 7 | static STROKE_TESSELLATOR:RefCell = RefCell::new(StrokeTessellator::new()); 8 | static FILL_TESSELLATOR:RefCell = RefCell::new(FillTessellator::new()); 9 | } 10 | 11 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 12 | pub(super) enum TessMode { 13 | Fill, 14 | Stroke, 15 | } 16 | 17 | pub(super) fn fill_lyon_path( 18 | path: &Path, 19 | color: Color, 20 | options: &FillOptions, 21 | ) -> (Vec, Vec) { 22 | let mut geometry: VertexBuffers<[f32; 6], u32> = VertexBuffers::new(); 23 | { 24 | FILL_TESSELLATOR.with(|tessellator| { 25 | tessellator 26 | .borrow_mut() 27 | .tessellate_path( 28 | path, 29 | options, 30 | &mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| { 31 | let [x, y] = vertex.position().to_array(); 32 | [x, y, color.r, color.g, color.b, color.a] 33 | }), 34 | ) 35 | .unwrap() 36 | }); 37 | } 38 | 39 | (geometry.vertices.concat(), geometry.indices) 40 | } 41 | 42 | pub(super) fn stroke_lyon_path( 43 | path: &Path, 44 | color: Color, 45 | options: &StrokeOptions, 46 | ) -> (Vec, Vec) { 47 | let mut geometry: VertexBuffers<[f32; 6], u32> = VertexBuffers::new(); 48 | { 49 | STROKE_TESSELLATOR.with(|tessellator| { 50 | tessellator 51 | .borrow_mut() 52 | .tessellate_path( 53 | path, 54 | options, 55 | &mut BuffersBuilder::new(&mut geometry, |vertex: StrokeVertex| { 56 | let [x, y] = vertex.position().to_array(); 57 | [x, y, color.r, color.g, color.b, color.a] 58 | }), 59 | ) 60 | .unwrap() 61 | }); 62 | } 63 | 64 | (geometry.vertices.concat(), geometry.indices) 65 | } 66 | -------------------------------------------------------------------------------- /examples/graphics_update_texture.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | const COLORS: [Color; 6] = [ 5 | Color::WHITE, 6 | Color::RED, 7 | Color::GREEN, 8 | Color::BLUE, 9 | Color::ORANGE, 10 | Color::from_rgb(0.1, 0.2, 0.3), 11 | ]; 12 | 13 | #[derive(AppState)] 14 | struct State { 15 | texture: Texture, 16 | bytes: Vec, 17 | count: usize, 18 | color: Color, 19 | step: usize, 20 | } 21 | 22 | #[notan_main] 23 | fn main() -> Result<(), String> { 24 | notan::init_with(init) 25 | .add_config(DrawConfig) 26 | .draw(draw) 27 | .build() 28 | } 29 | 30 | fn init(app: &mut App, gfx: &mut Graphics) -> State { 31 | let (width, height) = app.window().size(); 32 | let bytes_per_pixel = 4; 33 | 34 | let len = (width * height * bytes_per_pixel) as usize; 35 | let bytes = vec![0; len]; 36 | 37 | let texture = gfx 38 | .create_texture() 39 | .from_bytes(&bytes, width, height) 40 | .build() 41 | .unwrap(); 42 | 43 | State { 44 | texture, 45 | bytes, 46 | count: 0, 47 | color: Color::WHITE, 48 | step: 0, 49 | } 50 | } 51 | 52 | fn draw(gfx: &mut Graphics, state: &mut State) { 53 | // update the data that will be send to the gpu 54 | update_bytes(state); 55 | 56 | // Update the texture with the new data 57 | gfx.update_texture(&mut state.texture) 58 | .with_data(&state.bytes) 59 | .update() 60 | .unwrap(); 61 | 62 | // Draw the texture using the draw 2d API for convenience 63 | let mut draw = gfx.create_draw(); 64 | draw.clear(Color::BLACK); 65 | draw.image(&state.texture); 66 | gfx.render(&draw); 67 | } 68 | 69 | fn update_bytes(state: &mut State) { 70 | for _ in 0..100 { 71 | let index = state.count * 4; 72 | state.bytes[index..index + 4].copy_from_slice(&state.color.rgba_u8()); 73 | state.count += 9; 74 | 75 | let len = state.bytes.len() / 4; 76 | if state.count >= len { 77 | state.count -= len; 78 | state.step = (state.step + 1) % COLORS.len(); 79 | state.color = COLORS[state.step]; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /crates/notan_draw/src/atlas.rs: -------------------------------------------------------------------------------- 1 | use notan_graphics::Texture; 2 | use serde::{Deserialize, Serialize}; 3 | use std::collections::HashMap; 4 | 5 | /// Returns a HashMap containing a list of textures created from the Atlas data 6 | pub fn create_textures_from_atlas( 7 | data: &[u8], 8 | base_texture: &Texture, 9 | ) -> Result, String> { 10 | let data = atlas_from_bytes(data)?; 11 | let mut textures = HashMap::new(); 12 | data.frames.iter().for_each(|af| { 13 | textures.insert( 14 | af.filename.clone(), 15 | base_texture.with_frame( 16 | af.frame.x as _, 17 | af.frame.y as _, 18 | af.frame.w as _, 19 | af.frame.h as _, 20 | ), 21 | ); 22 | }); 23 | Ok(textures) 24 | } 25 | 26 | #[inline] 27 | fn atlas_from_bytes(data: &[u8]) -> Result { 28 | serde_json::from_slice(data).map_err(|e| e.to_string()) 29 | } 30 | 31 | #[derive(Serialize, Deserialize, Debug)] 32 | struct AtlasRoot { 33 | frames: Vec, 34 | meta: AtlasMeta, 35 | } 36 | 37 | #[derive(Serialize, Deserialize, Debug)] 38 | struct AtlasFrame { 39 | filename: String, 40 | frame: AtlasRect, 41 | rotated: bool, 42 | trimmed: bool, 43 | #[serde(alias = "spriteSourceSize")] 44 | sprite_source_size: AtlasRect, 45 | #[serde(alias = "sourceSize")] 46 | source_size: AtlasSize, 47 | #[serde(default)] 48 | pivot: AtlasPoint, 49 | } 50 | 51 | #[derive(Serialize, Deserialize, Debug)] 52 | struct AtlasMeta { 53 | app: String, 54 | version: String, 55 | image: String, 56 | format: String, 57 | size: AtlasSize, 58 | scale: String, 59 | } 60 | 61 | #[derive(Serialize, Deserialize, Debug)] 62 | struct AtlasPoint { 63 | x: f32, 64 | y: f32, 65 | } 66 | 67 | impl Default for AtlasPoint { 68 | fn default() -> Self { 69 | Self { x: 0.5, y: 0.5 } 70 | } 71 | } 72 | 73 | #[derive(Serialize, Deserialize, Debug)] 74 | struct AtlasSize { 75 | w: i32, 76 | h: i32, 77 | } 78 | 79 | #[derive(Serialize, Deserialize, Debug)] 80 | struct AtlasRect { 81 | x: i32, 82 | y: i32, 83 | w: i32, 84 | h: i32, 85 | } 86 | -------------------------------------------------------------------------------- /examples/draw_projection.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::math::{vec2, vec3, Mat4, Vec2}; 3 | use notan::prelude::*; 4 | 5 | // This is the size of our content, no matter the size 6 | // of the window our content will always keep this aspect ratio 7 | const WORK_SIZE: Vec2 = vec2(800.0, 600.0); 8 | 9 | #[notan_main] 10 | fn main() -> Result<(), String> { 11 | let win_config = WindowConfig::default().set_resizable(true); 12 | 13 | notan::init() 14 | .add_config(win_config) 15 | .add_config(DrawConfig) // Simple way to add the draw extension 16 | .draw(draw) 17 | .build() 18 | } 19 | 20 | fn draw(gfx: &mut Graphics) { 21 | let (width, height) = gfx.size(); 22 | let win_size = vec2(width as f32, height as f32); 23 | 24 | // get the projection that will fit and center our content in the screen 25 | let (projection, _) = calc_projection(win_size, WORK_SIZE); 26 | 27 | let mut draw = gfx.create_draw(); 28 | draw.clear(Color::BLACK); 29 | 30 | // We set our projection here 31 | // Anything draw below will keep the aspect ratio 32 | draw.set_projection(Some(projection)); 33 | 34 | // Our resolution bounds 35 | draw.rect((0.0, 0.0), WORK_SIZE.into()) 36 | .color(Color::ORANGE) 37 | .stroke(10.0); 38 | 39 | draw.triangle((400.0, 100.0), (100.0, 500.0), (700.0, 500.0)); 40 | 41 | // draw to screen 42 | gfx.render(&draw); 43 | } 44 | 45 | // This returns a projection that keeps the aspect ratio while scaling 46 | // and fitting the content in our window 47 | // It also returns the ratio in case we need it to calculate positions 48 | // or manually scale something 49 | fn calc_projection(win_size: Vec2, work_size: Vec2) -> (Mat4, f32) { 50 | let ratio = (win_size.x / work_size.x).min(win_size.y / work_size.y); 51 | 52 | let projection = Mat4::orthographic_rh_gl(0.0, win_size.x, win_size.y, 0.0, -1.0, 1.0); 53 | let scale = Mat4::from_scale(vec3(ratio, ratio, 1.0)); 54 | let position = vec3( 55 | (win_size.x - work_size.x * ratio) * 0.5, 56 | (win_size.y - work_size.y * ratio) * 0.5, 57 | 1.0, 58 | ); 59 | let translation = Mat4::from_translation(position); 60 | (projection * translation * scale, ratio) 61 | } 62 | -------------------------------------------------------------------------------- /xtask/src/cli.rs: -------------------------------------------------------------------------------- 1 | #![allow(unreachable_pub)] 2 | 3 | use std::str::FromStr; 4 | 5 | xflags::xflags! { 6 | src "./src/cli.rs" 7 | 8 | cmd cli { 9 | cmd docs {} 10 | cmd example { 11 | required name: String 12 | required target: TargetType 13 | 14 | optional --release 15 | optional --no-assets 16 | optional --gzip 17 | } 18 | 19 | cmd examples { 20 | required target: TargetType 21 | 22 | optional --release 23 | optional --gzip 24 | } 25 | } 26 | } 27 | 28 | // generated start 29 | // The following code is generated by `xflags` macro. 30 | // Run `env UPDATE_XFLAGS=1 cargo build` to regenerate. 31 | #[derive(Debug)] 32 | pub struct Cli { 33 | pub subcommand: CliCmd, 34 | } 35 | 36 | #[derive(Debug)] 37 | pub enum CliCmd { 38 | Docs(Docs), 39 | Example(Example), 40 | Examples(Examples), 41 | } 42 | 43 | #[derive(Debug)] 44 | pub struct Docs; 45 | 46 | #[derive(Debug)] 47 | pub struct Example { 48 | pub name: String, 49 | pub target: TargetType, 50 | 51 | pub release: bool, 52 | pub no_assets: bool, 53 | pub gzip: bool, 54 | } 55 | 56 | #[derive(Debug)] 57 | pub struct Examples { 58 | pub target: TargetType, 59 | 60 | pub release: bool, 61 | pub gzip: bool, 62 | } 63 | 64 | impl Cli { 65 | #[allow(dead_code)] 66 | pub fn from_env_or_exit() -> Self { 67 | Self::from_env_or_exit_() 68 | } 69 | 70 | #[allow(dead_code)] 71 | pub fn from_env() -> xflags::Result { 72 | Self::from_env_() 73 | } 74 | 75 | #[allow(dead_code)] 76 | pub fn from_vec(args: Vec) -> xflags::Result { 77 | Self::from_vec_(args) 78 | } 79 | } 80 | // generated end 81 | 82 | #[derive(Copy, Clone, Debug, Default)] 83 | pub enum TargetType { 84 | #[default] 85 | Msvc, 86 | Web, 87 | } 88 | 89 | impl FromStr for TargetType { 90 | type Err = String; 91 | fn from_str(s: &str) -> Result { 92 | match s { 93 | "msvc" => Ok(Self::Msvc), 94 | "web" => Ok(Self::Web), 95 | _ => Err("Invalid option".to_owned()), 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /examples/draw_text_max_width.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | const LOREM_IPSUM: &str = r#" 5 | Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32. 6 | 7 | The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from "de Finibus Bonorum et Malorum" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham. 8 | "#; 9 | 10 | #[derive(AppState)] 11 | struct State { 12 | font: Font, 13 | } 14 | 15 | #[notan_main] 16 | fn main() -> Result<(), String> { 17 | notan::init_with(setup) 18 | .add_config(DrawConfig) 19 | .draw(draw) 20 | .build() 21 | } 22 | 23 | fn setup(gfx: &mut Graphics) -> State { 24 | let font = gfx 25 | .create_font(include_bytes!("assets/Ubuntu-B.ttf")) 26 | .unwrap(); 27 | State { font } 28 | } 29 | 30 | fn draw(gfx: &mut Graphics, state: &mut State) { 31 | let mut draw = gfx.create_draw(); 32 | draw.clear(Color::BLACK); 33 | 34 | draw.text(&state.font, LOREM_IPSUM) 35 | .position(200.0, 300.0) 36 | .size(18.0) 37 | .color(Color::ORANGE) 38 | .max_width(360.0) 39 | .h_align_center() 40 | .v_align_middle(); 41 | 42 | draw.text(&state.font, LOREM_IPSUM) 43 | .position(600.0, 300.0) 44 | .size(14.0) 45 | .color(Color::MAGENTA) 46 | .max_width(300.0) 47 | .h_align_center() 48 | .v_align_middle(); 49 | 50 | gfx.render(&draw); 51 | } 52 | -------------------------------------------------------------------------------- /crates/notan_web/src/clipboard.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "clipboard")] 2 | #![cfg(web_sys_unstable_apis)] 3 | 4 | use crate::utils::window_add_event_listener; 5 | use crate::window::WebWindowBackend; 6 | use notan_core::events::Event; 7 | use wasm_bindgen::prelude::*; 8 | use web_sys::ClipboardEvent; 9 | 10 | #[derive(Default)] 11 | pub struct ClipboardCallbacks { 12 | on_cut: Option>, 13 | on_copy: Option>, 14 | on_paste: Option>, 15 | } 16 | 17 | pub fn enable_clipboard(win: &mut WebWindowBackend) -> Result<(), String> { 18 | let add_evt_copy = win.add_event_fn(); 19 | let add_evt_cut = win.add_event_fn(); 20 | let add_evt_paste = win.add_event_fn(); 21 | let callbacks = &mut win.clipboard_callbacks; 22 | callbacks.on_copy = Some(window_add_event_listener( 23 | "copy", 24 | move |_: ClipboardEvent| { 25 | add_evt_copy(Event::Copy); 26 | }, 27 | )?); 28 | 29 | callbacks.on_cut = Some(window_add_event_listener( 30 | "cut", 31 | move |_: ClipboardEvent| { 32 | add_evt_cut(Event::Cut); 33 | }, 34 | )?); 35 | 36 | callbacks.on_paste = Some(window_add_event_listener( 37 | "paste", 38 | move |e: ClipboardEvent| { 39 | if let Some(data) = e.clipboard_data() { 40 | if let Ok(text) = data.get_data("text") { 41 | let text = text.replace("\r\n", "\n"); 42 | if !text.is_empty() { 43 | add_evt_paste(Event::Paste(text)); 44 | } 45 | } 46 | } 47 | }, 48 | )?); 49 | 50 | Ok(()) 51 | } 52 | 53 | pub fn set_clipboard_text(text: &str) { 54 | if let Some(window) = web_sys::window() { 55 | let clipboard = window.navigator().clipboard(); 56 | let promise = clipboard.write_text(text); 57 | let future = wasm_bindgen_futures::JsFuture::from(promise); 58 | let future = async move { 59 | if let Err(err) = future.await { 60 | log::error!("failed to set text on clipboard: {:?}", err); 61 | } 62 | }; 63 | wasm_bindgen_futures::spawn_local(future); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/renderer_triangle.rs: -------------------------------------------------------------------------------- 1 | use notan::prelude::*; 2 | 3 | //language=glsl 4 | const VERT: ShaderSource = notan::vertex_shader! { 5 | r#" 6 | #version 450 7 | layout(location = 0) in vec2 a_pos; 8 | layout(location = 1) in vec3 a_color; 9 | 10 | layout(location = 0) out vec3 v_color; 11 | 12 | void main() { 13 | v_color = a_color; 14 | gl_Position = vec4(a_pos - 0.5, 0.0, 1.0); 15 | } 16 | "# 17 | }; 18 | 19 | //language=glsl 20 | const FRAG: ShaderSource = notan::fragment_shader! { 21 | r#" 22 | #version 450 23 | precision mediump float; 24 | 25 | layout(location = 0) in vec3 v_color; 26 | layout(location = 0) out vec4 color; 27 | 28 | void main() { 29 | color = vec4(v_color, 1.0); 30 | } 31 | "# 32 | }; 33 | 34 | #[derive(AppState)] 35 | struct State { 36 | clear_options: ClearOptions, 37 | pipeline: Pipeline, 38 | vbo: Buffer, 39 | } 40 | 41 | #[notan_main] 42 | fn main() -> Result<(), String> { 43 | notan::init_with(setup).draw(draw).build() 44 | } 45 | 46 | fn setup(gfx: &mut Graphics) -> State { 47 | let clear_options = ClearOptions::color(Color::new(0.1, 0.2, 0.3, 1.0)); 48 | 49 | let vertex_info = VertexInfo::new() 50 | .attr(0, VertexFormat::Float32x2) 51 | .attr(1, VertexFormat::Float32x3); 52 | 53 | let pipeline = gfx 54 | .create_pipeline() 55 | .from(&VERT, &FRAG) 56 | .with_vertex_info(&vertex_info) 57 | .build() 58 | .unwrap(); 59 | 60 | #[rustfmt::skip] 61 | let vertices = [ 62 | 0.5, 1.0, 1.0, 0.2, 0.3, 63 | 0.0, 0.0, 0.1, 1.0, 0.3, 64 | 1.0, 0.0, 0.1, 0.2, 1.0, 65 | ]; 66 | 67 | let vbo = gfx 68 | .create_vertex_buffer() 69 | .with_info(&vertex_info) 70 | .with_data(&vertices) 71 | .build() 72 | .unwrap(); 73 | 74 | State { 75 | clear_options, 76 | pipeline, 77 | vbo, 78 | } 79 | } 80 | 81 | fn draw(gfx: &mut Graphics, state: &mut State) { 82 | let mut renderer = gfx.create_renderer(); 83 | 84 | renderer.begin(Some(state.clear_options)); 85 | renderer.set_pipeline(&state.pipeline); 86 | renderer.bind_buffer(&state.vbo); 87 | renderer.draw(0, 3); 88 | renderer.end(); 89 | 90 | gfx.render(&renderer); 91 | } 92 | -------------------------------------------------------------------------------- /xtask/src/cli_examples_web.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{BufRead, BufReader, Write}; 3 | 4 | use crate::cli::{Example, Examples}; 5 | use crate::cli_example_web::docs_web_dir; 6 | use crate::{copy_assets, project_root, DynError}; 7 | 8 | impl Examples { 9 | pub(crate) fn run_web(self) -> Result<(), DynError> { 10 | copy_assets(docs_web_dir("assets")); 11 | 12 | let mut doc_body = String::from("
    \n"); 13 | 14 | let examples_path = project_root() 15 | .join("examples") 16 | .to_string_lossy() 17 | .into_owned(); 18 | 19 | let target = self.target; 20 | let release = self.release; 21 | let gzip = self.gzip; 22 | 23 | self.list_files(examples_path.as_str())? 24 | .for_each(|example| { 25 | let name = example.file_stem().unwrap().to_str().unwrap().to_owned(); 26 | let name_str = name.as_str().to_owned(); 27 | 28 | let example = Example { 29 | name, 30 | target, 31 | release, 32 | no_assets: true, 33 | gzip, 34 | }; 35 | 36 | let _ = example.run(); 37 | 38 | let url = format!("examples/{name_str}.html"); 39 | let image = format!("examples/images/{name_str}.jpg"); 40 | let tmp = format!("\n
  • \"{name_str}\"
    {name_str}
  • "); 41 | 42 | doc_body.push_str(tmp.as_str()); 43 | }); 44 | 45 | doc_body.push_str("\n
"); 46 | 47 | let file_in = File::open(crate::cli_example_web::res_dir().join("docs.html"))?; 48 | let reader = BufReader::new(file_in); 49 | 50 | let mut output_lines = Vec::new(); 51 | for line in reader.lines() { 52 | let mut line = line?; 53 | 54 | line = line.replace("{{ BODY }}", doc_body.as_str()); 55 | output_lines.push(line); 56 | } 57 | 58 | let mut file_out = File::create(docs_web_dir("../").join("index.html"))?; 59 | 60 | for line in &output_lines { 61 | writeln!(file_out, "{}", line)?; 62 | } 63 | 64 | Ok(()) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /crates/notan_draw/src/batch.rs: -------------------------------------------------------------------------------- 1 | use notan_glyph::OwnedSection; 2 | use notan_graphics::prelude::*; 3 | use notan_math::{Mat3, Vec3}; 4 | 5 | #[derive(Clone, Debug)] 6 | pub(crate) struct TextData { 7 | pub transform: Mat3, 8 | pub section: OwnedSection, 9 | pub alpha: f32, 10 | pub count: usize, 11 | pub flip: (bool, bool), 12 | } 13 | 14 | #[derive(Clone, Debug)] 15 | pub(crate) enum BatchType { 16 | Image { texture: Texture }, 17 | Pattern { texture: Texture }, 18 | Shape, 19 | Text { texts: Vec }, 20 | } 21 | 22 | #[derive(Clone, Debug)] 23 | pub(crate) struct Batch { 24 | pub typ: BatchType, 25 | pub vertices: Vec, 26 | pub indices: Vec, 27 | pub pipeline: Option, 28 | pub uniform_buffers: Option>, 29 | pub blend_mode: Option, 30 | pub alpha_mode: Option, 31 | pub is_mask: bool, 32 | pub masking: bool, 33 | } 34 | 35 | impl Batch { 36 | pub fn is_shape(&self) -> bool { 37 | matches!(self.typ, BatchType::Shape) 38 | } 39 | 40 | pub fn is_text(&self) -> bool { 41 | matches!(self.typ, BatchType::Text { .. }) 42 | } 43 | 44 | pub fn add(&mut self, indices: &[u32], vertices: &[f32], matrix: Mat3, alpha: f32) { 45 | let offset = self.offset(); 46 | 47 | //compute indices 48 | let last_index = (self.vertices.len() / offset) as u32; 49 | self.indices.extend(indices.iter().map(|i| i + last_index)); 50 | 51 | //compute vertices 52 | vertices 53 | .iter() 54 | .enumerate() 55 | .step_by(offset) 56 | .for_each(|(i, _)| { 57 | let start = i + 2; 58 | let end = i + offset - 1; 59 | let xyz = matrix * Vec3::new(vertices[i], vertices[i + 1], 1.0); 60 | self.vertices.extend([xyz.x, xyz.y]); //pos 61 | self.vertices.extend(&vertices[start..end]); //pipeline attrs and rgb 62 | self.vertices.push(vertices[i + offset - 1] * alpha); //alpha 63 | }); 64 | } 65 | 66 | fn offset(&self) -> usize { 67 | match &self.typ { 68 | BatchType::Image { .. } => 8, 69 | BatchType::Pattern { .. } => 12, 70 | BatchType::Shape => 6, 71 | BatchType::Text { .. } => 8, 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /crates/notan_glow/src/utils.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_arch = "wasm32")] 2 | use wasm_bindgen::JsCast; 3 | 4 | #[cfg(target_arch = "wasm32")] 5 | pub(crate) fn create_gl_context( 6 | win: &web_sys::HtmlCanvasElement, 7 | antialias: bool, 8 | transparent: bool, 9 | ) -> Result<(glow::Context, String), String> { 10 | if let Ok(ctx) = create_webgl2_context(win, antialias, transparent) { 11 | return Ok((ctx, "webgl2".to_string())); 12 | } 13 | 14 | let ctx = create_webgl_context(win, antialias, transparent)?; 15 | Ok((ctx, "webgl".to_string())) 16 | } 17 | 18 | #[cfg(target_arch = "wasm32")] 19 | fn webgl_options(antialias: bool, transparent: bool) -> web_sys::WebGlContextAttributes { 20 | let opts = web_sys::WebGlContextAttributes::new(); 21 | opts.set_stencil(true); 22 | opts.set_premultiplied_alpha(false); 23 | opts.set_alpha(transparent); 24 | opts.set_antialias(antialias); 25 | opts.set_power_preference(web_sys::WebGlPowerPreference::HighPerformance); 26 | opts.set_fail_if_major_performance_caveat(true); 27 | opts 28 | } 29 | 30 | #[cfg(target_arch = "wasm32")] 31 | fn create_webgl_context( 32 | win: &web_sys::HtmlCanvasElement, 33 | antialias: bool, 34 | transparent: bool, 35 | ) -> Result { 36 | let gl = win 37 | .get_context_with_context_options("webgl", webgl_options(antialias, transparent).as_ref()) 38 | .map_err(|e| format!("{e:?}"))? 39 | .ok_or("Cannot acquire the Webgl context. Is the canvas already instantiated?")? 40 | .dyn_into::() 41 | .map_err(|_| "Cannot acquire WebGL context.")?; 42 | 43 | let ctx = glow::Context::from_webgl1_context(gl); 44 | Ok(ctx) 45 | } 46 | 47 | #[cfg(target_arch = "wasm32")] 48 | fn create_webgl2_context( 49 | win: &web_sys::HtmlCanvasElement, 50 | antialias: bool, 51 | transparent: bool, 52 | ) -> Result { 53 | let gl = win 54 | .get_context_with_context_options("webgl2", webgl_options(antialias, transparent).as_ref()) 55 | .map_err(|e| format!("{e:?}"))? 56 | .ok_or("Cannot acquire the Webgl2 context. Is the canvas already instantiated?")? 57 | .dyn_into::() 58 | .map_err(|_| "Cannot acquire WebGL2 context.")?; 59 | 60 | let ctx = glow::Context::from_webgl2_context(gl); 61 | Ok(ctx) 62 | } 63 | -------------------------------------------------------------------------------- /examples/input_mouse.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[derive(AppState)] 5 | struct State { 6 | font: Font, 7 | x: f32, 8 | y: f32, 9 | left: Vec<(f32, f32)>, // red 10 | middle: Vec<(f32, f32)>, // green 11 | right: Vec<(f32, f32)>, // blue 12 | } 13 | 14 | #[notan_main] 15 | fn main() -> Result<(), String> { 16 | notan::init_with(setup) 17 | .add_config(DrawConfig) 18 | .update(update) 19 | .draw(draw) 20 | .build() 21 | } 22 | 23 | fn setup(gfx: &mut Graphics) -> State { 24 | let font = gfx 25 | .create_font(include_bytes!("assets/Ubuntu-B.ttf")) 26 | .unwrap(); 27 | 28 | State { 29 | font, 30 | x: 0.0, 31 | y: 0.0, 32 | left: vec![], 33 | middle: vec![], 34 | right: vec![], 35 | } 36 | } 37 | 38 | fn update(app: &mut App, state: &mut State) { 39 | // get mouse cursor position here 40 | let (x, y) = app.mouse.position(); 41 | 42 | if app.mouse.was_pressed(MouseButton::Left) { 43 | state.left.push((x, y)); 44 | } 45 | 46 | if app.mouse.was_pressed(MouseButton::Middle) { 47 | state.middle.push((x, y)); 48 | } 49 | 50 | if app.mouse.was_pressed(MouseButton::Right) { 51 | state.right.push((x, y)); 52 | } 53 | 54 | state.x = x; 55 | state.y = y; 56 | } 57 | 58 | fn draw(gfx: &mut Graphics, state: &mut State) { 59 | let mut draw = gfx.create_draw(); 60 | draw.clear(Color::BLACK); 61 | 62 | // Draw cursor 63 | draw.circle(8.0) 64 | .position(state.x, state.y) 65 | .color(Color::ORANGE); 66 | 67 | // Draw left clicks 68 | state.left.iter().for_each(|(x, y)| { 69 | draw.circle(4.0).position(*x, *y).color(Color::RED); 70 | }); 71 | 72 | // Draw middle clicks 73 | state.middle.iter().for_each(|(x, y)| { 74 | draw.circle(4.0).position(*x, *y).color(Color::GREEN); 75 | }); 76 | 77 | // Draw right clicks 78 | state.right.iter().for_each(|(x, y)| { 79 | draw.circle(4.0).position(*x, *y).color(Color::BLUE); 80 | }); 81 | 82 | // Draw position 83 | let text = format!("x: {} - y: {}", state.x, state.y); 84 | draw.text(&state.font, &text) 85 | .position(400.0, 300.0) 86 | .size(80.0) 87 | .h_align_center() 88 | .v_align_middle(); 89 | 90 | gfx.render(&draw); 91 | } 92 | -------------------------------------------------------------------------------- /crates/notan_audio/src/backend.rs: -------------------------------------------------------------------------------- 1 | use crate::tracker::{ResourceId, ResourceTracker}; 2 | use std::sync::Arc; 3 | 4 | /// Represent the audio implementation backend 5 | pub trait AudioBackend { 6 | fn set_global_volume(&mut self, volume: f32); 7 | fn global_volume(&self) -> f32; 8 | fn create_source(&mut self, bytes: &[u8]) -> Result; 9 | fn play_sound(&mut self, source: u64, volume: f32, repeat: bool) -> Result; 10 | fn pause(&mut self, sound: u64); 11 | fn resume(&mut self, sound: u64); 12 | fn stop(&mut self, sound: u64); 13 | #[allow(clippy::wrong_self_convention)] 14 | fn is_stopped(&mut self, sound: u64) -> bool; 15 | #[allow(clippy::wrong_self_convention)] 16 | fn is_paused(&mut self, sound: u64) -> bool; 17 | fn set_volume(&mut self, sound: u64, volume: f32); 18 | fn volume(&self, sound: u64) -> f32; 19 | fn clean(&mut self, sources: &[u64], sounds: &[u64]); 20 | // fn remaining_time(&self, sound: u64) -> f32; 21 | } 22 | 23 | #[derive(Debug)] 24 | struct SourceIdRef { 25 | id: u64, 26 | tracker: Arc, 27 | } 28 | 29 | impl Drop for SourceIdRef { 30 | fn drop(&mut self) { 31 | self.tracker.push(ResourceId::Source(self.id)); 32 | } 33 | } 34 | 35 | #[derive(Debug, Clone)] 36 | pub struct AudioSource { 37 | pub(crate) id: u64, 38 | _id_ref: Arc, 39 | } 40 | 41 | impl AudioSource { 42 | pub(crate) fn new(id: u64, tracker: Arc) -> Self { 43 | let _id_ref = Arc::new(SourceIdRef { id, tracker }); 44 | Self { id, _id_ref } 45 | } 46 | } 47 | 48 | impl PartialEq for AudioSource { 49 | fn eq(&self, other: &Self) -> bool { 50 | self.id == other.id 51 | } 52 | } 53 | 54 | #[derive(Debug)] 55 | struct SoundIdRef { 56 | id: u64, 57 | tracker: Arc, 58 | } 59 | 60 | impl Drop for SoundIdRef { 61 | fn drop(&mut self) { 62 | self.tracker.push(ResourceId::Sound(self.id)); 63 | } 64 | } 65 | 66 | #[derive(Debug, Clone)] 67 | pub struct Sound { 68 | pub(crate) id: u64, 69 | _id_ref: Arc, 70 | } 71 | 72 | impl Sound { 73 | pub(crate) fn new(id: u64, tracker: Arc) -> Self { 74 | let _id_ref = Arc::new(SoundIdRef { id, tracker }); 75 | Self { id, _id_ref } 76 | } 77 | } 78 | 79 | impl PartialEq for Sound { 80 | fn eq(&self, other: &Self) -> bool { 81 | self.id == other.id 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /examples/renderer_quad_wireframe.rs: -------------------------------------------------------------------------------- 1 | use notan::prelude::*; 2 | 3 | //language=glsl 4 | const VERT: ShaderSource = notan::vertex_shader! { 5 | r#" 6 | #version 450 7 | layout(location = 0) in vec2 a_pos; 8 | 9 | void main() { 10 | gl_Position = vec4(a_pos - 0.5, 0.0, 1.0); 11 | } 12 | "# 13 | }; 14 | 15 | //language=glsl 16 | const FRAG: ShaderSource = notan::fragment_shader! { 17 | r#" 18 | #version 450 19 | precision mediump float; 20 | 21 | layout(location = 0) out vec4 color; 22 | 23 | void main() { 24 | color = vec4(0.0, 1.0, 1.0, 1.0); 25 | } 26 | "# 27 | }; 28 | 29 | #[derive(AppState)] 30 | struct State { 31 | pipeline: Pipeline, 32 | vertex_buffer: Buffer, 33 | index_buffer: Buffer, 34 | } 35 | 36 | #[notan_main] 37 | fn main() -> Result<(), String> { 38 | notan::init_with(setup).draw(draw).build() 39 | } 40 | 41 | fn setup(gfx: &mut Graphics) -> State { 42 | #[rustfmt::skip] 43 | let vertices = [ 44 | 0.0, 1.0, // top-left 45 | 0.0, 0.0, // bottom-left 46 | 1.0, 0.0, // bottom-right 47 | 1.0, 1.0, // top-right 48 | ]; 49 | 50 | let indices = [ 51 | 0, 1, 1, 2, 2, 0, // first triangle lines 52 | 0, 2, 2, 3, 3, 0, // second triangle lines 53 | ]; 54 | 55 | let vertex_info = VertexInfo::new().attr(0, VertexFormat::Float32x2); 56 | 57 | let pipeline = gfx 58 | .create_pipeline() 59 | .from(&VERT, &FRAG) 60 | .with_vertex_info(&vertex_info) 61 | .build() 62 | .unwrap(); 63 | 64 | let vertex_buffer = gfx 65 | .create_vertex_buffer() 66 | .with_info(&vertex_info) 67 | .with_data(&vertices) 68 | .build() 69 | .unwrap(); 70 | 71 | let index_buffer = gfx 72 | .create_index_buffer() 73 | .with_data(&indices) 74 | .build() 75 | .unwrap(); 76 | 77 | State { 78 | pipeline, 79 | vertex_buffer, 80 | index_buffer, 81 | } 82 | } 83 | 84 | fn draw(gfx: &mut Graphics, state: &mut State) { 85 | let mut renderer = gfx.create_renderer(); 86 | 87 | renderer.begin(Some(ClearOptions::color(Color::BLACK))); 88 | renderer.set_pipeline(&state.pipeline); 89 | renderer.set_primitive(DrawPrimitive::LineStrip); 90 | renderer.bind_buffers(&[&state.vertex_buffer, &state.index_buffer]); 91 | renderer.draw(0, 12); 92 | renderer.end(); 93 | 94 | gfx.render(&renderer); 95 | } 96 | -------------------------------------------------------------------------------- /examples/assets_try_unwrap.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[derive(AppState)] 5 | struct State { 6 | font: Font, 7 | loading: Option>, 8 | loaded: Option, 9 | } 10 | 11 | impl State { 12 | fn new(assets: &mut Assets, gfx: &mut Graphics) -> Self { 13 | // Start loading the texture 14 | let texture = assets 15 | .load_asset(&asset_path("rust-logo-512x512.png")) 16 | .unwrap(); 17 | 18 | // Load a font only for debug info 19 | let font = gfx 20 | .create_font(include_bytes!("assets/Ubuntu-B.ttf")) 21 | .unwrap(); 22 | 23 | Self { 24 | font, 25 | loading: Some(texture), 26 | loaded: None, 27 | } 28 | } 29 | } 30 | 31 | #[notan_main] 32 | fn main() -> Result<(), String> { 33 | notan::init_with(State::new) 34 | .add_config(DrawConfig) 35 | .update(update) 36 | .draw(draw) 37 | .build() 38 | } 39 | 40 | fn update(state: &mut State) { 41 | // Unwrapping an asset reference after it has been loaded we avoid the performance penalty 42 | // of the Arc> inside the Asset instance 43 | 44 | let was_just_loaded = state 45 | .loading 46 | .as_ref() 47 | .map(|assets| assets.is_loaded()) 48 | .unwrap_or(false); 49 | 50 | if was_just_loaded { 51 | // Get ownership of the Asset inside the Option 52 | let asset = state.loading.take().unwrap(); 53 | 54 | // Unwrap the asset reference to get the inner 55 | let texture = asset.try_unwrap().unwrap(); 56 | state.loaded = Some(texture); 57 | } 58 | } 59 | 60 | fn draw(gfx: &mut Graphics, state: &mut State) { 61 | let mut draw = gfx.create_draw(); 62 | draw.clear(Color::BLACK); 63 | 64 | match &state.loaded { 65 | None => { 66 | draw.text(&state.font, "Loading...") 67 | .position(10.0, 10.0) 68 | .size(25.0); 69 | } 70 | Some(texture) => { 71 | draw.image(texture).position(150.0, 50.0); 72 | } 73 | } 74 | 75 | gfx.render(&draw); 76 | } 77 | 78 | // The relative path for the example is different on browsers 79 | fn asset_path(path: &str) -> String { 80 | let base = if cfg!(target_arch = "wasm32") { 81 | "./assets" 82 | } else { 83 | "./examples/assets" 84 | }; 85 | 86 | format!("{base}/{path}") 87 | } 88 | -------------------------------------------------------------------------------- /crates/notan_glyph/src/instance.rs: -------------------------------------------------------------------------------- 1 | use glyph_brush::ab_glyph::{point, Rect}; 2 | 3 | #[derive(Debug, Clone, Copy)] 4 | #[repr(C)] 5 | pub struct GlyphInstance { 6 | pub left_top: [f32; 3], 7 | pub right_bottom: [f32; 2], 8 | pub tex_left_top: [f32; 2], 9 | pub tex_right_bottom: [f32; 2], 10 | pub color: [f32; 4], 11 | } 12 | 13 | unsafe impl bytemuck::Zeroable for GlyphInstance {} 14 | unsafe impl bytemuck::Pod for GlyphInstance {} 15 | 16 | impl GlyphInstance { 17 | pub fn from_vertex( 18 | glyph_brush::GlyphVertex { 19 | mut tex_coords, 20 | pixel_coords, 21 | bounds, 22 | extra, 23 | }: glyph_brush::GlyphVertex, 24 | ) -> GlyphInstance { 25 | let gl_bounds = bounds; 26 | 27 | let mut gl_rect = Rect { 28 | min: point(pixel_coords.min.x, pixel_coords.min.y), 29 | max: point(pixel_coords.max.x, pixel_coords.max.y), 30 | }; 31 | 32 | // handle overlapping bounds, modify uv_rect to preserve texture aspect 33 | if gl_rect.max.x > gl_bounds.max.x { 34 | let old_width = gl_rect.width(); 35 | gl_rect.max.x = gl_bounds.max.x; 36 | tex_coords.max.x = tex_coords.min.x + tex_coords.width() * gl_rect.width() / old_width; 37 | } 38 | 39 | if gl_rect.min.x < gl_bounds.min.x { 40 | let old_width = gl_rect.width(); 41 | gl_rect.min.x = gl_bounds.min.x; 42 | tex_coords.min.x = tex_coords.max.x - tex_coords.width() * gl_rect.width() / old_width; 43 | } 44 | 45 | if gl_rect.max.y > gl_bounds.max.y { 46 | let old_height = gl_rect.height(); 47 | gl_rect.max.y = gl_bounds.max.y; 48 | tex_coords.max.y = 49 | tex_coords.min.y + tex_coords.height() * gl_rect.height() / old_height; 50 | } 51 | 52 | if gl_rect.min.y < gl_bounds.min.y { 53 | let old_height = gl_rect.height(); 54 | gl_rect.min.y = gl_bounds.min.y; 55 | tex_coords.min.y = 56 | tex_coords.max.y - tex_coords.height() * gl_rect.height() / old_height; 57 | } 58 | 59 | GlyphInstance { 60 | left_top: [gl_rect.min.x, gl_rect.max.y, extra.z], 61 | right_bottom: [gl_rect.max.x, gl_rect.min.y], 62 | tex_left_top: [tex_coords.min.x, tex_coords.max.y], 63 | tex_right_bottom: [tex_coords.max.x, tex_coords.min.y], 64 | color: extra.color, 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/draw_blend_mode_object.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | #[derive(AppState)] 5 | struct State { 6 | texture: Texture, 7 | font: Font, 8 | count: f32, 9 | } 10 | 11 | #[notan_main] 12 | fn main() -> Result<(), String> { 13 | notan::init_with(init) 14 | .add_config(DrawConfig) 15 | .draw(draw) 16 | .build() 17 | } 18 | 19 | fn init(gfx: &mut Graphics) -> State { 20 | let font = gfx 21 | .create_font(include_bytes!("assets/Ubuntu-B.ttf")) 22 | .unwrap(); 23 | 24 | let texture = gfx 25 | .create_texture() 26 | .from_image(include_bytes!("assets/ferris.png")) 27 | .build() 28 | .unwrap(); 29 | 30 | State { 31 | font, 32 | texture, 33 | count: 0.0, 34 | } 35 | } 36 | 37 | fn draw(app: &mut App, gfx: &mut Graphics, state: &mut State) { 38 | let mut draw = gfx.create_draw(); 39 | draw.clear(Color::BLACK); 40 | 41 | // background with different color 42 | let colors = [ 43 | Color::GREEN, 44 | Color::BLUE, 45 | Color::WHITE, 46 | Color::RED, 47 | Color::YELLOW, 48 | Color::AQUA, 49 | ]; 50 | colors.iter().enumerate().for_each(|(i, color)| { 51 | let height = (600 / colors.len()) as f32; 52 | let yy = height * (i as f32); 53 | draw.rect((0.0, yy), (800.0, height)).color(*color); 54 | }); 55 | 56 | // a few blend modes 57 | #[rustfmt::skip] 58 | let modes = [ 59 | ("Normal", BlendMode::NORMAL), 60 | ("Add", BlendMode::ADD), 61 | ("Erase", BlendMode::ERASE), 62 | ("Screen", BlendMode::SCREEN), 63 | ("Multiply", BlendMode::MULTIPLY), 64 | ]; 65 | 66 | modes.iter().enumerate().for_each(|(i, (name, mode))| { 67 | let width = (800 / modes.len()) as f32; 68 | let xx = width * (i as f32); 69 | 70 | let scale = width / state.texture.width(); 71 | let yy = 300.0 - (i as f32 * 20.0) + state.count.sin() * 300.0; 72 | 73 | // Draw image with a custom blend mode 74 | draw.image(&state.texture) 75 | .scale(scale, scale) 76 | .translate(xx, yy) 77 | .blend_mode(*mode); 78 | 79 | // print names 80 | draw.text(&state.font, name) 81 | .size(20.0) 82 | .position(xx + 10.0, 10.0) 83 | .color(Color::BLACK); 84 | 85 | state.count += 0.05 * app.timer.delta_f32(); 86 | }); 87 | 88 | gfx.render(&draw); 89 | } 90 | -------------------------------------------------------------------------------- /examples/glyph_hello_raw.rs: -------------------------------------------------------------------------------- 1 | /* 2 | This example shows how to use GlyphBrush directly without the GlyphExtension 3 | */ 4 | 5 | use notan::glyph::{ab_glyph, GlyphBrushBuilder, Section, Text}; 6 | use notan::prelude::*; 7 | use notan_glyph::{DefaultGlyphPipeline, GlyphBrush}; 8 | 9 | #[derive(AppState)] 10 | struct State { 11 | glyph_brush: GlyphBrush, 12 | pipeline: DefaultGlyphPipeline, 13 | } 14 | 15 | #[notan_main] 16 | fn main() -> Result<(), String> { 17 | notan::init_with(setup).draw(draw).build() 18 | } 19 | 20 | fn setup(gfx: &mut Graphics) -> State { 21 | // Load the font that we'll use to draw the text 22 | let font = ab_glyph::FontArc::try_from_slice(include_bytes!("./assets/Ubuntu-B.ttf")).unwrap(); 23 | 24 | // Create GlyphBrush using the loaded font 25 | let glyph_brush = GlyphBrushBuilder::using_font(font).build(gfx); 26 | 27 | // This is the default pipeline included to draw the glyphs using instancing, you can use your custom pipeline 28 | let pipeline = DefaultGlyphPipeline::new(gfx).unwrap(); 29 | 30 | State { 31 | glyph_brush, 32 | pipeline, 33 | } 34 | } 35 | 36 | fn draw(gfx: &mut Graphics, state: &mut State) { 37 | let (width, height) = gfx.size(); 38 | 39 | // Queue sections to draw 40 | state.glyph_brush.queue( 41 | Section::new() 42 | .with_screen_position((30.0, 30.0)) 43 | .with_bounds((width as _, height as _)) 44 | .add_text( 45 | Text::default() 46 | .with_text("Hello notan_glyph!") 47 | .with_color([1.0, 0.0, 0.0, 1.0]) 48 | .with_scale(40.0), 49 | ), 50 | ); 51 | 52 | state.glyph_brush.queue( 53 | Section::new() 54 | .with_screen_position((30.0, 90.0)) 55 | .with_bounds((width as _, height as _)) 56 | .add_text( 57 | Text::default() 58 | .with_text("Hello notan_glyph!") 59 | .with_color([1.0, 1.0, 1.0, 1.0]) 60 | .with_scale(40.0), 61 | ), 62 | ); 63 | 64 | // process the queued texts before render them 65 | state.glyph_brush.process_queued(gfx, &mut state.pipeline); 66 | 67 | // process the queue and return a renderer to draw 68 | let renderer = state 69 | .glyph_brush 70 | .render_queue(gfx, &mut state.pipeline) 71 | .clear(ClearOptions::color(Color::BLACK)) 72 | .build(); 73 | 74 | // Draw the renderer to the screen 75 | gfx.render(&renderer); 76 | } 77 | -------------------------------------------------------------------------------- /examples/draw_shapes_shader.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::math::Vec2; 3 | use notan::prelude::*; 4 | 5 | const POS: Vec2 = Vec2::new(600.0, 300.0); 6 | 7 | //language=glsl 8 | const FRAGMENT: ShaderSource = notan::fragment_shader! { 9 | r#" 10 | #version 450 11 | precision mediump float; 12 | 13 | layout(location = 0) in vec4 v_color; 14 | layout(location = 0) out vec4 color; 15 | 16 | layout(set = 0, binding = 1) uniform Surface { 17 | vec2 u_pos; 18 | }; 19 | 20 | void main() { 21 | if (gl_FragCoord.y < u_pos.y) { 22 | if (gl_FragCoord.x < u_pos.x) { 23 | color = vec4(1.0, 0.0, 0.0, 1.0) * v_color; 24 | } else { 25 | color = vec4(0.0, 0.0, 1.0, 1.0) * v_color; 26 | } 27 | } else { 28 | if (gl_FragCoord.x < u_pos.x) { 29 | color = vec4(0.0, 1.0, 0.0, 1.0) * v_color; 30 | } else { 31 | color = vec4(0.5, 0.0, 1.0, 1.0) * v_color; 32 | } 33 | } 34 | } 35 | "# 36 | }; 37 | 38 | #[derive(AppState)] 39 | struct State { 40 | pipeline: Pipeline, 41 | ubo: Buffer, 42 | count: f32, 43 | } 44 | 45 | #[notan_main] 46 | fn main() -> Result<(), String> { 47 | notan::init_with(init) 48 | .add_config(DrawConfig) 49 | .draw(draw) 50 | .build() 51 | } 52 | 53 | fn init(gfx: &mut Graphics) -> State { 54 | let pipeline = create_shape_pipeline(gfx, Some(&FRAGMENT)).unwrap(); 55 | 56 | let ubo = gfx 57 | .create_uniform_buffer(1, "Surface") 58 | .with_data(&[POS.x, POS.y]) 59 | .build() 60 | .unwrap(); 61 | 62 | State { 63 | pipeline, 64 | ubo, 65 | count: 0.0, 66 | } 67 | } 68 | 69 | fn draw(app: &mut App, gfx: &mut Graphics, state: &mut State) { 70 | state.count += app.timer.delta_f32() * 10.0; 71 | 72 | let mut draw = gfx.create_draw(); 73 | draw.clear(Color::BLACK); 74 | 75 | // star without custom pipeline 76 | draw.star(5, 150.0, 70.0) 77 | .position(200.0, 300.0) 78 | .rotate_from((200.0, 300.0), state.count.to_radians()); 79 | 80 | // add custom pipeline for shapes 81 | draw.shape_pipeline() 82 | .pipeline(&state.pipeline) 83 | .uniform_buffer(&state.ubo); 84 | 85 | draw.star(5, 150.0, 70.0) 86 | .position(600.0, 300.0) 87 | .rotate_from((600.0, 300.0), state.count.to_radians()); 88 | 89 | // remove custom pipeline 90 | draw.shape_pipeline().remove(); 91 | 92 | gfx.render(&draw); 93 | } 94 | -------------------------------------------------------------------------------- /crates/notan_glyph/src/builder.rs: -------------------------------------------------------------------------------- 1 | use core::hash::BuildHasher; 2 | 3 | use glyph_brush::ab_glyph::Font; 4 | use glyph_brush::delegate_glyph_brush_builder_fns; 5 | use glyph_brush::DefaultSectionHasher; 6 | use notan_app::Graphics; 7 | 8 | use super::GlyphBrush; 9 | 10 | /// Builder for a [`GlyphBrush`](struct.GlyphBrush.html). 11 | pub struct GlyphBrushBuilder { 12 | inner: glyph_brush::GlyphBrushBuilder, 13 | } 14 | 15 | impl From> for GlyphBrushBuilder { 16 | fn from(inner: glyph_brush::GlyphBrushBuilder) -> Self { 17 | GlyphBrushBuilder { inner } 18 | } 19 | } 20 | 21 | impl GlyphBrushBuilder<()> { 22 | /// Specifies the default font used to render glyphs. 23 | /// Referenced with `FontId(0)`, which is default. 24 | #[inline] 25 | pub fn using_font(font: F) -> GlyphBrushBuilder { 26 | Self::using_fonts(vec![font]) 27 | } 28 | 29 | pub fn using_fonts(fonts: Vec) -> GlyphBrushBuilder { 30 | GlyphBrushBuilder { 31 | inner: glyph_brush::GlyphBrushBuilder::using_fonts(fonts), 32 | } 33 | } 34 | } 35 | 36 | impl GlyphBrushBuilder { 37 | delegate_glyph_brush_builder_fns!(inner); 38 | 39 | /// When multiple CPU cores are available spread rasterization work across 40 | /// all cores. 41 | /// 42 | /// Significantly reduces worst case latency in multicore environments. 43 | /// 44 | /// # Platform-specific behaviour 45 | /// 46 | /// This option has no effect on wasm32. 47 | pub fn draw_cache_multithread(mut self, multithread: bool) -> Self { 48 | self.inner.draw_cache_builder = self.inner.draw_cache_builder.multithread(multithread); 49 | 50 | self 51 | } 52 | 53 | /// Sets the section hasher. `GlyphBrush` cannot handle absolute section 54 | /// hash collisions so use a good hash algorithm. 55 | /// 56 | /// This hasher is used to distinguish sections, rather than for hashmap 57 | /// internal use. 58 | /// 59 | /// Defaults to [seahash](https://docs.rs/seahash). 60 | pub fn section_hasher(self, section_hasher: T) -> GlyphBrushBuilder { 61 | GlyphBrushBuilder { 62 | inner: self.inner.section_hasher(section_hasher), 63 | } 64 | } 65 | 66 | /// Builds a `GlyphBrush` in the given `notan::Graphics`. 67 | pub fn build(self, gfx: &mut Graphics) -> GlyphBrush { 68 | GlyphBrush::::new(gfx, self.inner) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /crates/notan_app/src/app.rs: -------------------------------------------------------------------------------- 1 | pub use crate::timer::AppTimer; 2 | use crate::{Backend, WindowBackend}; 3 | 4 | #[cfg(feature = "audio")] 5 | use notan_audio::Audio; 6 | 7 | use notan_input::keyboard::Keyboard; 8 | use notan_input::mouse::Mouse; 9 | use notan_input::touch::Touch; 10 | 11 | /// Represents the state of the application, always accessible across the event's cycle 12 | pub trait AppState {} 13 | impl AppState for () {} 14 | 15 | /// Represents the context of the application 16 | pub struct App { 17 | /// Backend implementation 18 | pub backend: Box, 19 | 20 | /// Mouse data 21 | pub mouse: Mouse, 22 | 23 | /// Keyboard data 24 | pub keyboard: Keyboard, 25 | 26 | /// Touch data 27 | pub touch: Touch, 28 | 29 | /// System timer 30 | pub system_timer: AppTimer, 31 | 32 | /// App timer 33 | pub timer: AppTimer, 34 | 35 | #[cfg(feature = "audio")] 36 | /// Audio manager 37 | pub audio: Audio, 38 | 39 | pub(crate) closed: bool, 40 | } 41 | 42 | impl App { 43 | pub(crate) fn new(backend: Box, #[cfg(feature = "audio")] audio: Audio) -> Self { 44 | let mouse = Default::default(); 45 | let keyboard = Default::default(); 46 | let touch = Default::default(); 47 | Self { 48 | backend, 49 | #[cfg(feature = "audio")] 50 | audio, 51 | mouse, 52 | keyboard, 53 | touch, 54 | system_timer: AppTimer::default(), 55 | timer: AppTimer::default(), 56 | closed: false, 57 | } 58 | } 59 | 60 | #[inline] 61 | #[cfg(feature = "links")] 62 | pub fn open_link(&self, url: &str) { 63 | self.backend.open_link(url, false); 64 | } 65 | 66 | #[inline] 67 | #[cfg(feature = "links")] 68 | pub fn open_link_new_tab(&self, url: &str) { 69 | self.backend.open_link(url, true); 70 | } 71 | 72 | #[inline] 73 | pub fn date_now(&self) -> u64 { 74 | self.backend.system_timestamp() 75 | } 76 | 77 | #[inline] 78 | pub fn exit(&mut self) { 79 | self.closed = true; 80 | self.backend.exit(); 81 | } 82 | 83 | #[inline] 84 | pub fn window(&mut self) -> &mut dyn WindowBackend { 85 | self.backend.window() 86 | } 87 | 88 | #[inline] 89 | /// Returns the backend downcasted to the real type (useful for custom backends) 90 | pub fn backend(&mut self) -> Result<&mut T, String> { 91 | self.backend 92 | .downcast_mut::() 93 | .ok_or_else(|| "Invalid backend type.".to_string()) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /crates/notan_random/src/utils.rs: -------------------------------------------------------------------------------- 1 | use rand::seq::SliceRandom; 2 | use rand::SeedableRng; 3 | use rand_pcg::Pcg32; 4 | use std::ops::{Deref, DerefMut}; 5 | 6 | /// Wrapper around a random generator based on Pcg32. 7 | #[derive(Clone)] 8 | pub struct Random { 9 | rng: Pcg32, 10 | } 11 | 12 | impl Random { 13 | pub fn new(seed: u64) -> Self { 14 | Self { 15 | rng: Pcg32::seed_from_u64(seed), 16 | } 17 | } 18 | 19 | pub fn reseed(&mut self, seed: u64) { 20 | self.rng = Pcg32::seed_from_u64(seed); 21 | } 22 | } 23 | 24 | impl Deref for Random { 25 | type Target = Pcg32; 26 | fn deref(&self) -> &Self::Target { 27 | &self.rng 28 | } 29 | } 30 | 31 | impl DerefMut for Random { 32 | fn deref_mut(&mut self) -> &mut Self::Target { 33 | &mut self.rng 34 | } 35 | } 36 | 37 | impl Default for Random { 38 | fn default() -> Self { 39 | Self { 40 | rng: Pcg32::from_entropy(), 41 | } 42 | } 43 | } 44 | 45 | /// Returns a random number for a predefined bag of them 46 | pub struct ShuffleBag 47 | where 48 | T: Sized + Clone, 49 | { 50 | rng: Random, 51 | index: usize, 52 | items: Vec, 53 | bag: Vec, 54 | } 55 | 56 | impl ShuffleBag 57 | where 58 | T: Sized + Clone, 59 | { 60 | /// Create a new ShuffleBag using a random seed 61 | pub fn new(capacity: usize) -> Self { 62 | Self::new_with_random(Random::default(), capacity) 63 | } 64 | 65 | pub fn new_with_random(rng: Random, capacity: usize) -> Self { 66 | Self { 67 | rng, 68 | index: 0, 69 | items: vec![], 70 | bag: Vec::with_capacity(capacity), 71 | } 72 | } 73 | 74 | /// Adds a new value to the bag 75 | pub fn add(&mut self, item: T, amount: usize) { 76 | self.items.push(item); 77 | let index = self.items.len() - 1; 78 | self.bag.extend_from_slice(&vec![index; amount]); 79 | self.reset(); 80 | } 81 | 82 | /// Returns the next value from the bag 83 | pub fn item(&mut self) -> Option<&T> { 84 | if self.items.is_empty() { 85 | return None; 86 | } 87 | 88 | if self.index >= self.bag.len() { 89 | self.reset(); 90 | } 91 | 92 | let item = &self.items[self.bag[self.index]]; 93 | self.index += 1; 94 | Some(item) 95 | } 96 | 97 | /// Reset the bag to the initial state 98 | pub fn reset(&mut self) { 99 | self.bag.shuffle(&mut self.rng.rng); 100 | self.index = 0; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /examples/renderer_quad.rs: -------------------------------------------------------------------------------- 1 | use notan::prelude::*; 2 | 3 | //language=glsl 4 | const VERT: ShaderSource = notan::vertex_shader! { 5 | r#" 6 | #version 450 7 | layout(location = 0) in vec2 a_pos; 8 | layout(location = 1) in vec3 a_color; 9 | 10 | layout(location = 0) out vec3 v_color; 11 | 12 | void main() { 13 | v_color = a_color; 14 | gl_Position = vec4(a_pos - 0.5, 0.0, 1.0); 15 | } 16 | "# 17 | }; 18 | 19 | //language=glsl 20 | const FRAG: ShaderSource = notan::fragment_shader! { 21 | r#" 22 | #version 450 23 | precision mediump float; 24 | 25 | layout(location = 0) in vec3 v_color; 26 | layout(location = 0) out vec4 color; 27 | 28 | void main() { 29 | color = vec4(v_color, 1.0); 30 | } 31 | "# 32 | }; 33 | 34 | #[derive(AppState)] 35 | struct State { 36 | clear_options: ClearOptions, 37 | pipeline: Pipeline, 38 | vertex_buffer: Buffer, 39 | index_buffer: Buffer, 40 | } 41 | 42 | #[notan_main] 43 | fn main() -> Result<(), String> { 44 | notan::init_with(setup).draw(draw).build() 45 | } 46 | 47 | fn setup(gfx: &mut Graphics) -> State { 48 | let clear_options = ClearOptions::color(Color::new(0.1, 0.2, 0.3, 1.0)); 49 | 50 | #[rustfmt::skip] 51 | let vertices = [ 52 | 0.0, 1.0, 1.0, 0.2, 0.3, 53 | 0.0, 0.0, 0.1, 1.0, 0.3, 54 | 1.0, 0.0, 0.1, 0.2, 1.0, 55 | 1.0, 1.0, 1.0, 1.0, 0.1, 56 | ]; 57 | 58 | let indices = [0, 1, 2, 0, 2, 3]; 59 | 60 | let vertex_info = VertexInfo::new() 61 | .attr(0, VertexFormat::Float32x2) 62 | .attr(1, VertexFormat::Float32x3); 63 | 64 | let pipeline = gfx 65 | .create_pipeline() 66 | .from(&VERT, &FRAG) 67 | .with_vertex_info(&vertex_info) 68 | .build() 69 | .unwrap(); 70 | 71 | let vertex_buffer = gfx 72 | .create_vertex_buffer() 73 | .with_info(&vertex_info) 74 | .with_data(&vertices) 75 | .build() 76 | .unwrap(); 77 | 78 | let index_buffer = gfx 79 | .create_index_buffer() 80 | .with_data(&indices) 81 | .build() 82 | .unwrap(); 83 | 84 | State { 85 | clear_options, 86 | pipeline, 87 | vertex_buffer, 88 | index_buffer, 89 | } 90 | } 91 | 92 | fn draw(gfx: &mut Graphics, state: &mut State) { 93 | let mut renderer = gfx.create_renderer(); 94 | 95 | renderer.begin(Some(state.clear_options)); 96 | renderer.set_pipeline(&state.pipeline); 97 | renderer.bind_buffers(&[&state.vertex_buffer, &state.index_buffer]); 98 | renderer.draw(0, 6); 99 | renderer.end(); 100 | 101 | gfx.render(&renderer); 102 | } 103 | -------------------------------------------------------------------------------- /crates/notan_draw/src/shapes.rs: -------------------------------------------------------------------------------- 1 | mod circle; 2 | mod ellipse; 3 | mod geometry; 4 | mod line; 5 | mod painter; 6 | mod path; 7 | mod point; 8 | mod polygon; 9 | mod rect; 10 | mod star; 11 | mod tess; 12 | mod triangle; 13 | 14 | pub use crate::builder::DrawBuilder; 15 | pub use crate::draw::Draw; 16 | pub use circle::Circle; 17 | pub use ellipse::Ellipse; 18 | pub use line::Line; 19 | pub use painter::create_shape_pipeline; 20 | pub(crate) use painter::*; 21 | pub use path::Path; 22 | pub use point::{Point, XAlignment, YAlignment}; 23 | pub use polygon::Polygon; 24 | pub use rect::Rectangle; 25 | pub use star::Star; 26 | pub use triangle::Triangle; 27 | 28 | pub trait DrawShapes { 29 | fn point(&mut self, x: f32, y: f32) -> DrawBuilder; 30 | fn line(&mut self, p1: (f32, f32), p2: (f32, f32)) -> DrawBuilder; 31 | fn triangle(&mut self, a: (f32, f32), b: (f32, f32), c: (f32, f32)) -> DrawBuilder; 32 | fn path(&mut self) -> DrawBuilder; 33 | fn rect(&mut self, position: (f32, f32), size: (f32, f32)) -> DrawBuilder; 34 | fn circle(&mut self, radius: f32) -> DrawBuilder; 35 | fn ellipse(&mut self, position: (f32, f32), size: (f32, f32)) -> DrawBuilder; 36 | fn star(&mut self, spikes: u8, outer_radius: f32, inner_radius: f32) -> DrawBuilder; 37 | fn polygon(&mut self, sides: u8, radius: f32) -> DrawBuilder; 38 | } 39 | 40 | impl DrawShapes for Draw { 41 | fn point(&mut self, x: f32, y: f32) -> DrawBuilder { 42 | DrawBuilder::new(self, Point::new(x, y)) 43 | } 44 | 45 | fn line(&mut self, p1: (f32, f32), p2: (f32, f32)) -> DrawBuilder { 46 | DrawBuilder::new(self, Line::new(p1, p2)) 47 | } 48 | 49 | fn triangle(&mut self, a: (f32, f32), b: (f32, f32), c: (f32, f32)) -> DrawBuilder { 50 | DrawBuilder::new(self, Triangle::new(a, b, c)) 51 | } 52 | 53 | fn path(&mut self) -> DrawBuilder { 54 | DrawBuilder::new(self, Path::new()) 55 | } 56 | 57 | fn rect(&mut self, position: (f32, f32), size: (f32, f32)) -> DrawBuilder { 58 | DrawBuilder::new(self, Rectangle::new(position, size)) 59 | } 60 | 61 | fn circle(&mut self, radius: f32) -> DrawBuilder { 62 | DrawBuilder::new(self, Circle::new(radius)) 63 | } 64 | 65 | fn ellipse(&mut self, position: (f32, f32), size: (f32, f32)) -> DrawBuilder { 66 | DrawBuilder::new(self, Ellipse::new(position, size)) 67 | } 68 | 69 | fn star(&mut self, spikes: u8, outer_radius: f32, inner_radius: f32) -> DrawBuilder { 70 | DrawBuilder::new(self, Star::new(spikes, outer_radius, inner_radius)) 71 | } 72 | 73 | fn polygon(&mut self, sides: u8, radius: f32) -> DrawBuilder { 74 | DrawBuilder::new(self, Polygon::new(sides, radius)) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /examples/app_drop_file.rs: -------------------------------------------------------------------------------- 1 | use notan::app::Event; 2 | use notan::draw::*; 3 | use notan::prelude::*; 4 | 5 | #[derive(AppState)] 6 | struct State { 7 | font: Font, 8 | dragging: usize, 9 | asset: Option>, 10 | } 11 | 12 | #[notan_main] 13 | fn main() -> Result<(), String> { 14 | notan::init_with(setup) 15 | .add_config(DrawConfig) 16 | .add_config(notan::log::LogConfig::new(notan::log::LevelFilter::Debug)) 17 | .draw(draw) 18 | .event(event) 19 | .build() 20 | } 21 | 22 | fn setup(gfx: &mut Graphics) -> State { 23 | let font = gfx 24 | .create_font(include_bytes!("assets/Ubuntu-B.ttf")) 25 | .unwrap(); 26 | State { 27 | font, 28 | dragging: 0, 29 | asset: None, 30 | } 31 | } 32 | 33 | fn event(assets: &mut Assets, state: &mut State, evt: Event) { 34 | match evt { 35 | Event::DragEnter { .. } => { 36 | state.dragging += 1; 37 | } 38 | Event::DragLeft => { 39 | state.dragging = 0; 40 | } 41 | Event::Drop(file) => { 42 | state.dragging = 0; 43 | 44 | // Start loading the file if it's a png 45 | if file.name.contains(".png") { 46 | state.asset = Some(assets.load_dropped_file::(&file).unwrap()); 47 | } 48 | } 49 | _ => {} 50 | } 51 | } 52 | 53 | fn draw(gfx: &mut Graphics, state: &mut State) { 54 | let mut draw = gfx.create_draw(); 55 | draw.clear(Color::BLACK); 56 | 57 | // Display the image if it's already loaded 58 | if let Some(asset) = &state.asset { 59 | if let Some(img) = asset.lock() { 60 | draw.image(&img).position(30.0, 30.0).scale(0.5, 0.5); 61 | } 62 | } 63 | 64 | // Just UI Text 65 | if state.dragging == 0 { 66 | let text = match &state.asset { 67 | None => "Drop a PNG here", 68 | Some(asset) => { 69 | if asset.is_loaded() { 70 | "Drop another PNG here" 71 | } else { 72 | "Loading..." 73 | } 74 | } 75 | }; 76 | 77 | draw.text(&state.font, text) 78 | .color(Color::ORANGE) 79 | .size(30.0) 80 | .v_align_middle() 81 | .h_align_center() 82 | .position(400.0, 300.0); 83 | } else { 84 | draw.rect((10.0, 10.0), (780.0, 580.0)) 85 | .color(Color::WHITE) 86 | .stroke(6.0); 87 | 88 | let text = format!("You're dragging {} files", state.dragging); 89 | draw.text(&state.font, &text) 90 | .size(30.0) 91 | .color(Color::GRAY) 92 | .v_align_middle() 93 | .h_align_center() 94 | .position(400.0, 300.0); 95 | } 96 | 97 | gfx.render(&draw); 98 | } 99 | -------------------------------------------------------------------------------- /examples/draw_image_shader.rs: -------------------------------------------------------------------------------- 1 | use notan::draw::*; 2 | use notan::prelude::*; 3 | 4 | //language=glsl 5 | const FRAGMENT: ShaderSource = notan::fragment_shader! { 6 | r#" 7 | #version 450 8 | precision mediump float; 9 | 10 | layout(location = 0) in vec2 v_uvs; 11 | layout(location = 1) in vec4 v_color; 12 | 13 | layout(binding = 0) uniform sampler2D u_texture; 14 | layout(set = 0, binding = 1) uniform TextureInfo { 15 | float u_size; 16 | }; 17 | 18 | layout(location = 0) out vec4 color; 19 | 20 | void main() { 21 | vec2 tex_size = textureSize(u_texture, 0); 22 | vec2 p_size = vec2(u_size); 23 | vec2 coord = fract(v_uvs) * tex_size; 24 | coord = floor(coord/p_size) * p_size; 25 | color = texture(u_texture, coord / tex_size) * v_color; 26 | } 27 | "# 28 | }; 29 | 30 | #[derive(AppState)] 31 | struct State { 32 | texture: Texture, 33 | pipeline: Pipeline, 34 | uniforms: Buffer, 35 | count: f32, 36 | multi: f32, 37 | } 38 | 39 | #[notan_main] 40 | fn main() -> Result<(), String> { 41 | notan::init_with(init) 42 | .add_config(DrawConfig) 43 | .update(update) 44 | .draw(draw) 45 | .build() 46 | } 47 | 48 | fn init(gfx: &mut Graphics) -> State { 49 | let texture = gfx 50 | .create_texture() 51 | .from_image(include_bytes!("assets/ferris.png")) 52 | .build() 53 | .unwrap(); 54 | 55 | let pipeline = create_image_pipeline(gfx, Some(&FRAGMENT)).unwrap(); 56 | let uniforms = gfx 57 | .create_uniform_buffer(1, "TextureInfo") 58 | .with_data(&[5.0]) 59 | .build() 60 | .unwrap(); 61 | 62 | State { 63 | texture, 64 | pipeline, 65 | uniforms, 66 | count: 1.0, 67 | multi: 1.0, 68 | } 69 | } 70 | 71 | // Change the size of the pixel effect 72 | fn update(app: &mut App, state: &mut State) { 73 | if state.count > 5.0 || state.count < 0.0 { 74 | state.multi *= -1.0; 75 | } 76 | 77 | state.count += 0.3 * state.multi * app.timer.delta_f32(); 78 | } 79 | 80 | fn draw(gfx: &mut Graphics, state: &mut State) { 81 | let pixel_size = 5.0 + state.count; 82 | gfx.set_buffer_data(&state.uniforms, &[pixel_size]); 83 | 84 | let mut draw = gfx.create_draw(); 85 | draw.clear(Color::BLACK); 86 | 87 | // Image without a custom shader 88 | draw.image(&state.texture).position(10.0, 200.0); 89 | 90 | // Set the custom pipeline for image 91 | draw.image_pipeline() 92 | .pipeline(&state.pipeline) 93 | .uniform_buffer(&state.uniforms); 94 | 95 | draw.image(&state.texture) 96 | .position(10.0 + state.texture.width() + 40.0, 200.0); 97 | 98 | draw.image_pipeline().remove(); 99 | 100 | gfx.render(&draw); 101 | } 102 | -------------------------------------------------------------------------------- /examples/draw_path_flower.rs: -------------------------------------------------------------------------------- 1 | // This example is a port of javascript code made by Ibon Tolosona (@hyperandroid) 2 | // https://codepen.io/hyperandroid/full/yLyRQmw 3 | 4 | use notan::draw::*; 5 | use notan::prelude::*; 6 | 7 | const CENTER_X: f32 = 400.0; 8 | const CENTER_Y: f32 = 300.0; 9 | const START_RADIUS: usize = 20; 10 | const RADIUS_INCREMENT: usize = 13; 11 | const MAX_RADIUS: usize = 250; 12 | const MAX_LINES: usize = (MAX_RADIUS - START_RADIUS) / RADIUS_INCREMENT; 13 | const AMPLITUDE: f32 = 30.0; 14 | const PERIOD: f32 = 6.0; 15 | const PI: f32 = std::f32::consts::PI; 16 | 17 | #[notan_main] 18 | fn main() -> Result<(), String> { 19 | notan::init() 20 | .add_config(WindowConfig::new().set_multisampling(8)) 21 | .add_config(DrawConfig) 22 | .draw(draw) 23 | .build() 24 | } 25 | 26 | fn draw(app: &mut App, gfx: &mut Graphics) { 27 | let time = app.timer.elapsed_f32() * 1000.0; 28 | 29 | let mut draw = gfx.create_draw(); 30 | draw.clear(Color::BLACK); 31 | 32 | let mut count = 0.0; 33 | for (line_index, i) in (START_RADIUS..MAX_RADIUS) 34 | .step_by(RADIUS_INCREMENT as _) 35 | .enumerate() 36 | { 37 | let ti = ((time + line_index as f32 * 79.0) % 2000.0) / 2000.0; 38 | draw_flower( 39 | draw.path(), 40 | i as _, 41 | ((time % 38000.0) / 38000.0) * 2.0 * PI, 42 | count, 43 | (ti * PI * 2.0).cos(), 44 | ); 45 | count += if line_index <= MAX_LINES / 2 { 46 | 2.0 47 | } else { 48 | -2.0 49 | }; 50 | } 51 | 52 | gfx.render(&draw); 53 | } 54 | 55 | fn draw_flower( 56 | mut path_builder: DrawBuilder, 57 | radius: f32, 58 | initial_angle: f32, 59 | index: f32, 60 | amplitude_modifier: f32, 61 | ) { 62 | let segments = (2.0 * PI * radius).floor(); 63 | let mut begin = false; 64 | 65 | for i in 0..segments as usize { 66 | let n = i as f32; 67 | 68 | let period_segments = segments / PERIOD; 69 | let current_periods = n % period_segments; 70 | 71 | let radians_period = if current_periods < radius { 72 | current_periods / radius 73 | } else { 74 | 0.0 75 | }; 76 | 77 | let c_radius = radius 78 | + AMPLITUDE 79 | * (radians_period * (3.0 + index) * PI).sin() 80 | * ((radians_period * PI).sin() / 2.0 * amplitude_modifier); 81 | 82 | let radians = n / segments * 2.0 * PI + initial_angle; 83 | let x = CENTER_X + c_radius * radians.cos(); 84 | let y = CENTER_Y + c_radius * radians.sin(); 85 | 86 | if !begin { 87 | path_builder.move_to(x, y); 88 | begin = true; 89 | } 90 | 91 | path_builder.line_to(x, y); 92 | } 93 | 94 | path_builder.close().color(Color::MAGENTA).stroke(3.0); 95 | } 96 | -------------------------------------------------------------------------------- /crates/notan_extra/src/fps_limit.rs: -------------------------------------------------------------------------------- 1 | use notan_app::{assets::Assets, App, AppFlow, Graphics, Plugin}; 2 | 3 | const IS_WASM: bool = cfg!(target_arch = "wasm32"); 4 | 5 | /// Limit the App frame rate to a maximum 6 | pub struct FpsLimit { 7 | limit: u8, 8 | seconds: f64, 9 | elapsed: f64, 10 | sleep: bool, 11 | } 12 | 13 | impl FpsLimit { 14 | pub fn new(limit: u8) -> Self { 15 | let fps = limit as f64; 16 | let seconds = 1.0 / fps; 17 | Self { 18 | limit, 19 | seconds, 20 | elapsed: 0.0, 21 | sleep: true, 22 | } 23 | } 24 | 25 | /// Returns the fps limit 26 | pub fn limit(&self) -> u8 { 27 | self.limit 28 | } 29 | 30 | /// Set if the thread should wait sleeping or not (only native platforms) 31 | pub fn sleep(mut self, sleep: bool) -> Self { 32 | self.sleep = sleep; 33 | self 34 | } 35 | } 36 | 37 | impl Plugin for FpsLimit { 38 | // Wasm will run as fast as it can because the browser 39 | // will send requestAnimationFrame all the time 40 | // what we do to limit the framerate is just skipping frames 41 | fn pre_frame( 42 | &mut self, 43 | app: &mut App, 44 | _assets: &mut Assets, 45 | _gfx: &mut Graphics, 46 | ) -> Result { 47 | // If sleep is disabled then native platforms will use this too 48 | let can_skip_frame = IS_WASM || !self.sleep; 49 | if !can_skip_frame { 50 | return Ok(AppFlow::Next); 51 | } 52 | 53 | self.elapsed += app.system_timer.delta().as_secs_f64(); 54 | if self.elapsed >= self.seconds { 55 | self.elapsed -= self.seconds; 56 | Ok(AppFlow::Next) 57 | } else { 58 | Ok(AppFlow::SkipFrame) 59 | } 60 | } 61 | 62 | // On native platforms like desktop we can sleep the thread 63 | // reducing the use of the cpu. 64 | // We need spin_sleep because thread::sleep is not really accurate 65 | // and spin_sleep help us to be accurate mixing sleep + spin 66 | #[cfg(not(target_arch = "wasm32"))] 67 | fn post_frame( 68 | &mut self, 69 | app: &mut App, 70 | _assets: &mut Assets, 71 | _gfx: &mut Graphics, 72 | ) -> Result { 73 | // if sleep is disabled the pre_frame method will manage 74 | // what frames are skipped 75 | if !self.sleep { 76 | return Ok(AppFlow::Next); 77 | } 78 | 79 | // if sleep is enabled put the thread to sleep when needed to achieve the limit 80 | self.elapsed += app.system_timer.delta().as_secs_f64(); 81 | let wait = self.seconds - self.elapsed; 82 | if wait > 0.0 { 83 | spin_sleep::sleep(std::time::Duration::from_secs_f64(wait)); 84 | } 85 | 86 | self.elapsed -= self.seconds; 87 | Ok(AppFlow::Next) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /crates/notan_app/src/timer.rs: -------------------------------------------------------------------------------- 1 | use notan_utils::{Duration, Instant}; 2 | use std::collections::VecDeque; 3 | 4 | /// Helper to measure and expose application's time events 5 | #[derive(Debug, Clone)] 6 | pub struct AppTimer { 7 | init_time: Instant, 8 | last_time: Option, 9 | delta: Duration, 10 | delta_seconds: f32, 11 | elapsed: Duration, 12 | elapsed_time: f32, 13 | fps_cache: VecDeque, 14 | fps: f32, 15 | } 16 | 17 | impl Default for AppTimer { 18 | fn default() -> AppTimer { 19 | let fps = 60.0; 20 | 21 | // calculate the average fps for the last 60 frames 22 | let max_frames = 60; 23 | let mut fps_cache = VecDeque::with_capacity(max_frames); 24 | fps_cache.resize(max_frames, 1.0 / fps); 25 | 26 | AppTimer { 27 | init_time: Instant::now(), 28 | last_time: None, 29 | delta: Duration::from_secs(0), 30 | delta_seconds: 0.0, 31 | elapsed: Duration::from_secs(0), 32 | elapsed_time: 0.0, 33 | fps_cache, 34 | fps, 35 | } 36 | } 37 | } 38 | 39 | impl AppTimer { 40 | #[inline] 41 | pub(crate) fn update(&mut self) { 42 | let now = Instant::now(); 43 | 44 | if let Some(last_time) = self.last_time { 45 | self.delta = now - last_time; 46 | self.delta_seconds = self.delta.as_secs_f32(); 47 | } 48 | 49 | self.last_time = Some(now); 50 | 51 | self.elapsed = now - self.init_time; 52 | self.elapsed_time = self.elapsed.as_secs_f32(); 53 | 54 | self.fps_cache.pop_front(); 55 | self.fps_cache.push_back(self.delta_seconds); 56 | self.fps = 1.0 / (self.fps_cache.iter().sum::() / self.fps_cache.len() as f32); 57 | } 58 | 59 | /// Average frames per second (calculated using the last 60 frames) 60 | #[inline] 61 | pub fn fps(&self) -> f32 { 62 | self.fps 63 | } 64 | 65 | /// Delta time between frames 66 | #[inline] 67 | pub fn delta(&self) -> Duration { 68 | self.delta 69 | } 70 | 71 | /// Delta time between frames in seconds 72 | #[inline] 73 | pub fn delta_f32(&self) -> f32 { 74 | self.delta_seconds 75 | } 76 | 77 | /// Elapsed time since application's init 78 | #[inline] 79 | pub fn elapsed(&self) -> Duration { 80 | self.elapsed 81 | } 82 | 83 | /// Elapsed time since application's init in seconds 84 | #[inline] 85 | pub fn elapsed_f32(&self) -> f32 { 86 | self.elapsed_time 87 | } 88 | 89 | /// Application's init time 90 | #[inline] 91 | pub fn init_time(&self) -> Instant { 92 | self.init_time 93 | } 94 | 95 | /// Last frame time 96 | #[inline] 97 | pub fn last_time(&self) -> Option { 98 | self.last_time 99 | } 100 | } 101 | --------------------------------------------------------------------------------