├── .github ├── FUNDING.yml └── workflows │ └── integration.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── examples ├── README.md ├── color.rs ├── counter.rs ├── gamepad.rs ├── image.rs ├── input.rs ├── mesh.rs ├── particles.rs ├── progress.rs ├── rectangle.rs ├── snake.rs └── ui.rs ├── images ├── debug.png ├── examples │ └── particles.png ├── loading_screen │ └── progress_bar.png └── ui │ ├── button.png │ ├── button_classes.png │ ├── checkbox.png │ ├── radio.png │ ├── slider.png │ └── text.png ├── resources ├── font │ ├── Inconsolata-Regular.ttf │ └── OFL.txt └── ui.png ├── rustfmt.toml ├── src ├── debug.rs ├── debug │ ├── basic.rs │ └── null.rs ├── game.rs ├── game │ └── loop.rs ├── graphics.rs ├── graphics │ ├── backend_gfx │ │ ├── GGEZ_LICENSE │ │ ├── font.rs │ │ ├── format.rs │ │ ├── mod.rs │ │ ├── quad.rs │ │ ├── shader │ │ │ ├── quad.frag │ │ │ ├── quad.vert │ │ │ ├── triangle.frag │ │ │ └── triangle.vert │ │ ├── surface.rs │ │ ├── texture.rs │ │ ├── triangle.rs │ │ └── types.rs │ ├── backend_wgpu │ │ ├── font.rs │ │ ├── mod.rs │ │ ├── quad.rs │ │ ├── shader │ │ │ ├── quad.frag │ │ │ ├── quad.frag.spv │ │ │ ├── quad.vert │ │ │ ├── quad.vert.spv │ │ │ ├── triangle.frag │ │ │ ├── triangle.frag.spv │ │ │ ├── triangle.vert │ │ │ └── triangle.vert.spv │ │ ├── surface.rs │ │ ├── texture.rs │ │ ├── triangle.rs │ │ └── types.rs │ ├── batch.rs │ ├── canvas.rs │ ├── color.rs │ ├── font.rs │ ├── image.rs │ ├── mesh.rs │ ├── point.rs │ ├── quad.rs │ ├── rectangle.rs │ ├── shape.rs │ ├── sprite.rs │ ├── target.rs │ ├── text.rs │ ├── texture_array.rs │ ├── texture_array │ │ ├── batch.rs │ │ ├── builder.rs │ │ └── loader.rs │ ├── transformation.rs │ ├── vector.rs │ ├── window.rs │ └── window │ │ ├── cursor_icon.rs │ │ ├── event.rs │ │ ├── frame.rs │ │ └── settings.rs ├── input.rs ├── input │ ├── event.rs │ ├── gamepad.rs │ ├── gamepad │ │ └── event.rs │ ├── keyboard.rs │ ├── keyboard │ │ └── event.rs │ ├── keyboard_and_mouse.rs │ ├── mouse.rs │ ├── mouse │ │ ├── button.rs │ │ ├── event.rs │ │ └── wheel_movement.rs │ ├── window.rs │ └── window │ │ └── event.rs ├── lib.rs ├── load.rs ├── load │ ├── loading_screen.rs │ ├── loading_screen │ │ └── progress_bar.rs │ └── task.rs ├── result.rs ├── timer.rs ├── ui.rs └── ui │ ├── core.rs │ ├── core │ ├── element.rs │ ├── event.rs │ ├── hasher.rs │ ├── interface.rs │ ├── layout.rs │ ├── mouse_cursor.rs │ ├── node.rs │ ├── renderer.rs │ ├── style.rs │ └── widget.rs │ ├── renderer.rs │ ├── renderer │ ├── button.rs │ ├── checkbox.rs │ ├── image.rs │ ├── panel.rs │ ├── progress_bar.rs │ ├── radio.rs │ ├── slider.rs │ └── text.rs │ ├── widget.rs │ └── widget │ ├── button.rs │ ├── checkbox.rs │ ├── column.rs │ ├── image.rs │ ├── panel.rs │ ├── progress_bar.rs │ ├── radio.rs │ ├── row.rs │ ├── slider.rs │ └── text.rs └── tests ├── _graphics ├── mod.rs ├── test.rs └── test │ └── mesh.rs ├── diff_shaders └── graphics.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: hecrj_ 2 | -------------------------------------------------------------------------------- /.github/workflows/integration.yml: -------------------------------------------------------------------------------- 1 | name: Integration 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | runs-on: ${{ matrix.os }} 6 | strategy: 7 | matrix: 8 | os: [ubuntu-latest, windows-latest, macOS-latest] 9 | rust: [stable] 10 | backend: [opengl, vulkan] 11 | include: 12 | - os: ubuntu-latest 13 | rust: stable 14 | backend: vulkan 15 | release: yes 16 | exclude: 17 | - os: ubuntu-latest 18 | rust: beta 19 | backend: vulkan 20 | - os: windows-latest 21 | backend: vulkan 22 | - os: macOS-latest 23 | backend: vulkan 24 | steps: 25 | - uses: hecrj/setup-rust-action@v1 26 | with: 27 | rust-version: ${{ matrix.rust }} 28 | - name: Install libinput 29 | if: matrix.os == 'ubuntu-latest' 30 | run: | 31 | sudo apt-get -qq update 32 | sudo apt-get install -y libudev-dev 33 | - uses: actions/checkout@master 34 | - name: Run tests 35 | run: cargo test --verbose --features ${{ matrix.backend }} ${{ matrix.release && '--release' || '' }} 36 | 37 | diff_shaders: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@master 41 | - name: Download glslangValidator 42 | run: | 43 | mkdir glslang 44 | cd glslang 45 | wget https://github.com/KhronosGroup/glslang/releases/download/7.11.3214/glslang-master-linux-Release.zip 46 | unzip -u -q glslang-master-linux-Release.zip 47 | cd .. 48 | - name: Check differences in compiled shaders 49 | run: | 50 | export PATH=${GITHUB_WORKSPACE}/glslang/bin:$PATH 51 | glslangValidator --version 52 | chmod +x tests/diff_shaders 53 | ./tests/diff_shaders 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | tests/_graphics/models 3 | **/*.rs.bk 4 | Cargo.lock 5 | *.DS_Store 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "coffee" 3 | version = "0.4.1" 4 | authors = ["Héctor Ramón Jiménez "] 5 | edition = "2018" 6 | description = "An opinionated 2D game engine focused on simplicity, explicitness, and type-safety" 7 | license = "MIT" 8 | repository = "https://github.com/hecrj/coffee" 9 | documentation = "https://docs.rs/coffee" 10 | readme = "README.md" 11 | keywords = ["game", "engine", "2D", "graphics", "coffee"] 12 | categories = ["game-engines"] 13 | exclude = ["images/*"] 14 | 15 | [package.metadata.docs.rs] 16 | features = ["opengl", "debug"] 17 | 18 | [features] 19 | default = [] 20 | opengl = ["gfx", "gfx_core", "glutin", "gfx_device_gl", "gfx_glyph"] 21 | vulkan = ["wgpu", "wgpu_glyph", "zerocopy", "futures"] 22 | metal = ["wgpu", "wgpu_glyph", "zerocopy", "futures"] 23 | dx11 = ["wgpu", "wgpu_glyph", "zerocopy", "futures"] 24 | dx12 = ["wgpu", "wgpu_glyph", "zerocopy", "futures"] 25 | debug = [] 26 | 27 | [dependencies] 28 | image = "0.21" 29 | nalgebra = "0.18" 30 | rayon = "1.0" 31 | stretch = "0.2" 32 | twox-hash = "1.3" 33 | lyon_tessellation = "0.13" 34 | gilrs = "0.7" 35 | winit = "0.22" 36 | 37 | # gfx (OpenGL) 38 | gfx = { version = "0.18", optional = true } 39 | gfx_core = { version = "0.9", optional = true } 40 | gfx_device_gl = { version = "0.16", optional = true } 41 | gfx_glyph = { version = "0.15", optional = true } 42 | glutin = { version = "0.24", optional = true } 43 | 44 | # wgpu (Vulkan, Metal, D3D) 45 | wgpu = { version = "0.5", optional = true } 46 | wgpu_glyph = { version = "0.8", optional = true } 47 | zerocopy = { version = "0.3", optional = true } 48 | futures = { version = "0.3", optional = true } 49 | 50 | [dev-dependencies] 51 | rand = "0.6" 52 | env_logger = "0.6" 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Héctor Ramón, Coffee contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(any( 2 | feature = "opengl", 3 | feature = "vulkan", 4 | feature = "metal", 5 | feature = "dx11", 6 | feature = "dx12", 7 | )))] 8 | compile_error!( 9 | "You need to enable a graphics backend feature. \ 10 | Available options: opengl, vulkan, metal, dx11, dx12." 11 | ); 12 | 13 | fn main() {} 14 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | To try an example, clone the repository and use `cargo run`. You have to enable 4 | a graphics backend feature. For instance, if we want to run an example using 5 | OpenGL, we run: 6 | 7 | ``` 8 | cargo run --example --features opengl 9 | ``` 10 | 11 | __Coffee moves fast and the `master` branch can contain breaking changes!__ If 12 | you want to learn about a specific release, check out [the release list]. 13 | 14 | [the release list]: https://github.com/hecrj/coffee/releases 15 | 16 | ## [Particles](particles.rs) 17 | 18 | A particle gravity simulator that showcases a loading screen, input handling, 19 | and graphics interpolation with batched drawing and font rendering. Move the 20 | mouse around to attract the particles. 21 | 22 | This example renders 50k independent particles every frame. Using the 23 | `--release` flag to run the example is recommended. Additionally, you can 24 | compile it with the `debug` feature if you want to enable the built-in debug 25 | view: 26 | 27 | ``` 28 | cargo run --example particles --features vulkan,debug --release 29 | ``` 30 | 31 | [![Particles][particles]][particles_gfycat] 32 | 33 | [particles]: https://github.com/hecrj/coffee/blob/master/images/examples/particles.png?raw=true 34 | [particles_gfycat]: https://gfycat.com/beautifulseparatebeetle 35 | 36 | 37 | ## [User Interface](ui.rs) 38 | 39 | A tour showcasing the different built-in widgets available for building 40 | responsive user interfaces in Coffee. 41 | 42 | The user must interact with the different widgets in order to reach the end. 43 | 44 | ``` 45 | cargo run --example ui --features opengl,debug --release 46 | ``` 47 | 48 | [![GUI][gui_gif]][gui_gfycat] 49 | 50 | [gui_gif]: https://thumbs.gfycat.com/GloomyWeakHammerheadshark-small.gif 51 | [gui_gfycat]: https://gfycat.com/gloomyweakhammerheadshark 52 | 53 | 54 | ## [Mesh](mesh.rs) 55 | 56 | A simple mesh viewer showcasing the `Mesh` and `Shape` types. 57 | 58 | It renders different shapes and the user is able to tweak some settings using 59 | the user interface. 60 | 61 | ``` 62 | cargo run --example mesh --features opengl,debug --release 63 | ``` 64 | 65 | [![GUI][mesh_gif]][mesh_gfycat] 66 | 67 | [mesh_gif]: https://thumbs.gfycat.com/AcademicGlossyKingfisher-small.gif 68 | [mesh_gfycat]: https://gfycat.com/academicglossykingfisher 69 | -------------------------------------------------------------------------------- /examples/color.rs: -------------------------------------------------------------------------------- 1 | use coffee::graphics::{ 2 | Color, Font, Frame, Image, Point, Quad, Rectangle, Text, Window, 3 | WindowSettings, 4 | }; 5 | use coffee::load::{loading_screen::ProgressBar, Join, Task}; 6 | use coffee::{Game, Result, Timer}; 7 | 8 | fn main() -> Result<()> { 9 | Colors::run(WindowSettings { 10 | title: String::from("Color - Coffee"), 11 | size: (1280, 1024), 12 | resizable: false, 13 | fullscreen: false, 14 | maximized: false, 15 | }) 16 | } 17 | 18 | struct Colors { 19 | palette: Image, 20 | font: Font, 21 | } 22 | 23 | impl Colors { 24 | const PRUSSIAN_BLUE: Color = Color { 25 | r: 0.0, 26 | g: 0.1922, 27 | b: 0.3255, 28 | a: 1.0, 29 | }; 30 | 31 | fn load() -> Task { 32 | ( 33 | Task::using_gpu(|gpu| { 34 | Image::from_colors(gpu, &[Self::PRUSSIAN_BLUE]) 35 | }), 36 | Font::load_from_bytes(include_bytes!( 37 | "../resources/font/Inconsolata-Regular.ttf" 38 | )), 39 | ) 40 | .join() 41 | .map(|(palette, font)| Colors { palette, font }) 42 | } 43 | } 44 | 45 | impl Game for Colors { 46 | type Input = (); 47 | type LoadingScreen = ProgressBar; 48 | 49 | fn load(_window: &Window) -> Task { 50 | Task::stage("Loading view...", Colors::load()) 51 | } 52 | 53 | fn draw(&mut self, frame: &mut Frame, _timer: &Timer) { 54 | frame.clear(Color::new(0.5, 0.5, 0.5, 1.0)); 55 | 56 | let target = &mut frame.as_target(); 57 | 58 | self.palette.draw( 59 | Quad { 60 | source: Rectangle { 61 | x: 0.0, 62 | y: 0.0, 63 | width: 1.0, 64 | height: 1.0, 65 | }, 66 | position: Point::new(0.0, 0.0), 67 | size: (500.0, 500.0), 68 | }, 69 | target, 70 | ); 71 | 72 | self.font.add(Text { 73 | content: "Prussian blue", 74 | position: Point::new(20.0, 500.0), 75 | size: 50.0, 76 | color: Self::PRUSSIAN_BLUE, 77 | ..Text::default() 78 | }); 79 | 80 | self.font.draw(target); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /examples/counter.rs: -------------------------------------------------------------------------------- 1 | use coffee::graphics::{ 2 | Color, Frame, HorizontalAlignment, VerticalAlignment, Window, 3 | WindowSettings, 4 | }; 5 | use coffee::load::Task; 6 | use coffee::ui::{ 7 | button, Align, Button, Column, Element, Justify, Renderer, Text, 8 | UserInterface, 9 | }; 10 | use coffee::{Game, Result, Timer}; 11 | 12 | pub fn main() -> Result<()> { 13 | ::run(WindowSettings { 14 | title: String::from("Counter - Coffee"), 15 | size: (1280, 1024), 16 | resizable: false, 17 | fullscreen: false, 18 | maximized: false, 19 | }) 20 | } 21 | 22 | struct Counter { 23 | value: i32, 24 | increment_button: button::State, 25 | decrement_button: button::State, 26 | } 27 | 28 | impl Game for Counter { 29 | type Input = (); 30 | type LoadingScreen = (); 31 | 32 | fn load(_window: &Window) -> Task { 33 | Task::succeed(|| Counter { 34 | value: 0, 35 | increment_button: button::State::new(), 36 | decrement_button: button::State::new(), 37 | }) 38 | } 39 | 40 | fn draw(&mut self, frame: &mut Frame, _timer: &Timer) { 41 | frame.clear(Color { 42 | r: 0.3, 43 | g: 0.3, 44 | b: 0.6, 45 | a: 1.0, 46 | }); 47 | } 48 | } 49 | 50 | #[derive(Debug, Clone, Copy)] 51 | pub enum Message { 52 | IncrementPressed, 53 | DecrementPressed, 54 | } 55 | 56 | impl UserInterface for Counter { 57 | type Message = Message; 58 | type Renderer = Renderer; 59 | 60 | fn react(&mut self, message: Message, _window: &mut Window) { 61 | match message { 62 | Message::IncrementPressed => { 63 | self.value += 1; 64 | } 65 | Message::DecrementPressed => { 66 | self.value -= 1; 67 | } 68 | } 69 | } 70 | 71 | fn layout(&mut self, window: &Window) -> Element { 72 | Column::new() 73 | .width(window.width() as u32) 74 | .height(window.height() as u32) 75 | .align_items(Align::Center) 76 | .justify_content(Justify::Center) 77 | .spacing(20) 78 | .push( 79 | Button::new(&mut self.increment_button, "+") 80 | .on_press(Message::IncrementPressed), 81 | ) 82 | .push( 83 | Text::new(&self.value.to_string()) 84 | .size(50) 85 | .height(60) 86 | .horizontal_alignment(HorizontalAlignment::Center) 87 | .vertical_alignment(VerticalAlignment::Center), 88 | ) 89 | .push( 90 | Button::new(&mut self.decrement_button, "-") 91 | .on_press(Message::DecrementPressed), 92 | ) 93 | .into() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /examples/gamepad.rs: -------------------------------------------------------------------------------- 1 | //! An example that showcases gamepad events 2 | use coffee::graphics::{Color, Frame, Window, WindowSettings}; 3 | use coffee::input::{self, gamepad, Input}; 4 | use coffee::load::Task; 5 | use coffee::ui::{ 6 | Align, Column, Element, Justify, Renderer, Text, UserInterface, 7 | }; 8 | use coffee::{Game, Result, Timer}; 9 | 10 | fn main() -> Result<()> { 11 | ::run(WindowSettings { 12 | title: String::from("Gamepad - Coffee"), 13 | size: (1280, 1024), 14 | resizable: false, 15 | fullscreen: false, 16 | maximized: false, 17 | }) 18 | } 19 | 20 | struct Gamepad { 21 | last_event: Option, 22 | } 23 | 24 | impl Input for Gamepad { 25 | fn new() -> Gamepad { 26 | Gamepad { last_event: None } 27 | } 28 | 29 | fn update(&mut self, event: input::Event) { 30 | match event { 31 | input::Event::Gamepad { event, .. } => { 32 | self.last_event = Some(event); 33 | } 34 | _ => {} 35 | } 36 | } 37 | 38 | fn clear(&mut self) {} 39 | } 40 | 41 | struct GamepadExample { 42 | last_event: String, 43 | } 44 | 45 | impl Game for GamepadExample { 46 | type Input = Gamepad; 47 | type LoadingScreen = (); 48 | 49 | fn load(_window: &Window) -> Task { 50 | Task::succeed(|| GamepadExample { 51 | last_event: "None".to_string(), 52 | }) 53 | } 54 | 55 | fn interact(&mut self, gamepad: &mut Gamepad, _window: &mut Window) { 56 | if let Some(event) = gamepad.last_event { 57 | self.last_event = format!("{:#?}", event); 58 | } 59 | } 60 | 61 | fn draw(&mut self, frame: &mut Frame, _timer: &Timer) { 62 | frame.clear(Color { 63 | r: 0.3, 64 | g: 0.3, 65 | b: 0.6, 66 | a: 1.0, 67 | }); 68 | } 69 | } 70 | 71 | impl UserInterface for GamepadExample { 72 | type Message = (); 73 | type Renderer = Renderer; 74 | 75 | fn react(&mut self, _msg: (), _window: &mut Window) {} 76 | 77 | fn layout(&mut self, window: &Window) -> Element<()> { 78 | Column::new() 79 | .width(window.width() as u32) 80 | .height(window.height() as u32) 81 | .align_items(Align::Center) 82 | .justify_content(Justify::Center) 83 | .push( 84 | Column::new() 85 | .max_width(500) 86 | .spacing(20) 87 | .push(Text::new("Last gamepad event:").size(30)) 88 | .push(Text::new(&self.last_event)), 89 | ) 90 | .into() 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /examples/image.rs: -------------------------------------------------------------------------------- 1 | use coffee::graphics::{ 2 | self, Color, Frame, HorizontalAlignment, VerticalAlignment, Window, 3 | WindowSettings, 4 | }; 5 | use coffee::load::Task; 6 | use coffee::ui::{ 7 | Align, Column, Element, Image, Justify, Renderer, Text, UserInterface, 8 | }; 9 | use coffee::{Game, Result, Timer}; 10 | 11 | pub fn main() -> Result<()> { 12 | ::run(WindowSettings { 13 | title: String::from("ImageScreen - Coffee"), 14 | size: (1280, 1024), 15 | resizable: false, 16 | fullscreen: false, 17 | maximized: false, 18 | }) 19 | } 20 | 21 | struct ImageScreen { 22 | image: graphics::Image, 23 | } 24 | 25 | impl Game for ImageScreen { 26 | type Input = (); 27 | type LoadingScreen = (); 28 | 29 | fn load(_window: &Window) -> Task { 30 | graphics::Image::load("resources/ui.png") 31 | .map(|image| ImageScreen { image }) 32 | } 33 | 34 | fn draw(&mut self, frame: &mut Frame, _timer: &Timer) { 35 | frame.clear(Color { 36 | r: 0.3, 37 | g: 0.3, 38 | b: 0.6, 39 | a: 1.0, 40 | }); 41 | } 42 | } 43 | 44 | impl UserInterface for ImageScreen { 45 | type Message = (); 46 | type Renderer = Renderer; 47 | 48 | fn react(&mut self, _message: (), _window: &mut Window) {} 49 | 50 | fn layout(&mut self, window: &Window) -> Element<()> { 51 | Column::new() 52 | .width(window.width() as u32) 53 | .height(window.height() as u32) 54 | .align_items(Align::Center) 55 | .justify_content(Justify::Center) 56 | .spacing(20) 57 | .push( 58 | Text::new("This is an image") 59 | .size(50) 60 | .height(60) 61 | .horizontal_alignment(HorizontalAlignment::Center) 62 | .vertical_alignment(VerticalAlignment::Center), 63 | ) 64 | .push(Image::new(&self.image).height(250)) 65 | .into() 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/progress.rs: -------------------------------------------------------------------------------- 1 | use coffee::graphics::{ 2 | Color, Frame, HorizontalAlignment, VerticalAlignment, Window, 3 | WindowSettings, 4 | }; 5 | use coffee::load::Task; 6 | use coffee::ui::{ 7 | Align, Column, Element, Justify, ProgressBar, Renderer, Text, UserInterface, 8 | }; 9 | use coffee::{Game, Result, Timer}; 10 | 11 | pub fn main() -> Result<()> { 12 | ::run(WindowSettings { 13 | title: String::from("Progress - Coffee"), 14 | size: (1280, 1024), 15 | resizable: false, 16 | fullscreen: false, 17 | maximized: false, 18 | }) 19 | } 20 | 21 | struct Progress { 22 | value: f32, 23 | } 24 | 25 | impl Game for Progress { 26 | type Input = (); 27 | type LoadingScreen = (); 28 | 29 | fn load(_window: &Window) -> Task { 30 | Task::succeed(|| Progress { value: 0.0 }) 31 | } 32 | 33 | fn draw(&mut self, frame: &mut Frame, timer: &Timer) { 34 | frame.clear(Color { 35 | r: 0.3, 36 | g: 0.3, 37 | b: 0.6, 38 | a: 1.0, 39 | }); 40 | 41 | if timer.has_ticked() { 42 | if self.value >= 1.0 { 43 | self.value = 0.0; 44 | } 45 | self.value += 0.002; 46 | } 47 | } 48 | } 49 | 50 | impl UserInterface for Progress { 51 | type Message = (); 52 | type Renderer = Renderer; 53 | 54 | fn react(&mut self, _message: (), _window: &mut Window) {} 55 | 56 | fn layout(&mut self, window: &Window) -> Element<()> { 57 | Column::new() 58 | .width(window.width() as u32) 59 | .height(window.height() as u32) 60 | .align_items(Align::Center) 61 | .justify_content(Justify::Center) 62 | .spacing(20) 63 | .push( 64 | Text::new(&format!("{:.0}%", self.value * 100.0)) 65 | .size(50) 66 | .height(60) 67 | .horizontal_alignment(HorizontalAlignment::Center) 68 | .vertical_alignment(VerticalAlignment::Center), 69 | ) 70 | .push(ProgressBar::new(self.value).width(400)) 71 | .into() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /examples/rectangle.rs: -------------------------------------------------------------------------------- 1 | use coffee::graphics::{ 2 | Color, Frame, Mesh, Rectangle, Shape, Window, WindowSettings, 3 | }; 4 | use coffee::load::Task; 5 | use coffee::{Game, Timer}; 6 | 7 | fn main() -> coffee::Result<()> { 8 | Example::run(WindowSettings { 9 | title: String::from("Rectangle - Coffee"), 10 | size: (1280, 1024), 11 | resizable: true, 12 | fullscreen: false, 13 | maximized: false, 14 | }) 15 | } 16 | 17 | struct Example; 18 | 19 | impl Game for Example { 20 | type Input = (); 21 | type LoadingScreen = (); 22 | 23 | fn load(_window: &Window) -> Task { 24 | Task::succeed(|| Example) 25 | } 26 | 27 | fn draw(&mut self, frame: &mut Frame, _timer: &Timer) { 28 | frame.clear(Color::BLACK); 29 | let mut mesh = Mesh::new(); 30 | mesh.fill( 31 | Shape::Rectangle(Rectangle { 32 | x: 0.0, 33 | y: 0.0, 34 | width: 200.0, 35 | height: 100.0, 36 | }), 37 | Color::WHITE, 38 | ); 39 | mesh.draw(&mut frame.as_target()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /images/debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hecrj/coffee/5bb04cf8483188582a2adb8c36567242af86f49d/images/debug.png -------------------------------------------------------------------------------- /images/examples/particles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hecrj/coffee/5bb04cf8483188582a2adb8c36567242af86f49d/images/examples/particles.png -------------------------------------------------------------------------------- /images/loading_screen/progress_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hecrj/coffee/5bb04cf8483188582a2adb8c36567242af86f49d/images/loading_screen/progress_bar.png -------------------------------------------------------------------------------- /images/ui/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hecrj/coffee/5bb04cf8483188582a2adb8c36567242af86f49d/images/ui/button.png -------------------------------------------------------------------------------- /images/ui/button_classes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hecrj/coffee/5bb04cf8483188582a2adb8c36567242af86f49d/images/ui/button_classes.png -------------------------------------------------------------------------------- /images/ui/checkbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hecrj/coffee/5bb04cf8483188582a2adb8c36567242af86f49d/images/ui/checkbox.png -------------------------------------------------------------------------------- /images/ui/radio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hecrj/coffee/5bb04cf8483188582a2adb8c36567242af86f49d/images/ui/radio.png -------------------------------------------------------------------------------- /images/ui/slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hecrj/coffee/5bb04cf8483188582a2adb8c36567242af86f49d/images/ui/slider.png -------------------------------------------------------------------------------- /images/ui/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hecrj/coffee/5bb04cf8483188582a2adb8c36567242af86f49d/images/ui/text.png -------------------------------------------------------------------------------- /resources/font/Inconsolata-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hecrj/coffee/5bb04cf8483188582a2adb8c36567242af86f49d/resources/font/Inconsolata-Regular.ttf -------------------------------------------------------------------------------- /resources/font/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright 2006 The Inconsolata Project Authors 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /resources/ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hecrj/coffee/5bb04cf8483188582a2adb8c36567242af86f49d/resources/ui.png -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width=80 2 | -------------------------------------------------------------------------------- /src/debug.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(any(debug_assertions, feature = "debug")))] 2 | mod null; 3 | 4 | #[cfg(any(debug_assertions, feature = "debug"))] 5 | mod basic; 6 | 7 | #[cfg(not(any(debug_assertions, feature = "debug")))] 8 | pub use null::Debug; 9 | 10 | #[cfg(any(debug_assertions, feature = "debug"))] 11 | pub use basic::Debug; 12 | -------------------------------------------------------------------------------- /src/debug/null.rs: -------------------------------------------------------------------------------- 1 | use crate::graphics; 2 | 3 | // Null debug implementation 4 | #[allow(missing_debug_implementations)] 5 | #[allow(missing_docs)] 6 | pub struct Debug {} 7 | 8 | impl Debug { 9 | pub(crate) fn new(_gpu: &mut graphics::Gpu) -> Self { 10 | Self {} 11 | } 12 | 13 | pub(crate) fn loading_started(&mut self) {} 14 | pub(crate) fn loading_finished(&mut self) {} 15 | pub(crate) fn frame_started(&mut self) {} 16 | pub(crate) fn frame_finished(&mut self) {} 17 | pub(crate) fn interact_started(&mut self) {} 18 | pub(crate) fn interact_finished(&mut self) {} 19 | pub(crate) fn update_started(&mut self) {} 20 | pub(crate) fn update_finished(&mut self) {} 21 | pub(crate) fn draw_started(&mut self) {} 22 | pub(crate) fn draw_finished(&mut self) {} 23 | pub(crate) fn ui_started(&mut self) {} 24 | pub(crate) fn ui_finished(&mut self) {} 25 | pub(crate) fn debug_started(&mut self) {} 26 | pub(crate) fn debug_finished(&mut self) {} 27 | 28 | #[allow(dead_code)] 29 | pub(crate) fn toggle(&mut self) {} 30 | 31 | pub(crate) fn is_enabled(&self) -> bool { 32 | false 33 | } 34 | 35 | #[allow(missing_docs)] 36 | pub fn draw(&mut self, _frame: &mut graphics::Frame<'_>) {} 37 | } 38 | -------------------------------------------------------------------------------- /src/graphics.rs: -------------------------------------------------------------------------------- 1 | //! Draw your game with an explicit 2D graphics API. 2 | //! 3 | //! Graphics in Coffee focus on simplicity while __reducing global state__. 4 | //! Operations like matrix transformations, off-screen rendering and draw calls 5 | //! have always an explicit scope. In Coffee, you do not have to remember to pop 6 | //! a transformation from the matrix stack, reset the render target, reset the 7 | //! screen coordinates, etc. There are no global functions! 8 | //! 9 | //! # Basic concepts 10 | //! The graphics module revolves around three concepts: [graphics processors], 11 | //! [targets], and [resources]. 12 | //! 13 | //! ## Graphics processors 14 | //! A [`Gpu`] represents a link between your game and a graphics processor. It 15 | //! is necessary to perform any kind of graphical operation, like loading 16 | //! resources and drawing. 17 | //! 18 | //! As of now, you will only have one [`Gpu`] available at a given time. 19 | //! However, in the future, the graphics module may allow recording graphical 20 | //! operations concurrently. 21 | //! 22 | //! ## Targets 23 | //! A [`Target`] represents a drawable target on a specific [`Gpu`]. A 24 | //! [`Transformation`] can be applied to them. 25 | //! 26 | //! Any kind of draw operation needs an explicit [`Target`]. For example, 27 | //! [`Image::draw`] needs a reference to a [`Target`] as the last argument. 28 | //! 29 | //! Currently, there are two ways to obtain a [`Target`]: either from a 30 | //! [`Frame`] or a [`Canvas`], using the `as_target` method. 31 | //! 32 | //! ## Resources 33 | //! A resource is the source of some drawable object. In Coffee, there is no 34 | //! `Resource` or `Drawable` type/trait. Resources are represented by different 35 | //! types like [`Image`], [`Font`], [`TextureArray`], etc. 36 | //! 37 | //! # Getting started 38 | //! You should probably start your [`Game::draw`] implementation by clearing 39 | //! the provided [`Frame`]: 40 | //! 41 | //! ``` 42 | //! use coffee::graphics::{Color, Frame, Window}; 43 | //! use coffee::{Game, Timer}; 44 | //! # use coffee::Result; 45 | //! # use coffee::graphics::Gpu; 46 | //! # use coffee::load::Task; 47 | //! # 48 | //! # struct MyGame; 49 | //! 50 | //! impl Game for MyGame { 51 | //! # type Input = (); 52 | //! # type LoadingScreen = (); 53 | //! # 54 | //! # fn load(window: &Window) -> Task { 55 | //! # Task::succeed(|| MyGame) 56 | //! # } 57 | //! # 58 | //! // ... 59 | //! 60 | //! fn draw(&mut self, frame: &mut Frame, _timer: &Timer) { 61 | //! frame.clear(Color::BLACK); 62 | //! 63 | //! // Use your resources here... 64 | //! // self.image.draw(Sprite { ... }, &mut frame.as_target()); 65 | //! } 66 | //! } 67 | //! ``` 68 | //! 69 | //! You can load your resources during [`Game::load`]. Check out the different 70 | //! types in this module to get a basic understanding of which kind of resources 71 | //! are supported. 72 | //! 73 | //! [graphics processors]: #graphics-processors 74 | //! [targets]: #targets 75 | //! [resources]: #resources 76 | //! [`Gpu`]: struct.Gpu.html 77 | //! [`Target`]: struct.Target.html 78 | //! [`Transformation`]: struct.Transformation.html 79 | //! [`Frame`]: struct.Frame.html 80 | //! [`Canvas`]: struct.Canvas.html 81 | //! [`Image`]: struct.Image.html 82 | //! [`Image::draw`]: struct.Image.html#method.draw 83 | //! [`TextureArray`]: texture_array/struct.TextureArray.html 84 | //! [`Font`]: struct.Font.html 85 | //! [`Game::draw`]: ../trait.Game.html#tymethod.draw 86 | //! [`Game::load`]: ../trait.Game.html#tymethod.load 87 | 88 | #[cfg(feature = "opengl")] 89 | mod backend_gfx; 90 | #[cfg(feature = "opengl")] 91 | use backend_gfx as gpu; 92 | 93 | #[cfg(any( 94 | feature = "vulkan", 95 | feature = "metal", 96 | feature = "dx11", 97 | feature = "dx12", 98 | ))] 99 | mod backend_wgpu; 100 | #[cfg(any( 101 | feature = "vulkan", 102 | feature = "metal", 103 | feature = "dx11", 104 | feature = "dx12", 105 | ))] 106 | use backend_wgpu as gpu; 107 | 108 | mod batch; 109 | mod canvas; 110 | mod color; 111 | mod font; 112 | mod image; 113 | mod mesh; 114 | mod point; 115 | mod quad; 116 | mod rectangle; 117 | mod shape; 118 | mod sprite; 119 | mod target; 120 | mod text; 121 | mod transformation; 122 | mod vector; 123 | 124 | pub mod texture_array; 125 | pub(crate) mod window; 126 | 127 | pub use self::image::Image; 128 | pub use batch::Batch; 129 | pub use canvas::Canvas; 130 | pub use color::Color; 131 | pub use font::Font; 132 | pub use gpu::Gpu; 133 | pub use mesh::Mesh; 134 | pub use point::Point; 135 | pub use quad::{IntoQuad, Quad}; 136 | pub use rectangle::Rectangle; 137 | pub use shape::Shape; 138 | pub use sprite::Sprite; 139 | pub use target::Target; 140 | pub use text::{HorizontalAlignment, Text, VerticalAlignment}; 141 | pub use texture_array::TextureArray; 142 | pub use transformation::Transformation; 143 | pub use vector::Vector; 144 | pub use window::{CursorIcon, Frame, Settings as WindowSettings, Window}; 145 | -------------------------------------------------------------------------------- /src/graphics/backend_gfx/GGEZ_LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2019 ggez-dev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/graphics/backend_gfx/font.rs: -------------------------------------------------------------------------------- 1 | use gfx_device_gl as gl; 2 | use gfx_glyph::GlyphCruncher; 3 | 4 | use crate::graphics::gpu::{TargetView, Transformation}; 5 | use crate::graphics::{HorizontalAlignment, Text, Vector, VerticalAlignment}; 6 | 7 | pub struct Font { 8 | glyphs: gfx_glyph::GlyphBrush<'static, gl::Resources, gl::Factory>, 9 | } 10 | 11 | impl Font { 12 | pub fn from_bytes(factory: &mut gl::Factory, bytes: &'static [u8]) -> Font { 13 | Font { 14 | glyphs: gfx_glyph::GlyphBrushBuilder::using_font_bytes(bytes) 15 | .depth_test(gfx::preset::depth::PASS_TEST) 16 | .texture_filter_method(gfx::texture::FilterMethod::Scale) 17 | .build(factory.clone()), 18 | } 19 | } 20 | 21 | pub fn add(&mut self, text: Text<'_>) { 22 | let section: gfx_glyph::Section<'_> = text.into(); 23 | self.glyphs.queue(section); 24 | } 25 | 26 | pub fn measure(&mut self, text: Text<'_>) -> (f32, f32) { 27 | let section: gfx_glyph::Section<'_> = text.into(); 28 | let bounds = self.glyphs.glyph_bounds(section); 29 | 30 | match bounds { 31 | Some(bounds) => (bounds.width(), bounds.height()), 32 | None => (0.0, 0.0), 33 | } 34 | } 35 | 36 | pub fn draw( 37 | &mut self, 38 | encoder: &mut gfx::Encoder, 39 | target: &TargetView, 40 | transformation: Transformation, 41 | ) { 42 | let typed_target: gfx::handle::RenderTargetView< 43 | gl::Resources, 44 | gfx::format::Srgba8, 45 | > = gfx::memory::Typed::new(target.clone()); 46 | 47 | self.glyphs 48 | .use_queue() 49 | .transform( 50 | Transformation::nonuniform_scale(Vector::new(1.0, -1.0)) 51 | * transformation, 52 | ) 53 | .draw(encoder, &typed_target) 54 | .expect("Font draw"); 55 | } 56 | } 57 | 58 | impl<'a> From> for gfx_glyph::Section<'a> { 59 | fn from(text: Text<'a>) -> gfx_glyph::Section<'a> { 60 | let x = match text.horizontal_alignment { 61 | HorizontalAlignment::Left => text.position.x, 62 | HorizontalAlignment::Center => { 63 | text.position.x + text.bounds.0 / 2.0 64 | } 65 | HorizontalAlignment::Right => text.position.x + text.bounds.0, 66 | }; 67 | 68 | let y = match text.vertical_alignment { 69 | VerticalAlignment::Top => text.position.y, 70 | VerticalAlignment::Center => text.position.y + text.bounds.1 / 2.0, 71 | VerticalAlignment::Bottom => text.position.y + text.bounds.1, 72 | }; 73 | 74 | gfx_glyph::Section { 75 | text: &text.content, 76 | screen_position: (x, y), 77 | scale: gfx_glyph::Scale { 78 | x: text.size, 79 | y: text.size, 80 | }, 81 | color: text.color.into_linear(), 82 | bounds: text.bounds, 83 | layout: gfx_glyph::Layout::default() 84 | .h_align(text.horizontal_alignment.into()) 85 | .v_align(text.vertical_alignment.into()), 86 | ..Default::default() 87 | } 88 | } 89 | } 90 | 91 | impl From for gfx_glyph::HorizontalAlign { 92 | fn from(alignment: HorizontalAlignment) -> gfx_glyph::HorizontalAlign { 93 | match alignment { 94 | HorizontalAlignment::Left => gfx_glyph::HorizontalAlign::Left, 95 | HorizontalAlignment::Center => gfx_glyph::HorizontalAlign::Center, 96 | HorizontalAlignment::Right => gfx_glyph::HorizontalAlign::Right, 97 | } 98 | } 99 | } 100 | 101 | impl From for gfx_glyph::VerticalAlign { 102 | fn from(alignment: VerticalAlignment) -> gfx_glyph::VerticalAlign { 103 | match alignment { 104 | VerticalAlignment::Top => gfx_glyph::VerticalAlign::Top, 105 | VerticalAlignment::Center => gfx_glyph::VerticalAlign::Center, 106 | VerticalAlignment::Bottom => gfx_glyph::VerticalAlign::Bottom, 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/graphics/backend_gfx/format.rs: -------------------------------------------------------------------------------- 1 | pub const COLOR: gfx::format::Format = gfx::format::Format( 2 | gfx::format::SurfaceType::R8_G8_B8_A8, 3 | gfx::format::ChannelType::Unorm, 4 | ); 5 | 6 | pub const DEPTH: gfx::format::Format = gfx::format::Format( 7 | gfx::format::SurfaceType::D24_S8, 8 | gfx::format::ChannelType::Unorm, 9 | ); 10 | 11 | pub type View = ::View; 12 | pub type Surface = ::Surface; 13 | pub type Channel = ::Channel; 14 | -------------------------------------------------------------------------------- /src/graphics/backend_gfx/mod.rs: -------------------------------------------------------------------------------- 1 | mod font; 2 | mod format; 3 | mod quad; 4 | mod surface; 5 | pub mod texture; 6 | mod triangle; 7 | mod types; 8 | 9 | pub use font::Font; 10 | pub use quad::Quad; 11 | pub use surface::Surface; 12 | pub use texture::Texture; 13 | pub use triangle::Vertex; 14 | pub use types::TargetView; 15 | 16 | use gfx::{self, Device}; 17 | use gfx_device_gl as gl; 18 | 19 | use crate::graphics::{Color, Transformation}; 20 | use crate::Result; 21 | 22 | /// A link between your game and a graphics processor. 23 | /// 24 | /// It is necessary to perform any kind of graphical operation, like loading 25 | /// resources and drawing. 26 | /// 27 | /// A [`Gpu`] can be obtained from a [`Window`] or a [`Frame`]. 28 | /// 29 | /// [`Gpu`]: struct.Gpu.html 30 | /// [`Window`]: struct.Window.html 31 | /// [`Frame`]: struct.Frame.html 32 | #[allow(missing_debug_implementations)] 33 | pub struct Gpu { 34 | device: gl::Device, 35 | factory: gl::Factory, 36 | encoder: gfx::Encoder, 37 | triangle_pipeline: triangle::Pipeline, 38 | quad_pipeline: quad::Pipeline, 39 | } 40 | 41 | impl Gpu { 42 | pub(super) fn for_window( 43 | builder: winit::window::WindowBuilder, 44 | events_loop: &winit::event_loop::EventLoop<()>, 45 | ) -> Result<(Gpu, Surface)> { 46 | let (surface, device, mut factory) = 47 | Surface::new(builder, events_loop)?; 48 | 49 | let mut encoder: gfx::Encoder = 50 | factory.create_command_buffer().into(); 51 | 52 | let triangle_pipeline = triangle::Pipeline::new( 53 | &mut factory, 54 | &mut encoder, 55 | surface.target(), 56 | ); 57 | 58 | let quad_pipeline = 59 | quad::Pipeline::new(&mut factory, &mut encoder, surface.target()); 60 | 61 | Ok(( 62 | Gpu { 63 | device, 64 | factory, 65 | encoder, 66 | triangle_pipeline, 67 | quad_pipeline, 68 | }, 69 | surface, 70 | )) 71 | } 72 | 73 | pub(super) fn clear(&mut self, view: &TargetView, color: Color) { 74 | let typed_render_target: gfx::handle::RenderTargetView< 75 | gl::Resources, 76 | gfx::format::Srgba8, 77 | > = gfx::memory::Typed::new(view.clone()); 78 | 79 | self.encoder 80 | .clear(&typed_render_target, color.into_linear()) 81 | } 82 | 83 | fn flush(&mut self) { 84 | self.encoder.flush(&mut self.device); 85 | } 86 | 87 | fn cleanup(&mut self) { 88 | self.device.cleanup(); 89 | } 90 | 91 | pub(super) fn upload_texture( 92 | &mut self, 93 | image: &image::DynamicImage, 94 | ) -> Texture { 95 | Texture::new(&mut self.factory, image) 96 | } 97 | 98 | pub(super) fn upload_texture_array( 99 | &mut self, 100 | layers: &[image::DynamicImage], 101 | ) -> Texture { 102 | Texture::new_array(&mut self.factory, layers) 103 | } 104 | 105 | pub(super) fn create_drawable_texture( 106 | &mut self, 107 | width: u16, 108 | height: u16, 109 | ) -> texture::Drawable { 110 | texture::Drawable::new(&mut self.factory, width, height) 111 | } 112 | 113 | pub(super) fn read_drawable_texture_pixels( 114 | &mut self, 115 | drawable: &texture::Drawable, 116 | ) -> image::DynamicImage { 117 | self.flush(); 118 | 119 | drawable.read_pixels(&mut self.device, &mut self.factory) 120 | } 121 | 122 | pub(super) fn upload_font(&mut self, bytes: &'static [u8]) -> Font { 123 | Font::from_bytes(&mut self.factory, bytes) 124 | } 125 | 126 | pub(super) fn draw_triangles( 127 | &mut self, 128 | vertices: &[Vertex], 129 | indices: &[u32], 130 | view: &TargetView, 131 | transformation: &Transformation, 132 | ) { 133 | self.triangle_pipeline.draw( 134 | &mut self.factory, 135 | &mut self.encoder, 136 | vertices, 137 | indices, 138 | transformation, 139 | view, 140 | ); 141 | } 142 | 143 | pub(super) fn draw_texture_quads( 144 | &mut self, 145 | texture: &Texture, 146 | instances: &[Quad], 147 | view: &TargetView, 148 | transformation: &Transformation, 149 | ) { 150 | self.quad_pipeline.bind_texture(texture); 151 | 152 | self.quad_pipeline.draw_textured( 153 | &mut self.encoder, 154 | instances, 155 | transformation, 156 | view, 157 | ); 158 | } 159 | 160 | pub(super) fn draw_font( 161 | &mut self, 162 | font: &mut Font, 163 | target: &TargetView, 164 | transformation: Transformation, 165 | ) { 166 | font.draw(&mut self.encoder, target, transformation); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/graphics/backend_gfx/shader/quad.frag: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | uniform sampler2DArray t_Texture; 4 | flat in uint v_Layer; 5 | in vec2 v_Uv; 6 | 7 | out vec4 Target0; 8 | 9 | layout (std140) uniform Globals { 10 | mat4 u_MVP; 11 | }; 12 | 13 | void main() { 14 | Target0 = texture(t_Texture, vec3(v_Uv, v_Layer)); 15 | } 16 | -------------------------------------------------------------------------------- /src/graphics/backend_gfx/shader/quad.vert: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | in vec2 a_Pos; 4 | 5 | in vec4 a_Src; 6 | in vec2 a_Scale; 7 | in vec2 a_Translation; 8 | in uint t_Layer; 9 | 10 | layout (std140) uniform Globals { 11 | mat4 u_MVP; 12 | }; 13 | 14 | out vec2 v_Uv; 15 | flat out uint v_Layer; 16 | 17 | void main() { 18 | v_Uv = a_Pos * a_Src.zw + a_Src.xy; 19 | v_Layer = t_Layer; 20 | 21 | mat4 instance_transform = mat4( 22 | vec4(a_Scale.x, 0.0, 0.0, 0.0), 23 | vec4(0.0, a_Scale.y, 0.0, 0.0), 24 | vec4(0.0, 0.0, 1.0, 0.0), 25 | vec4(a_Translation, 0.0, 1.0) 26 | ); 27 | 28 | vec4 position = u_MVP * instance_transform * vec4(a_Pos, 0.0, 1.0); 29 | 30 | gl_Position = position; 31 | } 32 | -------------------------------------------------------------------------------- /src/graphics/backend_gfx/shader/triangle.frag: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | in vec4 v_Color; 4 | 5 | out vec4 Target0; 6 | 7 | layout (std140) uniform Globals { 8 | mat4 u_MVP; 9 | }; 10 | 11 | void main() { 12 | Target0 = v_Color; 13 | } 14 | -------------------------------------------------------------------------------- /src/graphics/backend_gfx/shader/triangle.vert: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | in vec2 a_Pos; 4 | in vec4 a_Color; 5 | 6 | layout (std140) uniform Globals { 7 | mat4 u_MVP; 8 | }; 9 | 10 | out vec4 v_Color; 11 | 12 | void main() { 13 | v_Color = a_Color; 14 | 15 | gl_Position = u_MVP * vec4(a_Pos, 0.0, 1.0); 16 | } 17 | -------------------------------------------------------------------------------- /src/graphics/backend_gfx/surface.rs: -------------------------------------------------------------------------------- 1 | use gfx_device_gl as gl; 2 | 3 | use super::{format, Gpu, TargetView}; 4 | use crate::{Error, Result}; 5 | 6 | pub struct Surface { 7 | context: glutin::WindowedContext, 8 | target: TargetView, 9 | } 10 | 11 | impl Surface { 12 | pub(super) fn new( 13 | builder: winit::window::WindowBuilder, 14 | event_loop: &winit::event_loop::EventLoop<()>, 15 | ) -> Result<(Self, gl::Device, gl::Factory)> { 16 | let gl_builder = glutin::ContextBuilder::new() 17 | .with_gl(glutin::GlRequest::Latest) 18 | .with_gl_profile(glutin::GlProfile::Core) 19 | .with_multisampling(0) 20 | // 24 color bits, 8 alpha bits 21 | .with_pixel_format(24, 8) 22 | .with_vsync(true); 23 | 24 | let (context, device, factory, target, _depth) = init_raw( 25 | builder, 26 | gl_builder, 27 | &event_loop, 28 | format::COLOR, 29 | format::DEPTH, 30 | ) 31 | .map_err(|error| Error::WindowCreation(error.to_string()))?; 32 | 33 | Ok((Self { context, target }, device, factory)) 34 | } 35 | 36 | pub fn window(&self) -> &winit::window::Window { 37 | self.context.window() 38 | } 39 | 40 | pub fn target(&self) -> &TargetView { 41 | &self.target 42 | } 43 | 44 | pub fn resize( 45 | &mut self, 46 | _gpu: &mut Gpu, 47 | size: winit::dpi::PhysicalSize, 48 | ) { 49 | self.context.resize(size); 50 | 51 | let dimensions = self.target.get_dimensions(); 52 | 53 | if let Some((target, _depth)) = update_views_raw( 54 | &self.context, 55 | dimensions, 56 | format::COLOR, 57 | format::DEPTH, 58 | ) { 59 | self.target = target; 60 | } 61 | } 62 | 63 | pub fn request_redraw(&mut self) { 64 | self.context.window().request_redraw(); 65 | } 66 | 67 | pub fn swap_buffers(&mut self, gpu: &mut Gpu) { 68 | gpu.flush(); 69 | self.context.swap_buffers().expect("Buffer swap"); 70 | gpu.cleanup(); 71 | } 72 | } 73 | 74 | fn init_raw( 75 | window: glutin::window::WindowBuilder, 76 | context: glutin::ContextBuilder<'_, glutin::NotCurrent>, 77 | events_loop: &glutin::event_loop::EventLoop<()>, 78 | color_format: gfx::format::Format, 79 | ds_format: gfx::format::Format, 80 | ) -> std::result::Result< 81 | ( 82 | glutin::WindowedContext, 83 | gl::Device, 84 | gl::Factory, 85 | gfx::handle::RawRenderTargetView, 86 | gfx::handle::RawDepthStencilView, 87 | ), 88 | glutin::CreationError, 89 | > { 90 | let window = { 91 | let color_total_bits = color_format.0.get_total_bits(); 92 | let alpha_bits = color_format.0.get_alpha_stencil_bits(); 93 | let depth_total_bits = ds_format.0.get_total_bits(); 94 | let stencil_bits = ds_format.0.get_alpha_stencil_bits(); 95 | 96 | context 97 | .with_depth_buffer(depth_total_bits - stencil_bits) 98 | .with_stencil_buffer(stencil_bits) 99 | .with_pixel_format(color_total_bits - alpha_bits, alpha_bits) 100 | .with_srgb(color_format.1 == gfx::format::ChannelType::Srgb) 101 | .build_windowed(window, events_loop)? 102 | }; 103 | 104 | let (window, device, factory, color_view, ds_view) = 105 | init_existing_raw(window, color_format, ds_format); 106 | 107 | Ok((window, device, factory, color_view, ds_view)) 108 | } 109 | 110 | fn init_existing_raw( 111 | window: glutin::WindowedContext, 112 | color_format: gfx::format::Format, 113 | ds_format: gfx::format::Format, 114 | ) -> ( 115 | glutin::WindowedContext, 116 | gl::Device, 117 | gl::Factory, 118 | gfx::handle::RawRenderTargetView, 119 | gfx::handle::RawDepthStencilView, 120 | ) { 121 | #[allow(unsafe_code)] 122 | let window = unsafe { window.make_current().unwrap() }; 123 | 124 | let (device, factory) = gl::create(|s| { 125 | window.get_proc_address(s) as *const std::os::raw::c_void 126 | }); 127 | 128 | // create the main color/depth targets 129 | let dim = get_window_dimensions(&window); 130 | let (color_view, ds_view) = 131 | gl::create_main_targets_raw(dim, color_format.0, ds_format.0); 132 | 133 | // done 134 | (window, device, factory, color_view, ds_view) 135 | } 136 | 137 | pub fn update_views_raw( 138 | window: &glutin::WindowedContext, 139 | old_dimensions: gfx::texture::Dimensions, 140 | color_format: gfx::format::Format, 141 | ds_format: gfx::format::Format, 142 | ) -> Option<( 143 | gfx::handle::RawRenderTargetView, 144 | gfx::handle::RawDepthStencilView, 145 | )> { 146 | let dim = get_window_dimensions(window); 147 | 148 | if dim != old_dimensions { 149 | Some(gl::create_main_targets_raw( 150 | dim, 151 | color_format.0, 152 | ds_format.0, 153 | )) 154 | } else { 155 | None 156 | } 157 | } 158 | 159 | fn get_window_dimensions( 160 | ctx: &glutin::WindowedContext, 161 | ) -> gfx::texture::Dimensions { 162 | let window = ctx.window(); 163 | 164 | let (width, height) = { 165 | let size = window.inner_size(); 166 | (size.width as _, size.height as _) 167 | }; 168 | 169 | let aa = ctx.get_pixel_format().multisampling.unwrap_or(0) 170 | as gfx::texture::NumSamples; 171 | 172 | (width, height, 1, aa.into()) 173 | } 174 | -------------------------------------------------------------------------------- /src/graphics/backend_gfx/types.rs: -------------------------------------------------------------------------------- 1 | use gfx_device_gl as gl; 2 | 3 | use super::format; 4 | 5 | pub type TargetView = gfx::handle::RawRenderTargetView; 6 | 7 | pub type RawTexture = gfx::handle::RawTexture; 8 | 9 | pub type ShaderResource = 10 | gfx::handle::ShaderResourceView; 11 | -------------------------------------------------------------------------------- /src/graphics/backend_wgpu/font.rs: -------------------------------------------------------------------------------- 1 | use crate::graphics::gpu::TargetView; 2 | use crate::graphics::{ 3 | HorizontalAlignment, Text, Transformation, VerticalAlignment, 4 | }; 5 | 6 | use wgpu_glyph::GlyphCruncher; 7 | 8 | pub struct Font { 9 | glyphs: wgpu_glyph::GlyphBrush<'static, ()>, 10 | } 11 | 12 | impl Font { 13 | pub fn from_bytes(device: &mut wgpu::Device, bytes: &'static [u8]) -> Font { 14 | Font { 15 | glyphs: wgpu_glyph::GlyphBrushBuilder::using_font_bytes(bytes) 16 | .expect("Load font") 17 | .texture_filter_method(wgpu::FilterMode::Nearest) 18 | .build(device, wgpu::TextureFormat::Bgra8UnormSrgb), 19 | } 20 | } 21 | 22 | pub fn add(&mut self, text: Text<'_>) { 23 | let section: wgpu_glyph::Section<'_> = text.into(); 24 | self.glyphs.queue(section); 25 | } 26 | 27 | pub fn measure(&mut self, text: Text<'_>) -> (f32, f32) { 28 | let section: wgpu_glyph::Section<'_> = text.into(); 29 | let bounds = self.glyphs.glyph_bounds(section); 30 | 31 | match bounds { 32 | Some(bounds) => (bounds.width(), bounds.height()), 33 | None => (0.0, 0.0), 34 | } 35 | } 36 | 37 | pub fn draw( 38 | &mut self, 39 | device: &mut wgpu::Device, 40 | encoder: &mut wgpu::CommandEncoder, 41 | target: &TargetView, 42 | transformation: Transformation, 43 | ) { 44 | self.glyphs 45 | .draw_queued_with_transform( 46 | device, 47 | encoder, 48 | target, 49 | transformation.into(), 50 | ) 51 | .expect("Draw font"); 52 | } 53 | } 54 | 55 | impl<'a> From> for wgpu_glyph::Section<'a> { 56 | fn from(text: Text<'a>) -> wgpu_glyph::Section<'a> { 57 | let x = match text.horizontal_alignment { 58 | HorizontalAlignment::Left => text.position.x, 59 | HorizontalAlignment::Center => { 60 | text.position.x + text.bounds.0 / 2.0 61 | } 62 | HorizontalAlignment::Right => text.position.x + text.bounds.0, 63 | }; 64 | 65 | let y = match text.vertical_alignment { 66 | VerticalAlignment::Top => text.position.y, 67 | VerticalAlignment::Center => text.position.y + text.bounds.1 / 2.0, 68 | VerticalAlignment::Bottom => text.position.y + text.bounds.1, 69 | }; 70 | 71 | wgpu_glyph::Section { 72 | text: &text.content, 73 | screen_position: (x, y), 74 | scale: wgpu_glyph::Scale { 75 | x: text.size, 76 | y: text.size, 77 | }, 78 | color: text.color.into_linear(), 79 | bounds: text.bounds, 80 | layout: wgpu_glyph::Layout::default() 81 | .h_align(text.horizontal_alignment.into()) 82 | .v_align(text.vertical_alignment.into()), 83 | ..Default::default() 84 | } 85 | } 86 | } 87 | 88 | impl From for wgpu_glyph::HorizontalAlign { 89 | fn from(alignment: HorizontalAlignment) -> wgpu_glyph::HorizontalAlign { 90 | match alignment { 91 | HorizontalAlignment::Left => wgpu_glyph::HorizontalAlign::Left, 92 | HorizontalAlignment::Center => wgpu_glyph::HorizontalAlign::Center, 93 | HorizontalAlignment::Right => wgpu_glyph::HorizontalAlign::Right, 94 | } 95 | } 96 | } 97 | 98 | impl From for wgpu_glyph::VerticalAlign { 99 | fn from(alignment: VerticalAlignment) -> wgpu_glyph::VerticalAlign { 100 | match alignment { 101 | VerticalAlignment::Top => wgpu_glyph::VerticalAlign::Top, 102 | VerticalAlignment::Center => wgpu_glyph::VerticalAlign::Center, 103 | VerticalAlignment::Bottom => wgpu_glyph::VerticalAlign::Bottom, 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/graphics/backend_wgpu/mod.rs: -------------------------------------------------------------------------------- 1 | mod font; 2 | mod quad; 3 | mod surface; 4 | pub mod texture; 5 | mod triangle; 6 | mod types; 7 | 8 | pub use font::Font; 9 | pub use quad::Quad; 10 | pub use surface::Surface; 11 | pub use texture::Texture; 12 | pub use triangle::Vertex; 13 | pub use types::TargetView; 14 | 15 | use crate::graphics::{Color, Transformation}; 16 | use crate::{Error, Result}; 17 | 18 | #[allow(missing_debug_implementations)] 19 | #[allow(missing_docs)] 20 | pub struct Gpu { 21 | device: wgpu::Device, 22 | queue: wgpu::Queue, 23 | quad_pipeline: quad::Pipeline, 24 | triangle_pipeline: triangle::Pipeline, 25 | encoder: wgpu::CommandEncoder, 26 | } 27 | 28 | impl Gpu { 29 | pub(super) fn for_window( 30 | builder: winit::window::WindowBuilder, 31 | event_loop: &winit::event_loop::EventLoop<()>, 32 | ) -> Result<(Gpu, Surface)> { 33 | let window = builder 34 | .build(event_loop) 35 | .map_err(|error| Error::WindowCreation(error.to_string()))?; 36 | 37 | let (mut device, queue) = futures::executor::block_on(async { 38 | let adapter = wgpu::Adapter::request( 39 | &wgpu::RequestAdapterOptions { 40 | power_preference: wgpu::PowerPreference::HighPerformance, 41 | compatible_surface: None, 42 | }, 43 | wgpu::BackendBit::all(), 44 | ) 45 | .await 46 | .expect("Request adapter"); 47 | 48 | let (device, queue) = adapter 49 | .request_device(&wgpu::DeviceDescriptor { 50 | extensions: wgpu::Extensions { 51 | anisotropic_filtering: false, 52 | }, 53 | limits: wgpu::Limits::default(), 54 | }) 55 | .await; 56 | 57 | (device, queue) 58 | }); 59 | 60 | let surface = Surface::new(window, &device); 61 | 62 | let quad_pipeline = quad::Pipeline::new(&mut device); 63 | let triangle_pipeline = triangle::Pipeline::new(&mut device); 64 | 65 | let encoder = 66 | device.create_command_encoder(&wgpu::CommandEncoderDescriptor { 67 | label: Some("coffee::backend encoder"), 68 | }); 69 | 70 | Ok(( 71 | Gpu { 72 | device, 73 | queue, 74 | quad_pipeline, 75 | triangle_pipeline, 76 | encoder, 77 | }, 78 | surface, 79 | )) 80 | } 81 | 82 | pub(super) fn clear(&mut self, view: &TargetView, color: Color) { 83 | let [r, g, b, a] = color.into_linear(); 84 | 85 | let _ = self.encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 86 | color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { 87 | attachment: &view, 88 | resolve_target: None, 89 | load_op: wgpu::LoadOp::Clear, 90 | store_op: wgpu::StoreOp::Store, 91 | clear_color: wgpu::Color { 92 | r: r as f64, 93 | g: g as f64, 94 | b: b as f64, 95 | a: a as f64, 96 | }, 97 | }], 98 | depth_stencil_attachment: None, 99 | }); 100 | } 101 | 102 | pub(super) fn upload_texture( 103 | &mut self, 104 | image: &image::DynamicImage, 105 | ) -> Texture { 106 | Texture::new(&mut self.device, &self.queue, &self.quad_pipeline, image) 107 | } 108 | 109 | pub(super) fn upload_texture_array( 110 | &mut self, 111 | layers: &[image::DynamicImage], 112 | ) -> Texture { 113 | Texture::new_array( 114 | &mut self.device, 115 | &self.queue, 116 | &self.quad_pipeline, 117 | layers, 118 | ) 119 | } 120 | 121 | pub(super) fn create_drawable_texture( 122 | &mut self, 123 | width: u16, 124 | height: u16, 125 | ) -> texture::Drawable { 126 | texture::Drawable::new( 127 | &mut self.device, 128 | &self.queue, 129 | &self.quad_pipeline, 130 | width, 131 | height, 132 | ) 133 | } 134 | 135 | pub(super) fn read_drawable_texture_pixels( 136 | &mut self, 137 | drawable: &texture::Drawable, 138 | ) -> image::DynamicImage { 139 | let new_encoder = self.device.create_command_encoder( 140 | &wgpu::CommandEncoderDescriptor { 141 | label: Some("coffee::backend encoder"), 142 | }, 143 | ); 144 | 145 | let encoder = std::mem::replace(&mut self.encoder, new_encoder); 146 | 147 | drawable.read_pixels(&mut self.device, &self.queue, encoder) 148 | } 149 | 150 | pub(super) fn upload_font(&mut self, bytes: &'static [u8]) -> Font { 151 | Font::from_bytes(&mut self.device, bytes) 152 | } 153 | 154 | pub(super) fn draw_triangles( 155 | &mut self, 156 | vertices: &[Vertex], 157 | indices: &[u32], 158 | view: &TargetView, 159 | transformation: &Transformation, 160 | ) { 161 | self.triangle_pipeline.draw( 162 | &mut self.device, 163 | &mut self.encoder, 164 | vertices, 165 | indices, 166 | transformation, 167 | view, 168 | ); 169 | } 170 | 171 | pub(super) fn draw_texture_quads( 172 | &mut self, 173 | texture: &Texture, 174 | instances: &[Quad], 175 | view: &TargetView, 176 | transformation: &Transformation, 177 | ) { 178 | self.quad_pipeline.draw_textured( 179 | &mut self.device, 180 | &mut self.encoder, 181 | texture.binding(), 182 | instances, 183 | transformation, 184 | view, 185 | ); 186 | } 187 | 188 | pub(super) fn draw_font( 189 | &mut self, 190 | font: &mut Font, 191 | target: &TargetView, 192 | transformation: Transformation, 193 | ) { 194 | font.draw(&mut self.device, &mut self.encoder, target, transformation); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/graphics/backend_wgpu/shader/quad.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec2 v_Uv; 4 | layout(location = 1) flat in uint v_Layer; 5 | 6 | layout(set = 0, binding = 1) uniform sampler u_Sampler; 7 | layout(set = 1, binding = 0) uniform texture2DArray u_Texture; 8 | 9 | layout(location = 0) out vec4 o_Target; 10 | 11 | void main() { 12 | o_Target = texture(sampler2DArray(u_Texture, u_Sampler), vec3(v_Uv, v_Layer)); 13 | } 14 | -------------------------------------------------------------------------------- /src/graphics/backend_wgpu/shader/quad.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hecrj/coffee/5bb04cf8483188582a2adb8c36567242af86f49d/src/graphics/backend_wgpu/shader/quad.frag.spv -------------------------------------------------------------------------------- /src/graphics/backend_wgpu/shader/quad.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec2 a_Pos; 4 | layout(location = 1) in vec4 a_Src; 5 | layout(location = 2) in vec2 a_Scale; 6 | layout(location = 3) in vec2 a_Translation; 7 | layout(location = 4) in uint t_Layer; 8 | 9 | layout (set = 0, binding = 0) uniform Globals { 10 | mat4 u_Transform; 11 | }; 12 | 13 | layout(location = 0) out vec2 v_Uv; 14 | layout(location = 1) flat out uint v_Layer; 15 | 16 | void main() { 17 | v_Uv = a_Pos * a_Src.zw + a_Src.xy; 18 | v_Layer = t_Layer; 19 | 20 | mat4 a_Transform = mat4( 21 | vec4(a_Scale.x, 0.0, 0.0, 0.0), 22 | vec4(0.0, a_Scale.y, 0.0, 0.0), 23 | vec4(0.0, 0.0, 1.0, 0.0), 24 | vec4(a_Translation, 0.0, 1.0) 25 | ); 26 | 27 | gl_Position = u_Transform * a_Transform * vec4(a_Pos, 0.0, 1.0); 28 | } 29 | -------------------------------------------------------------------------------- /src/graphics/backend_wgpu/shader/quad.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hecrj/coffee/5bb04cf8483188582a2adb8c36567242af86f49d/src/graphics/backend_wgpu/shader/quad.vert.spv -------------------------------------------------------------------------------- /src/graphics/backend_wgpu/shader/triangle.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec4 v_Color; 4 | 5 | layout(location = 0) out vec4 o_Target; 6 | 7 | void main() { 8 | o_Target = v_Color; 9 | } 10 | -------------------------------------------------------------------------------- /src/graphics/backend_wgpu/shader/triangle.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hecrj/coffee/5bb04cf8483188582a2adb8c36567242af86f49d/src/graphics/backend_wgpu/shader/triangle.frag.spv -------------------------------------------------------------------------------- /src/graphics/backend_wgpu/shader/triangle.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec2 a_Pos; 4 | layout(location = 1) in vec4 a_Color; 5 | 6 | layout (set = 0, binding = 0) uniform Globals { 7 | mat4 u_Transform; 8 | }; 9 | 10 | layout(location = 0) flat out vec4 v_Color; 11 | 12 | void main() { 13 | v_Color = a_Color; 14 | 15 | gl_Position = u_Transform * vec4(a_Pos, 0.0, 1.0); 16 | } 17 | -------------------------------------------------------------------------------- /src/graphics/backend_wgpu/shader/triangle.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hecrj/coffee/5bb04cf8483188582a2adb8c36567242af86f49d/src/graphics/backend_wgpu/shader/triangle.vert.spv -------------------------------------------------------------------------------- /src/graphics/backend_wgpu/surface.rs: -------------------------------------------------------------------------------- 1 | use super::{Gpu, TargetView}; 2 | 3 | pub struct Surface { 4 | window: winit::window::Window, 5 | surface: wgpu::Surface, 6 | swap_chain: wgpu::SwapChain, 7 | extent: wgpu::Extent3d, 8 | output: Option, 9 | } 10 | 11 | impl Surface { 12 | pub fn new( 13 | window: winit::window::Window, 14 | device: &wgpu::Device, 15 | ) -> Surface { 16 | let surface = wgpu::Surface::create(&window); 17 | let size = window.inner_size(); 18 | 19 | let (swap_chain, extent) = new_swap_chain(device, &surface, size); 20 | 21 | Surface { 22 | window, 23 | surface, 24 | swap_chain, 25 | extent, 26 | output: None, 27 | } 28 | } 29 | 30 | pub fn window(&self) -> &winit::window::Window { 31 | &self.window 32 | } 33 | 34 | pub fn target(&mut self) -> &TargetView { 35 | if self.output.is_none() { 36 | let output = self 37 | .swap_chain 38 | .get_next_texture() 39 | .expect("Get next texture"); 40 | 41 | self.output = Some(output); 42 | } 43 | 44 | &self.output.as_ref().unwrap().view 45 | } 46 | 47 | pub fn resize( 48 | &mut self, 49 | gpu: &mut Gpu, 50 | size: winit::dpi::PhysicalSize, 51 | ) { 52 | let (swap_chain, extent) = 53 | new_swap_chain(&gpu.device, &self.surface, size); 54 | 55 | self.swap_chain = swap_chain; 56 | self.extent = extent; 57 | self.output = None; 58 | } 59 | 60 | pub fn swap_buffers(&mut self, gpu: &mut Gpu) { 61 | let new_encoder = gpu.device.create_command_encoder( 62 | &wgpu::CommandEncoderDescriptor { 63 | label: Some("coffee::backend::surface blit"), 64 | }, 65 | ); 66 | 67 | // We swap the current decoder by a new one here, so we can finish the 68 | // current frame 69 | let encoder = std::mem::replace(&mut gpu.encoder, new_encoder); 70 | 71 | gpu.queue.submit(&[encoder.finish()]); 72 | 73 | self.output = None; 74 | } 75 | 76 | pub fn request_redraw(&mut self) { 77 | self.window.request_redraw(); 78 | } 79 | } 80 | 81 | fn new_swap_chain( 82 | device: &wgpu::Device, 83 | surface: &wgpu::Surface, 84 | size: winit::dpi::PhysicalSize, 85 | ) -> (wgpu::SwapChain, wgpu::Extent3d) { 86 | let swap_chain = device.create_swap_chain( 87 | surface, 88 | &wgpu::SwapChainDescriptor { 89 | usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, 90 | format: wgpu::TextureFormat::Bgra8UnormSrgb, 91 | width: size.width, 92 | height: size.height, 93 | present_mode: wgpu::PresentMode::Mailbox, 94 | }, 95 | ); 96 | 97 | let extent = wgpu::Extent3d { 98 | width: size.width, 99 | height: size.height, 100 | depth: 1, 101 | }; 102 | 103 | (swap_chain, extent) 104 | } 105 | -------------------------------------------------------------------------------- /src/graphics/backend_wgpu/types.rs: -------------------------------------------------------------------------------- 1 | pub type TargetView = wgpu::TextureView; 2 | -------------------------------------------------------------------------------- /src/graphics/batch.rs: -------------------------------------------------------------------------------- 1 | use rayon::prelude::*; 2 | 3 | use crate::graphics::gpu; 4 | use crate::graphics::{Image, IntoQuad, Target}; 5 | 6 | /// A collection of quads that will be drawn all at once using the same 7 | /// [`Image`]. 8 | /// 9 | /// [`Image`]: struct.Image.html 10 | pub struct Batch { 11 | image: Image, 12 | instances: Vec, 13 | x_unit: f32, 14 | y_unit: f32, 15 | } 16 | 17 | impl Batch { 18 | /// Creates a new [`Batch`] using the given [`Image`]. 19 | /// 20 | /// [`Batch`]: struct.Batch.html 21 | /// [`Image`]: struct.Image.html 22 | pub fn new(image: Image) -> Self { 23 | let x_unit = 1.0 / image.width() as f32; 24 | let y_unit = 1.0 / image.height() as f32; 25 | 26 | Self { 27 | image, 28 | instances: Vec::new(), 29 | x_unit, 30 | y_unit, 31 | } 32 | } 33 | 34 | /// Adds a quad to the [`Batch`]. 35 | /// 36 | /// [`Batch`]: struct.Batch.html 37 | #[inline] 38 | pub fn add(&mut self, quad: Q) { 39 | let instance = 40 | gpu::Quad::from(quad.into_quad(self.x_unit, self.y_unit)); 41 | 42 | self.instances.push(instance); 43 | } 44 | 45 | /// Draws the [`Batch`] on the given [`Target`]. 46 | /// 47 | /// [`Batch`]: struct.Batch.html 48 | /// [`Target`]: struct.Target.html 49 | pub fn draw(&self, target: &mut Target<'_>) { 50 | target.draw_texture_quads(&self.image.texture, &self.instances[..]); 51 | } 52 | 53 | /// Clears the [`Batch`] contents. 54 | /// 55 | /// This is useful to avoid creating a new batch every frame and 56 | /// reallocating the same memory. 57 | /// 58 | /// [`Batch`]: struct.Batch.html 59 | pub fn clear(&mut self) { 60 | self.instances.clear(); 61 | } 62 | } 63 | 64 | impl std::fmt::Debug for Batch { 65 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 66 | write!(f, "Batch {{ image: {:?} }}", self.image,) 67 | } 68 | } 69 | 70 | impl Extend for Batch { 71 | fn extend(&mut self, iter: I) 72 | where 73 | I: IntoIterator, 74 | { 75 | let iter = iter.into_iter(); 76 | let x_unit = self.x_unit; 77 | let y_unit = self.y_unit; 78 | 79 | self.instances.extend( 80 | iter.map(|quad| gpu::Quad::from(quad.into_quad(x_unit, y_unit))), 81 | ); 82 | } 83 | } 84 | 85 | /// Extends the [`Batch`] using a parallel iterator from [`rayon`]. 86 | /// 87 | /// If you are dealing with many thousands of quads, `par_extend` can help you 88 | /// speed up your drawing by using multiple threads to populate a [`Batch`]. 89 | /// 90 | /// [`Batch`]: struct.Batch.html 91 | /// [`rayon`]: https://docs.rs/rayon/1.0/rayon/ 92 | impl ParallelExtend for Batch { 93 | fn par_extend(&mut self, par_iter: I) 94 | where 95 | I: IntoParallelIterator, 96 | { 97 | let par_iter = par_iter.into_par_iter(); 98 | let x_unit = self.x_unit; 99 | let y_unit = self.y_unit; 100 | 101 | self.instances.par_extend( 102 | par_iter 103 | .map(|quad| gpu::Quad::from(quad.into_quad(x_unit, y_unit))), 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/graphics/canvas.rs: -------------------------------------------------------------------------------- 1 | use crate::graphics::gpu::{self, texture, Gpu}; 2 | use crate::graphics::{IntoQuad, Target}; 3 | use crate::load::Task; 4 | use crate::Result; 5 | 6 | /// An off-screen rendering target. 7 | /// 8 | /// It can be used both as a [`Target`] and as a resource. 9 | /// 10 | /// [`Target`]: struct.Target.html 11 | #[derive(Clone)] 12 | pub struct Canvas { 13 | drawable: texture::Drawable, 14 | } 15 | 16 | impl Canvas { 17 | /// Creates a new [`Canvas`] with the given size. 18 | /// 19 | /// [`Canvas`]: struct.Canvas.html 20 | pub fn new(gpu: &mut Gpu, width: u16, height: u16) -> Result { 21 | Ok(Canvas { 22 | drawable: gpu.create_drawable_texture(width, height), 23 | }) 24 | } 25 | 26 | /// Creates a [`Task`] that produces a new [`Canvas`] with the given size. 27 | /// 28 | /// [`Task`]: ../load/struct.Task.html 29 | /// [`Canvas`]: struct.Canvas.html 30 | pub fn load(width: u16, height: u16) -> Task { 31 | Task::using_gpu(move |gpu| Canvas::new(gpu, width, height)) 32 | } 33 | 34 | /// Returns the width of the [`Canvas`]. 35 | /// 36 | /// [`Canvas`]: struct.Canvas.html 37 | pub fn width(&self) -> u16 { 38 | self.drawable.texture().width() 39 | } 40 | 41 | /// Returns the height of the [`Canvas`]. 42 | /// 43 | /// [`Canvas`]: struct.Canvas.html 44 | pub fn height(&self) -> u16 { 45 | self.drawable.texture().height() 46 | } 47 | 48 | /// Views the [`Canvas`] as a [`Target`]. 49 | /// 50 | /// [`Canvas`]: struct.Canvas.html 51 | /// [`Target`]: struct.Target.html 52 | pub fn as_target<'a>(&'a mut self, gpu: &'a mut Gpu) -> Target<'a> { 53 | let texture = self.drawable.texture(); 54 | 55 | Target::with_transformation( 56 | gpu, 57 | self.drawable.target(), 58 | f32::from(texture.width()), 59 | f32::from(texture.height()), 60 | texture::Drawable::render_transformation(), 61 | ) 62 | } 63 | 64 | /// Renders the [`Canvas`] on the given [`Target`]. 65 | /// 66 | /// [`Canvas`]: struct.Canvas.html 67 | /// [`Target`]: struct.Target.html 68 | pub fn draw(&self, quad: Q, target: &mut Target<'_>) { 69 | target.draw_texture_quads( 70 | &self.drawable.texture(), 71 | &[gpu::Quad::from(quad.into_quad( 72 | 1.0 / self.width() as f32, 73 | 1.0 / self.height() as f32, 74 | ))], 75 | ); 76 | } 77 | 78 | /// Reads the pixels of the [`Canvas`]. 79 | /// 80 | /// _Note:_ This is a very slow operation. 81 | /// 82 | /// [`Canvas`]: struct.Canvas.html 83 | pub fn read_pixels(&self, gpu: &mut Gpu) -> image::DynamicImage { 84 | gpu.read_drawable_texture_pixels(&self.drawable) 85 | } 86 | } 87 | 88 | impl std::fmt::Debug for Canvas { 89 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 90 | write!( 91 | f, 92 | "Canvas {{ width: {}, height: {} }}", 93 | self.width(), 94 | self.height() 95 | ) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/graphics/color.rs: -------------------------------------------------------------------------------- 1 | /// An RGBA color in the sRGB color space. 2 | #[derive(PartialEq, Clone, Copy, Debug)] 3 | pub struct Color { 4 | /// Red component. 5 | pub r: f32, 6 | 7 | /// Green component. 8 | pub g: f32, 9 | 10 | /// Blue component. 11 | pub b: f32, 12 | 13 | /// Alpha component. 14 | pub a: f32, 15 | } 16 | 17 | impl Color { 18 | /// White color. 19 | pub const WHITE: Self = Self { 20 | r: 1.0, 21 | g: 1.0, 22 | b: 1.0, 23 | a: 1.0, 24 | }; 25 | 26 | /// Black color. 27 | pub const BLACK: Self = Self { 28 | r: 0.0, 29 | g: 0.0, 30 | b: 0.0, 31 | a: 1.0, 32 | }; 33 | 34 | /// Red color. 35 | pub const RED: Self = Self { 36 | r: 1.0, 37 | g: 0.0, 38 | b: 0.0, 39 | a: 1.0, 40 | }; 41 | 42 | /// Green color. 43 | pub const GREEN: Self = Self { 44 | r: 0.0, 45 | g: 1.0, 46 | b: 0.0, 47 | a: 1.0, 48 | }; 49 | 50 | /// Blue color. 51 | pub const BLUE: Self = Self { 52 | r: 0.0, 53 | g: 0.0, 54 | b: 1.0, 55 | a: 1.0, 56 | }; 57 | 58 | /// Creates a new [`Color`] from components in the [0, 1.0] range. 59 | /// 60 | /// [`Color`]: struct.Color.html 61 | pub fn new(r: f32, g: f32, b: f32, a: f32) -> Color { 62 | debug_assert!(r >= 0.0, "Red component is < 0.0"); 63 | debug_assert!(r <= 1.0, "Red component is > 1.0"); 64 | debug_assert!(g >= 0.0, "Green component is < 0.0"); 65 | debug_assert!(g <= 1.0, "Green component is > 1.0"); 66 | debug_assert!(b >= 0.0, "Blue component is < 0.0"); 67 | debug_assert!(b <= 1.0, "Blue component is > 1.0"); 68 | debug_assert!(a >= 0.0, "Alpha component is < 0.0"); 69 | debug_assert!(a <= 1.0, "Alpha component is > 1.0"); 70 | Color { r, g, b, a } 71 | } 72 | 73 | /// Creates a new [`Color`] from its RGB components in the [0, 255] range. 74 | /// 75 | /// [`Color`]: struct.Color.html 76 | pub fn from_rgb(r: u8, g: u8, b: u8) -> Color { 77 | Color { 78 | r: r as f32 / 255.0, 79 | g: g as f32 / 255.0, 80 | b: b as f32 / 255.0, 81 | a: 1.0, 82 | } 83 | } 84 | 85 | /// Creates a new [`Color`] from its RGB representation (0xRRGGBB). 86 | /// 87 | /// [`Color`]: struct.Color.html 88 | pub fn from_rgb_u32(color: u32) -> Color { 89 | debug_assert!( 90 | color <= 0xFFFFFF, 91 | "Color contains value higher than 0xFFFFFF" 92 | ); 93 | let r = ((color & 0xFF0000) >> 16) as u8; 94 | let g = ((color & 0x00FF00) >> 8) as u8; 95 | let b = ((color & 0x0000FF) >> 0) as u8; 96 | Color::from_rgb(r, g, b) 97 | } 98 | 99 | /// Returns the [`Color`] components in the [0, 255] range. 100 | /// 101 | /// [`Color`]: struct.Color.html 102 | pub fn to_rgba(&self) -> [u8; 4] { 103 | [ 104 | (self.r * 255.0).round() as u8, 105 | (self.g * 255.0).round() as u8, 106 | (self.b * 255.0).round() as u8, 107 | (self.a * 255.0).round() as u8, 108 | ] 109 | } 110 | 111 | pub(crate) fn into_linear(self) -> [f32; 4] { 112 | // As described in: 113 | // https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation 114 | fn linear_component(u: f32) -> f32 { 115 | if u < 0.04045 { 116 | u / 12.92 117 | } else { 118 | ((u + 0.055) / 1.055).powf(2.4) 119 | } 120 | } 121 | 122 | [ 123 | linear_component(self.r), 124 | linear_component(self.g), 125 | linear_component(self.b), 126 | self.a, 127 | ] 128 | } 129 | } 130 | 131 | impl From<[u8; 3]> for Color { 132 | fn from(values: [u8; 3]) -> Color { 133 | let [r, g, b] = values; 134 | 135 | Color::from_rgb(r, g, b) 136 | } 137 | } 138 | 139 | impl From for [f32; 4] { 140 | fn from(color: Color) -> [f32; 4] { 141 | [color.r, color.g, color.b, color.a] 142 | } 143 | } 144 | 145 | impl From for [u8; 4] { 146 | fn from(color: Color) -> [u8; 4] { 147 | color.to_rgba() 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/graphics/font.rs: -------------------------------------------------------------------------------- 1 | use crate::graphics::gpu; 2 | use crate::graphics::{Gpu, Target, Text}; 3 | use crate::load::Task; 4 | use crate::Result; 5 | 6 | /// A collection of text with the same font. 7 | #[allow(missing_debug_implementations)] 8 | pub struct Font(gpu::Font); 9 | 10 | impl Font { 11 | pub(crate) const DEFAULT: &'static [u8] = 12 | include_bytes!("../../resources/font/Inconsolata-Regular.ttf"); 13 | 14 | /// Loads a [`Font`] from raw data. 15 | /// 16 | /// [`Font`]: struct.Font.html 17 | pub fn from_bytes(gpu: &mut Gpu, bytes: &'static [u8]) -> Result { 18 | Ok(Font(gpu.upload_font(bytes))) 19 | } 20 | 21 | /// Creates a [`Task`] that loads a [`Font`] from raw data. 22 | /// 23 | /// [`Task`]: ../load/struct.Task.html 24 | /// [`Font`]: struct.Font.html 25 | pub fn load_from_bytes(bytes: &'static [u8]) -> Task { 26 | Task::using_gpu(move |gpu| Font::from_bytes(gpu, bytes)) 27 | } 28 | 29 | /// Adds [`Text`] to this [`Font`]. 30 | /// 31 | /// [`Text`]: struct.Text.html 32 | /// [`Font`]: struct.Font.html 33 | pub fn add(&mut self, text: Text<'_>) { 34 | self.0.add(text) 35 | } 36 | 37 | /// Computes the layout bounds of the given [`Text`]. 38 | /// 39 | /// [`Text`]: struct.Text.html 40 | pub fn measure(&mut self, text: Text<'_>) -> (f32, f32) { 41 | self.0.measure(text) 42 | } 43 | 44 | /// Renders and flushes all the text added to this [`Font`]. 45 | /// 46 | /// [`Font`]: struct.Font.html 47 | #[inline] 48 | pub fn draw(&mut self, target: &mut Target<'_>) { 49 | target.draw_font(&mut self.0) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/graphics/image.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Read; 3 | use std::path::{Path, PathBuf}; 4 | 5 | use crate::graphics::gpu::{self, Texture}; 6 | use crate::graphics::{Color, Gpu, IntoQuad, Target}; 7 | use crate::load::Task; 8 | use crate::Result; 9 | 10 | /// A loaded image. 11 | /// 12 | /// You can use this to load your spritesheets and draw your sprites! 13 | /// 14 | /// Cloning an [`Image`] is cheap, it only clones a handle. It does not 15 | /// create a new copy of the image on the GPU. 16 | /// 17 | /// [`Image`]: struct.Image.html 18 | #[derive(Clone)] 19 | pub struct Image { 20 | pub(super) texture: Texture, 21 | } 22 | 23 | impl Image { 24 | /// Loads an [`Image`] from the given path. 25 | /// 26 | /// [`Image`]: struct.Image.html 27 | pub fn new>(gpu: &mut Gpu, path: P) -> Result { 28 | let image = { 29 | let mut buf = Vec::new(); 30 | let mut reader = File::open(path)?; 31 | let _ = reader.read_to_end(&mut buf)?; 32 | image::load_from_memory(&buf)? 33 | }; 34 | 35 | Image::from_image(gpu, &image) 36 | } 37 | 38 | /// Creates a [`Task`] that loads an [`Image`] from the given path. 39 | /// 40 | /// [`Task`]: ../load/struct.Task.html 41 | /// [`Image`]: struct.Image.html 42 | pub fn load>(path: P) -> Task { 43 | let p = path.into(); 44 | 45 | Task::using_gpu(move |gpu| Image::new(gpu, &p)) 46 | } 47 | 48 | /// Creates an [`Image`] from a [`DynamicImage`] of the [`image` crate]. 49 | /// 50 | /// [`Image`]: struct.Image.html 51 | /// [`DynamicImage`]: https://docs.rs/image/0.21.1/image/enum.DynamicImage.html 52 | /// [`image` crate]: https://docs.rs/image 53 | pub fn from_image( 54 | gpu: &mut Gpu, 55 | image: &image::DynamicImage, 56 | ) -> Result { 57 | let texture = gpu.upload_texture(&image); 58 | 59 | Ok(Image { texture }) 60 | } 61 | 62 | /// Creates an [`Image`] representing a color palette. 63 | /// 64 | /// Each [`Color`] will be a pixel of the image, arranged horizontally. 65 | /// 66 | /// [`Image`]: struct.Image.html 67 | /// [`Color`]: struct.Color.html 68 | pub fn from_colors(gpu: &mut Gpu, colors: &[Color]) -> Result { 69 | let colors: Vec<[u8; 4]> = 70 | colors.iter().map(|color| color.to_rgba()).collect(); 71 | 72 | Self::from_image( 73 | gpu, 74 | &image::DynamicImage::ImageRgba8( 75 | image::RgbaImage::from_raw( 76 | colors.len() as u32, 77 | 1, 78 | colors.iter().flatten().cloned().collect(), 79 | ) 80 | .unwrap(), 81 | ), 82 | ) 83 | } 84 | 85 | /// Returns the width of the [`Image`]. 86 | /// 87 | /// [`Image`]: struct.Image.html 88 | pub fn width(&self) -> u16 { 89 | self.texture.width() 90 | } 91 | 92 | /// Returns the height of the [`Image`]. 93 | /// 94 | /// [`Image`]: struct.Image.html 95 | pub fn height(&self) -> u16 { 96 | self.texture.height() 97 | } 98 | 99 | /// Draws the [`Image`] on the given [`Target`]. 100 | /// 101 | /// [`Image`]: struct.Image.html 102 | /// [`Target`]: struct.Target.html 103 | #[inline] 104 | pub fn draw(&self, quad: Q, target: &mut Target<'_>) { 105 | target.draw_texture_quads( 106 | &self.texture, 107 | &[gpu::Quad::from(quad.into_quad( 108 | 1.0 / self.width() as f32, 109 | 1.0 / self.height() as f32, 110 | ))], 111 | ); 112 | } 113 | } 114 | 115 | impl std::fmt::Debug for Image { 116 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 117 | write!( 118 | f, 119 | "Image {{ width: {}, height: {} }}", 120 | self.width(), 121 | self.height() 122 | ) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/graphics/point.rs: -------------------------------------------------------------------------------- 1 | use nalgebra; 2 | 3 | /// A 2D point. 4 | pub type Point = nalgebra::Point2; 5 | -------------------------------------------------------------------------------- /src/graphics/quad.rs: -------------------------------------------------------------------------------- 1 | use crate::graphics::point::Point; 2 | use crate::graphics::rectangle::Rectangle; 3 | 4 | /// A textured quad. 5 | #[derive(Debug, PartialEq, Clone)] 6 | pub struct Quad { 7 | /// The region of the resource that should be shown on the quad, in relative 8 | /// coordinates: [0.0, 1.0]. 9 | pub source: Rectangle, 10 | 11 | /// The position where the quad should be drawn. 12 | pub position: Point, 13 | 14 | /// The size of the quad. 15 | pub size: (f32, f32), 16 | } 17 | 18 | impl Default for Quad { 19 | fn default() -> Self { 20 | Self { 21 | source: Rectangle { 22 | x: 0.0, 23 | y: 0.0, 24 | width: 1.0, 25 | height: 1.0, 26 | }, 27 | position: Point::new(0.0, 0.0), 28 | size: (1.0, 1.0), 29 | } 30 | } 31 | } 32 | 33 | /// Turn a type into a quad. 34 | /// 35 | /// Most methods accept generic types that can be turned into quads. This allows 36 | /// you to use your own quad-based type. 37 | pub trait IntoQuad { 38 | /// Turns the implementor into a quad. 39 | /// 40 | /// `x_unit` and `y_unit` are conversion factors for the [`source`] field. 41 | /// Use them to convert absolute resource coordinates into relative 42 | /// coordinates. 43 | /// 44 | /// [`source`]: struct.Quad.html#structfield.source 45 | fn into_quad(self, x_unit: f32, y_unit: f32) -> Quad; 46 | } 47 | 48 | impl IntoQuad for Quad { 49 | fn into_quad(self, _x_unit: f32, _y_unit: f32) -> Quad { 50 | self 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/graphics/rectangle.rs: -------------------------------------------------------------------------------- 1 | use crate::graphics::Point; 2 | 3 | /// A generic rectangle. 4 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 5 | pub struct Rectangle { 6 | /// X coordinate of the top-left corner. 7 | pub x: T, 8 | 9 | /// Y coordinate of the top-left corner. 10 | pub y: T, 11 | 12 | /// Width of the rectangle. 13 | pub width: T, 14 | 15 | /// Height of the rectangle. 16 | pub height: T, 17 | } 18 | 19 | impl Rectangle { 20 | /// Returns true if the given [`Point`] is contained in the [`Rectangle`]. 21 | /// 22 | /// [`Point`]: type.Point.html 23 | /// [`Rectangle`]: struct.Rectangle.html 24 | pub fn contains(&self, point: Point) -> bool { 25 | self.x <= point.x 26 | && point.x <= self.x + self.width 27 | && self.y <= point.y 28 | && point.y <= self.y + self.height 29 | } 30 | 31 | /// Returns [`Point`] that is exactly in the center of this [`Rectangle`]. 32 | /// 33 | /// [`Point`]: type.Point.html 34 | /// [`Rectangle`]: struct.Rectangle.html 35 | pub fn center(&self) -> Point { 36 | Point::new( 37 | self.x + self.width / 2.0, 38 | self.y + self.height / 2.0, 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/graphics/shape.rs: -------------------------------------------------------------------------------- 1 | use crate::graphics::{Point, Rectangle}; 2 | 3 | /// A geometric figure. 4 | #[derive(Debug, Clone, PartialEq)] 5 | pub enum Shape { 6 | /// A rectangle 7 | Rectangle(Rectangle), 8 | 9 | /// A circle 10 | Circle { 11 | /// The center of the circle 12 | center: Point, 13 | 14 | /// The radius of the circle 15 | radius: f32, 16 | }, 17 | 18 | /// An ellipse 19 | Ellipse { 20 | /// The center of the ellipse 21 | center: Point, 22 | 23 | /// The horizontal radius of the ellipse 24 | horizontal_radius: f32, 25 | 26 | /// The vertical radius of the ellipse 27 | vertical_radius: f32, 28 | 29 | /// The rotation of the ellipse in radians 30 | rotation: f32, 31 | }, 32 | 33 | /// A polyline 34 | Polyline { 35 | /// The points of the polyline 36 | points: Vec, 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /src/graphics/sprite.rs: -------------------------------------------------------------------------------- 1 | use crate::graphics::{IntoQuad, Point, Quad, Rectangle}; 2 | 3 | /// A quad describing the portion of a resource in absolute coordinates. 4 | /// 5 | /// Unlike a [`Quad`], the `source` coordinates of a [`Sprite`] are absolute. It 6 | /// can be used as a convenient alternative. 7 | /// 8 | /// [`Quad`]: struct.Quad.html 9 | /// [`Sprite`]: struct.Sprite.html 10 | #[derive(Debug, PartialEq, Clone)] 11 | pub struct Sprite { 12 | /// The portion of a resource that contains the sprite, in absolute 13 | /// coordinates. 14 | pub source: Rectangle, 15 | 16 | /// The position where the sprite should be drawn. 17 | pub position: Point, 18 | 19 | /// The scale to apply to the sprite. 20 | pub scale: (f32, f32), 21 | } 22 | 23 | impl Default for Sprite { 24 | #[inline] 25 | fn default() -> Sprite { 26 | Sprite { 27 | source: Rectangle { 28 | x: 0, 29 | y: 0, 30 | width: 1, 31 | height: 1, 32 | }, 33 | position: Point::new(0.0, 0.0), 34 | scale: (1.0, 1.0), 35 | } 36 | } 37 | } 38 | 39 | impl IntoQuad for Sprite { 40 | fn into_quad(self, x_unit: f32, y_unit: f32) -> Quad { 41 | Quad { 42 | source: Rectangle { 43 | x: self.source.x as f32 * x_unit, 44 | y: self.source.y as f32 * y_unit, 45 | width: self.source.width as f32 * x_unit, 46 | height: self.source.height as f32 * y_unit, 47 | }, 48 | position: self.position, 49 | size: ( 50 | self.source.width as f32 * self.scale.0, 51 | self.source.height as f32 * self.scale.1, 52 | ), 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/graphics/target.rs: -------------------------------------------------------------------------------- 1 | use crate::graphics::gpu::{self, Font, Gpu, TargetView, Texture, Vertex}; 2 | use crate::graphics::{Color, Transformation}; 3 | 4 | /// A rendering target. 5 | /// 6 | /// In Coffee, all the draw operations need an explicit [`Target`]. You can 7 | /// obtain one from a [`Frame`] or a [`Canvas`]. 8 | /// 9 | /// The default coordinate system of a [`Target`] has the origin `(0, 0)` at 10 | /// its top-left corner and `(Target::width, Target::height)` at its bottom-right 11 | /// corner. 12 | /// 13 | /// [`Target`]: struct.Target.html 14 | /// [`Frame`]: struct.Frame.html 15 | /// [`Canvas`]: struct.Canvas.html 16 | pub struct Target<'a> { 17 | gpu: &'a mut Gpu, 18 | view: &'a TargetView, 19 | transformation: Transformation, 20 | } 21 | 22 | impl<'a> Target<'a> { 23 | pub(super) fn new( 24 | gpu: &'a mut Gpu, 25 | view: &'a TargetView, 26 | width: f32, 27 | height: f32, 28 | ) -> Self { 29 | Target { 30 | gpu, 31 | view, 32 | transformation: Transformation::orthographic(width, height), 33 | } 34 | } 35 | 36 | pub(super) fn with_transformation( 37 | gpu: &'a mut Gpu, 38 | view: &'a TargetView, 39 | width: f32, 40 | height: f32, 41 | transformation: Transformation, 42 | ) -> Self { 43 | let mut target = Self::new(gpu, view, width, height); 44 | target.transformation = transformation * target.transformation; 45 | target 46 | } 47 | 48 | /// Creates a new [`Target`] applying the given transformation. 49 | /// 50 | /// This is equivalent to multiplying the current [`Target`] transform by 51 | /// the provided transform. 52 | /// 53 | /// You can use blocks to emulate a transformation stack! Imagine we want to 54 | /// apply a camera translation with some zoom, but only use it to draw a 55 | /// particular scene. We can simply do: 56 | /// 57 | /// ``` 58 | /// use coffee::graphics::{Frame, Transformation, Vector}; 59 | /// 60 | /// fn draw_something(frame: &mut Frame) { 61 | /// let mut target = frame.as_target(); 62 | /// 63 | /// // We can draw stuff on `target` here 64 | /// // ... 65 | /// 66 | /// { 67 | /// let transformation = Transformation::scale(2.0) 68 | /// * Transformation::translate(Vector::new(10.0, 10.0)); 69 | /// 70 | /// let mut camera = target.transform(transformation); 71 | /// 72 | /// // Use `camera` to draw the particular scene here 73 | /// // ... 74 | /// } 75 | /// 76 | /// // We can keep using `target` as if no transformation happened 77 | /// // ... 78 | /// } 79 | /// ``` 80 | /// 81 | /// [`Target`]: struct.Target.html 82 | pub fn transform(&mut self, transformation: Transformation) -> Target<'_> { 83 | Target { 84 | gpu: self.gpu, 85 | view: self.view, 86 | transformation: self.transformation * transformation, 87 | } 88 | } 89 | 90 | /// Clears the [`Target`] with the given [`Color`]. 91 | /// 92 | /// [`Target`]: struct.Target.html 93 | /// [`Color`]: struct.Color.html 94 | pub fn clear(&mut self, color: Color) { 95 | self.gpu.clear(&self.view, color); 96 | } 97 | 98 | pub(super) fn draw_triangles( 99 | &mut self, 100 | vertices: &[Vertex], 101 | indices: &[u32], 102 | ) { 103 | self.gpu.draw_triangles( 104 | vertices, 105 | indices, 106 | &self.view, 107 | &self.transformation, 108 | ); 109 | } 110 | 111 | pub(super) fn draw_texture_quads( 112 | &mut self, 113 | texture: &Texture, 114 | instances: &[gpu::Quad], 115 | ) { 116 | self.gpu.draw_texture_quads( 117 | texture, 118 | instances, 119 | &self.view, 120 | &self.transformation, 121 | ); 122 | } 123 | 124 | pub(in crate::graphics) fn draw_font(&mut self, font: &mut Font) { 125 | self.gpu.draw_font(font, &self.view, self.transformation); 126 | } 127 | } 128 | 129 | impl<'a> std::fmt::Debug for Target<'a> { 130 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 131 | write!(f, "Target {{ transformation: {:?} }}", self.transformation) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/graphics/text.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | 3 | use crate::graphics::{Color, Point}; 4 | 5 | /// A section of text. 6 | #[derive(Clone, PartialEq, Debug)] 7 | pub struct Text<'a> { 8 | /// Text content 9 | pub content: &'a str, 10 | 11 | /// Text position 12 | pub position: Point, 13 | 14 | /// Text bounds, in screen coordinates 15 | pub bounds: (f32, f32), 16 | 17 | /// Text size 18 | pub size: f32, 19 | 20 | /// Text color 21 | pub color: Color, 22 | 23 | /// Text horizontal alignment 24 | pub horizontal_alignment: HorizontalAlignment, 25 | 26 | /// Text vertical alignment 27 | pub vertical_alignment: VerticalAlignment, 28 | } 29 | 30 | impl Default for Text<'static> { 31 | #[inline] 32 | fn default() -> Text<'static> { 33 | Text { 34 | content: "", 35 | position: Point::new(0.0, 0.0), 36 | bounds: (f32::INFINITY, f32::INFINITY), 37 | size: 16.0, 38 | color: Color::BLACK, 39 | horizontal_alignment: HorizontalAlignment::Left, 40 | vertical_alignment: VerticalAlignment::Top, 41 | } 42 | } 43 | } 44 | 45 | /// The horizontal alignment of some resource. 46 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 47 | pub enum HorizontalAlignment { 48 | /// Align left 49 | Left, 50 | 51 | /// Horizontally centered 52 | Center, 53 | 54 | /// Align right 55 | Right, 56 | } 57 | 58 | /// The vertical alignment of some resource. 59 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 60 | pub enum VerticalAlignment { 61 | /// Align top 62 | Top, 63 | 64 | /// Vertically centered 65 | Center, 66 | 67 | /// Align bottom 68 | Bottom, 69 | } 70 | -------------------------------------------------------------------------------- /src/graphics/texture_array.rs: -------------------------------------------------------------------------------- 1 | //! Build, load, and use texture arrays. 2 | mod batch; 3 | mod builder; 4 | mod loader; 5 | 6 | pub use batch::Batch; 7 | pub use builder::Builder; 8 | pub use loader::{Indices, Key, Loader}; 9 | 10 | use std::fmt; 11 | use std::path::PathBuf; 12 | 13 | use crate::graphics::gpu::Texture; 14 | 15 | /// A collection of different textures with the same size. 16 | /// 17 | /// If you want to use different images to render multiple sprites efficiently, 18 | /// a [`TextureArray`] can do the job. 19 | /// 20 | /// You need to use a [`Builder`] or a [`Loader`] to create one. Use a [`Batch`] 21 | /// to draw it. 22 | /// 23 | /// Cloning a [`TextureArray`] is cheap, it only clones a handle. It does not 24 | /// create new copy of the texture on the GPU. 25 | /// 26 | /// [`TextureArray`]: struct.TextureArray.html 27 | /// [`Builder`]: struct.Builder.html 28 | /// [`Loader`]: struct.Loader.html 29 | /// [`Batch`]: struct.Batch.html 30 | #[derive(Debug, Clone)] 31 | pub struct TextureArray { 32 | texture: Texture, 33 | x_unit: f32, 34 | y_unit: f32, 35 | } 36 | 37 | /// An index that identifies a texture in a [`TextureArray`]. 38 | /// 39 | /// You will need this in order to draw using a [`Batch`]. 40 | /// 41 | /// [`TextureArray`]: struct.TextureArray.html 42 | /// [`Batch`]: struct.Batch.html 43 | #[derive(Debug, Clone, Copy, PartialEq)] 44 | pub struct Index { 45 | layer: u16, 46 | offset: Offset, 47 | } 48 | 49 | #[derive(Debug, Clone, Copy, PartialEq)] 50 | struct Offset { 51 | x: f32, 52 | y: f32, 53 | } 54 | 55 | /// A texture array loading error. 56 | #[derive(Debug, Clone)] 57 | pub enum Error { 58 | /// A texture array [`Index`] could not be found for the given [`Key`]. 59 | /// 60 | /// [`Key`]: struct.Key.html 61 | /// [`Index`]: struct.Index.html 62 | KeyNotFound(usize), 63 | 64 | /// A provided image did not fit in a texture array layer. 65 | ImageIsTooBig(PathBuf), 66 | } 67 | 68 | impl fmt::Display for Error { 69 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 70 | match self { 71 | Error::KeyNotFound(key) => write!(f, "Key not found: {}", key), 72 | Error::ImageIsTooBig(path) => { 73 | write!(f, "Image is too big: {}", path.display()) 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/graphics/texture_array/batch.rs: -------------------------------------------------------------------------------- 1 | use super::{Index, TextureArray}; 2 | use crate::graphics::{gpu, IntoQuad, Target}; 3 | 4 | /// A collection of quads that can be drawn with a [`TextureArray`] all at once. 5 | /// 6 | /// [`TextureArray`]: struct.TextureArray.html 7 | #[derive(Debug)] 8 | pub struct Batch { 9 | texture_array: TextureArray, 10 | instances: Vec, 11 | } 12 | 13 | impl Batch { 14 | /// Creates a new [`Batch`] from a [`TextureArray`]. 15 | /// 16 | /// [`Batch`]: struct.Batch.html 17 | /// [`TextureArray`]: struct.TextureArray.html 18 | pub fn new(texture_array: TextureArray) -> Batch { 19 | Batch { 20 | texture_array, 21 | instances: Vec::new(), 22 | } 23 | } 24 | 25 | /// Adds a quad to the [`Batch`] that will be rendered using the texture 26 | /// represented by the given [`Index`]. 27 | /// 28 | /// [`Batch`]: struct.Batch.html 29 | /// [`Index`]: struct.Index.html 30 | #[inline] 31 | pub fn add(&mut self, index: &Index, quad: Q) { 32 | let mut quad = quad 33 | .into_quad(self.texture_array.x_unit, self.texture_array.y_unit); 34 | 35 | quad.source.x += index.offset.x; 36 | quad.source.y += index.offset.y; 37 | 38 | let mut instance = gpu::Quad::from(quad); 39 | 40 | instance.layer = index.layer.into(); 41 | 42 | self.instances.push(instance); 43 | } 44 | 45 | /// Draws the [`Batch`] on the given [`Target`]. 46 | /// 47 | /// [`Batch`]: struct.Batch.html 48 | /// [`Target`]: ../struct.Target.html 49 | pub fn draw(&self, target: &mut Target<'_>) { 50 | target.draw_texture_quads( 51 | &self.texture_array.texture, 52 | &self.instances[..], 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/graphics/texture_array/loader.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use std::path::PathBuf; 3 | 4 | use super::{Builder, Index, TextureArray}; 5 | use crate::load::Task; 6 | use crate::{Error, Result}; 7 | 8 | /// A [`TextureArray`] builder that produces a [`Task`]. 9 | /// 10 | /// You should use [`add`] to get an index [`Key`] per texture so you can 11 | /// retrieve each [`Index`] from the provided [`Indices`] on [`finish`]. 12 | /// 13 | /// For example, let's say that we want to use a [`TextureArray`] for our 14 | /// entities. We could write in our `entity` module: 15 | /// 16 | /// ``` 17 | /// use coffee::load::Task; 18 | /// use coffee::graphics::texture_array::{TextureArray, Index, Loader}; 19 | /// 20 | /// pub struct Assets { 21 | /// player: Index, 22 | /// enemy: Index, 23 | /// building: Index, 24 | /// items: Index, 25 | /// texture: TextureArray, 26 | /// } 27 | /// 28 | /// impl Assets { 29 | /// pub fn load() -> Task { 30 | /// let mut loader = Loader::new(2048, 2048); 31 | /// 32 | /// let player = loader.add("player.png"); 33 | /// let enemy = loader.add("enemy.png"); 34 | /// let building = loader.add("building.png"); 35 | /// let items = loader.add("items.png"); 36 | /// 37 | /// loader.finish(move |texture, indices| Ok(Assets { 38 | /// player: indices.get(player)?, 39 | /// enemy: indices.get(enemy)?, 40 | /// building: indices.get(building)?, 41 | /// items: indices.get(items)?, 42 | /// texture, 43 | /// })) 44 | /// } 45 | /// } 46 | /// ``` 47 | /// 48 | /// [`TextureArray`]: struct.TextureArray.html 49 | /// [`Task`]: ../../load/struct.Task.html 50 | /// [`add`]: #method.add 51 | /// [`Key`]: struct.Key.html 52 | /// [`Index`]: struct.Index.html 53 | /// [`Indices`]: struct.Indices.html 54 | /// [`finish`]: #method.finish 55 | #[derive(Debug)] 56 | pub struct Loader { 57 | width: u16, 58 | height: u16, 59 | paths: Vec, 60 | } 61 | 62 | impl Loader { 63 | /// Creates a new [`Loader`] that produces a [`TextureArray`] of the given 64 | /// size. 65 | /// 66 | /// [`Loader`]: struct.Loader.html 67 | /// [`TextureArray`]: struct.TextureArray.html 68 | pub fn new(width: u16, height: u16) -> Loader { 69 | Loader { 70 | width, 71 | height, 72 | paths: Vec::new(), 73 | } 74 | } 75 | 76 | /// Queues an image to be added to the produced [`TextureArray`] and obtain 77 | /// a [`Key`] to its [`Index`]. 78 | /// 79 | /// [`TextureArray`]: struct.TextureArray.html 80 | /// [`Key`]: struct.Key.html 81 | /// [`Index`]: struct.Index.html 82 | pub fn add>(&mut self, path: P) -> Key { 83 | self.paths.push(path.into()); 84 | Key(self.paths.len() - 1) 85 | } 86 | 87 | /// Finishes the [`Loader`] definition and obtain a [`Task`] that produces 88 | /// a value from the loaded [`TextureArray`] and its [`Indices`]. 89 | /// 90 | /// [`Loader`]: struct.Loader.html 91 | /// [`Task`]: ../../load/struct.Task.html 92 | /// [`TextureArray`]: struct.TextureArray.html 93 | /// [`Indices`]: struct.Indices.html 94 | pub fn finish(self, on_completion: F) -> Task 95 | where 96 | F: 'static + Fn(TextureArray, Indices) -> Result, 97 | { 98 | let total_work = self.paths.len() as u32 + 1; 99 | 100 | Task::sequence(total_work, move |task| { 101 | let mut builder = Builder::new(self.width, self.height); 102 | let mut work_todo = VecDeque::from(self.paths.clone()); 103 | let mut indices = Vec::new(); 104 | 105 | while let Some(next) = work_todo.pop_front() { 106 | let index = builder.add(next)?; 107 | indices.push(index); 108 | 109 | task.notify_progress(1); 110 | } 111 | 112 | let result = 113 | on_completion(builder.build(task.gpu()), Indices(indices))?; 114 | 115 | task.notify_progress(1); 116 | 117 | Ok(result) 118 | }) 119 | } 120 | } 121 | 122 | /// A key used to obtain an [`Index`] from [`Indices`] once a [`TextureArray`] 123 | /// is loaded using a [`Loader`]. 124 | /// 125 | /// [`Key`]: struct.Key.html 126 | /// [`Index`]: struct.Index.html 127 | /// [`Indices`]: struct.Indices.html 128 | /// [`TextureArray`]: struct.TextureArray.html 129 | /// [`Loader`]: struct.Loader.html 130 | #[derive(Clone, Copy, Eq, PartialEq, Debug)] 131 | pub struct Key(usize); 132 | 133 | /// A set of loaded indices obtained when using a [`Loader`]. 134 | /// 135 | /// [`Loader`]: struct.Loader.html 136 | #[derive(Clone, PartialEq, Debug)] 137 | pub struct Indices(Vec); 138 | 139 | impl Indices { 140 | /// Get an [`Index`] for the given [`Key`]. 141 | /// 142 | /// [`Key`]: struct.Key.html 143 | /// [`Index`]: struct.Index.html 144 | pub fn get(&self, key: Key) -> Result { 145 | self.0 146 | .get(key.0) 147 | .cloned() 148 | .ok_or(Error::TextureArray(super::Error::KeyNotFound(key.0))) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/graphics/transformation.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::Matrix3; 2 | use std::ops::Mul; 3 | 4 | use crate::graphics::Vector; 5 | 6 | /// A 2D transformation matrix. 7 | /// 8 | /// It can be used to apply a transformation to a [`Target`]. 9 | /// 10 | /// [`Target`]: struct.Target.html 11 | #[derive(Debug, Clone, Copy, PartialEq)] 12 | pub struct Transformation(Matrix3); 13 | 14 | impl Transformation { 15 | /// Get the identity transformation. 16 | pub fn identity() -> Transformation { 17 | Transformation(Matrix3::identity()) 18 | } 19 | 20 | /// Creates an orthographic projection. 21 | /// 22 | /// You should rarely need this. On creation, a [`Target`] is automatically 23 | /// set up with the correct orthographic projection. 24 | /// 25 | /// [`Target`]: struct.Target.html 26 | #[rustfmt::skip] 27 | pub fn orthographic(width: f32, height: f32) -> Transformation { 28 | Transformation(nalgebra::Matrix3::new( 29 | 2.0 / width, 0.0, -1.0, 30 | 0.0, -2.0 / height, 1.0, 31 | 0.0, 0.0, 1.0 32 | )) 33 | } 34 | 35 | /// Creates a translate transformation. 36 | /// 37 | /// You can use this to pan your camera, for example. 38 | pub fn translate(translation: Vector) -> Transformation { 39 | Transformation(Matrix3::new_translation(&Vector::new( 40 | translation.x, 41 | translation.y, 42 | ))) 43 | } 44 | 45 | /// Creates a uniform scale transformation. 46 | /// 47 | /// You can use this to zoom your camera, for example. 48 | pub fn scale(scale: f32) -> Transformation { 49 | Transformation(Matrix3::new_scaling(scale)) 50 | } 51 | 52 | /// Creates a non-uniform scale transformation. 53 | /// 54 | /// It allows you to scale each axis independently. You should rarely need 55 | /// this. 56 | pub fn nonuniform_scale(scale: Vector) -> Transformation { 57 | Transformation(Matrix3::new_nonuniform_scaling(&scale)) 58 | } 59 | 60 | /// Creates a rotation transformation (in radians). 61 | /// 62 | /// You can use this to rotate your camera, for example. 63 | pub fn rotate(rotation: f32) -> Transformation { 64 | Transformation(Matrix3::new_rotation(rotation)) 65 | } 66 | } 67 | 68 | impl Mul for Transformation { 69 | type Output = Self; 70 | 71 | fn mul(self, rhs: Self) -> Self { 72 | Transformation(self.0 * rhs.0) 73 | } 74 | } 75 | 76 | impl From for [[f32; 4]; 4] { 77 | #[rustfmt::skip] 78 | fn from(t: Transformation) -> [[f32; 4]; 4] { 79 | [ 80 | [t.0[0], t.0[1], 0.0, t.0[2]], 81 | [t.0[3], t.0[4], 0.0, t.0[5]], 82 | [0.0, 0.0, -1.0, 0.0], 83 | [t.0[6], t.0[7], 0.0, t.0[8]], 84 | ] 85 | } 86 | } 87 | 88 | impl From for [f32; 16] { 89 | #[rustfmt::skip] 90 | fn from(t: Transformation) -> [f32; 16] { 91 | [ 92 | t.0[0], t.0[1], 0.0, t.0[2], 93 | t.0[3], t.0[4], 0.0, t.0[5], 94 | 0.0, 0.0, -1.0, 0.0, 95 | t.0[6], t.0[7], 0.0, t.0[8] 96 | ] 97 | } 98 | } 99 | 100 | impl From> for Transformation { 101 | fn from(matrix: Matrix3) -> Self { 102 | Transformation(matrix) 103 | } 104 | } 105 | 106 | impl Into> for Transformation { 107 | fn into(self) -> Matrix3 { 108 | self.0 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/graphics/vector.rs: -------------------------------------------------------------------------------- 1 | use nalgebra; 2 | 3 | /// A 2D vector. 4 | pub type Vector = nalgebra::Vector2; 5 | -------------------------------------------------------------------------------- /src/graphics/window.rs: -------------------------------------------------------------------------------- 1 | mod cursor_icon; 2 | mod frame; 3 | mod settings; 4 | 5 | pub(crate) use winit; 6 | 7 | pub use cursor_icon::CursorIcon; 8 | pub use frame::Frame; 9 | pub use settings::Settings; 10 | 11 | use crate::graphics::gpu::{self, Gpu}; 12 | use crate::Result; 13 | 14 | /// An open window. 15 | /// 16 | /// It is provided as an argument in some methods in [`Game`]. 17 | /// 18 | /// [`Game`]: ../trait.Game.html 19 | pub struct Window { 20 | gpu: Gpu, 21 | surface: gpu::Surface, 22 | width: f32, 23 | height: f32, 24 | is_fullscreen: bool, 25 | cursor_icon: Option, 26 | } 27 | 28 | impl Window { 29 | pub(crate) fn new( 30 | settings: Settings, 31 | event_loop: &winit::event_loop::EventLoop<()>, 32 | ) -> Result { 33 | let (width, height) = settings.size; 34 | let is_fullscreen = settings.fullscreen; 35 | 36 | let (gpu, surface) = 37 | Gpu::for_window(settings.into_builder(event_loop), event_loop)?; 38 | 39 | Ok(Window { 40 | is_fullscreen, 41 | gpu, 42 | surface, 43 | width: width as f32, 44 | height: height as f32, 45 | cursor_icon: Some(winit::window::CursorIcon::Default), 46 | }) 47 | } 48 | 49 | /// Returns the [`Gpu`] linked to the [`Window`]. 50 | /// 51 | /// [`Gpu`]: struct.Gpu.html 52 | /// [`Window`]: struct.Window.html 53 | pub fn gpu(&mut self) -> &mut Gpu { 54 | &mut self.gpu 55 | } 56 | 57 | pub(crate) fn frame(&mut self) -> Frame<'_> { 58 | Frame::new(self) 59 | } 60 | 61 | /// Toggles the [`Window`]'s fullscreen state. 62 | /// 63 | /// [`Window`]: struct.Window.html 64 | pub fn toggle_fullscreen(&mut self) { 65 | let window = self.surface.window(); 66 | 67 | let monitor = if self.is_fullscreen { 68 | None 69 | } else { 70 | Some(window.primary_monitor()) 71 | }; 72 | 73 | window 74 | .set_fullscreen(monitor.map(winit::window::Fullscreen::Borderless)); 75 | 76 | self.is_fullscreen = !self.is_fullscreen; 77 | } 78 | 79 | /// Returns the width of the [`Window`]. 80 | /// 81 | /// [`Window`]: struct.Window.html 82 | pub fn width(&self) -> f32 { 83 | self.width 84 | } 85 | 86 | /// Returns the height of the [`Window`]. 87 | /// 88 | /// [`Window`]: struct.Window.html 89 | pub fn height(&self) -> f32 { 90 | self.height 91 | } 92 | 93 | pub(crate) fn swap_buffers(&mut self) { 94 | self.surface.swap_buffers(&mut self.gpu); 95 | } 96 | 97 | pub(crate) fn request_redraw(&mut self) { 98 | self.surface.request_redraw(); 99 | } 100 | 101 | pub(crate) fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { 102 | self.surface.resize(&mut self.gpu, new_size); 103 | 104 | self.width = new_size.width as f32; 105 | self.height = new_size.height as f32; 106 | } 107 | 108 | pub(crate) fn update_cursor( 109 | &mut self, 110 | new_cursor: Option, 111 | ) { 112 | if self.cursor_icon != new_cursor { 113 | if let Some(cursor_icon) = new_cursor { 114 | self.surface.window().set_cursor_icon(cursor_icon); 115 | } 116 | self.surface 117 | .window() 118 | .set_cursor_visible(new_cursor.is_some()); 119 | self.cursor_icon = new_cursor; 120 | } 121 | } 122 | } 123 | 124 | impl std::fmt::Debug for Window { 125 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 126 | write!( 127 | f, 128 | "Window {{ width: {}, height: {} }}", 129 | self.width, self.height 130 | ) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/graphics/window/cursor_icon.rs: -------------------------------------------------------------------------------- 1 | use crate::graphics::window::winit; 2 | use std::convert::TryFrom; 3 | 4 | /// Describes the appearance of the mouse cursor. 5 | #[derive(Debug, Clone, Copy, PartialEq)] 6 | pub enum CursorIcon { 7 | /// The platform-dependent default cursor. 8 | Default, 9 | /// A simple crosshair. 10 | Crosshair, 11 | /// A hand (often used to indicate links in web browsers). 12 | Hand, 13 | /// Hides the cursor. 14 | Hidden, 15 | /// Indicates something is to be moved. 16 | Move, 17 | } 18 | 19 | impl Default for CursorIcon { 20 | fn default() -> Self { 21 | Self::Default 22 | } 23 | } 24 | 25 | impl TryFrom for winit::window::CursorIcon { 26 | type Error = (); 27 | 28 | fn try_from( 29 | cursor_icon: CursorIcon, 30 | ) -> Result { 31 | match cursor_icon { 32 | CursorIcon::Default => Ok(winit::window::CursorIcon::Default), 33 | CursorIcon::Crosshair => Ok(winit::window::CursorIcon::Crosshair), 34 | CursorIcon::Hand => Ok(winit::window::CursorIcon::Hand), 35 | CursorIcon::Hidden => Err(()), 36 | CursorIcon::Move => Ok(winit::window::CursorIcon::Move), 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/graphics/window/event.rs: -------------------------------------------------------------------------------- 1 | use super::winit; 2 | use crate::input::{self, keyboard, mouse, window}; 3 | 4 | pub(crate) enum Event { 5 | CloseRequested, 6 | Resized(winit::dpi::LogicalSize), 7 | Input(input::Event), 8 | CursorMoved(winit::dpi::LogicalPosition), 9 | Moved(winit::dpi::LogicalPosition), 10 | } 11 | 12 | pub struct EventLoop(winit::EventsLoop); 13 | 14 | impl EventLoop { 15 | pub fn new() -> Self { 16 | Self(winit::EventsLoop::new()) 17 | } 18 | 19 | pub(super) fn raw(&self) -> &winit::EventsLoop { 20 | &self.0 21 | } 22 | 23 | pub(crate) fn poll(&mut self, mut f: F) 24 | where 25 | F: FnMut(Event), 26 | { 27 | self.0.poll_events(|event| { 28 | match event { 29 | winit::Event::WindowEvent { event, .. } => match event { 30 | winit::WindowEvent::KeyboardInput { 31 | input: 32 | winit::KeyboardInput { 33 | state, 34 | virtual_keycode: Some(key_code), 35 | .. 36 | }, 37 | .. 38 | } => { 39 | f(Event::Input(input::Event::Keyboard( 40 | keyboard::Event::Input { state, key_code }, 41 | ))); 42 | } 43 | winit::WindowEvent::ReceivedCharacter(codepoint) => { 44 | f(Event::Input(input::Event::Keyboard( 45 | keyboard::Event::TextEntered { 46 | character: codepoint, 47 | }, 48 | ))) 49 | } 50 | winit::WindowEvent::MouseInput { 51 | state, button, .. 52 | } => f(Event::Input(input::Event::Mouse( 53 | mouse::Event::Input { state, button }, 54 | ))), 55 | winit::WindowEvent::MouseWheel { delta, .. } => match delta 56 | { 57 | winit::MouseScrollDelta::LineDelta(x, y) => { 58 | f(Event::Input(input::Event::Mouse( 59 | mouse::Event::WheelScrolled { 60 | delta_x: x, 61 | delta_y: y, 62 | }, 63 | ))) 64 | } 65 | _ => {} 66 | }, 67 | winit::WindowEvent::CursorMoved { position, .. } => { 68 | f(Event::CursorMoved(position)) 69 | } 70 | winit::WindowEvent::CursorEntered { .. } => { 71 | f(Event::Input(input::Event::Mouse( 72 | mouse::Event::CursorEntered, 73 | ))) 74 | } 75 | winit::WindowEvent::CursorLeft { .. } => f(Event::Input( 76 | input::Event::Mouse(mouse::Event::CursorLeft), 77 | )), 78 | winit::WindowEvent::CloseRequested { .. } => { 79 | f(Event::CloseRequested) 80 | } 81 | winit::WindowEvent::Resized(logical_size) => { 82 | f(Event::Resized(logical_size)) 83 | } 84 | winit::WindowEvent::Focused(focus) => { 85 | f(Event::Input(if focus == true { 86 | input::Event::Window(window::Event::Focused) 87 | } else { 88 | input::Event::Window(window::Event::Unfocused) 89 | })) 90 | } 91 | winit::WindowEvent::Moved(position) => { 92 | f(Event::Moved(position)) 93 | } 94 | _ => {} 95 | }, 96 | _ => (), 97 | }; 98 | }); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/graphics/window/frame.rs: -------------------------------------------------------------------------------- 1 | use super::Window; 2 | 3 | use crate::graphics::{Color, Gpu, Target}; 4 | 5 | /// The next frame of your game. 6 | /// 7 | /// You can only get a [`Frame`] by using [`Window::frame`]. 8 | /// 9 | /// This type is useful to define explicit rendering function signatures. If 10 | /// a function should never render off-screen, consider taking a `Frame` as an 11 | /// argument instead of a generic [`Target`]. 12 | /// 13 | /// [`Frame`]: struct.Frame.html 14 | /// [`Window::frame`]: struct.Window.html#method.frame 15 | /// [`Target`]: struct.Target.html 16 | #[derive(Debug)] 17 | pub struct Frame<'a> { 18 | window: &'a mut Window, 19 | } 20 | 21 | impl<'a> Frame<'a> { 22 | pub(crate) fn new(window: &mut Window) -> Frame<'_> { 23 | Frame { window } 24 | } 25 | 26 | /// Get the [`Gpu`] linked to the [`Window`] of this [`Frame`]. 27 | /// 28 | /// [`Gpu`]: struct.Gpu.html 29 | /// [`Window`]: struct.Window.html 30 | /// [`Frame`]: struct.Frame.html 31 | pub fn gpu(&mut self) -> &mut Gpu { 32 | self.window.gpu() 33 | } 34 | 35 | /// Get the width of the frame. 36 | pub fn width(&self) -> f32 { 37 | self.window.width 38 | } 39 | 40 | /// Get the height of the frame. 41 | pub fn height(&self) -> f32 { 42 | self.window.height 43 | } 44 | 45 | /// See the frame as a [`Target`]. 46 | /// 47 | /// You will need to use this in order to render some resources to it. 48 | /// 49 | /// [`Target`]: struct.Target.html 50 | pub fn as_target(&mut self) -> Target<'_> { 51 | let Window { 52 | surface, 53 | gpu, 54 | width, 55 | height, 56 | .. 57 | } = &mut self.window; 58 | 59 | let view = surface.target(); 60 | 61 | Target::new(gpu, view, *width, *height) 62 | } 63 | 64 | /// Clear the frame with the given [`Color`]. 65 | /// 66 | /// [`Color`]: struct.Color.html 67 | pub fn clear(&mut self, color: Color) { 68 | self.as_target().clear(color); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/graphics/window/settings.rs: -------------------------------------------------------------------------------- 1 | use super::winit; 2 | 3 | /// A window configuration. 4 | #[derive(Debug, Eq, PartialEq, Clone)] 5 | pub struct Settings { 6 | /// A title for the window. 7 | pub title: String, 8 | 9 | /// A target size for the window. 10 | pub size: (u32, u32), 11 | 12 | /// Defines whether or not the window should be resizable. 13 | pub resizable: bool, 14 | 15 | /// Defines whether or not the window should start in fullscreen mode. 16 | pub fullscreen: bool, 17 | 18 | /// Defines whether or not the window should start maximized. 19 | pub maximized: bool, 20 | } 21 | 22 | impl Settings { 23 | pub(super) fn into_builder( 24 | self, 25 | events_loop: &winit::event_loop::EventLoop<()>, 26 | ) -> winit::window::WindowBuilder { 27 | let monitor = if self.fullscreen { 28 | Some(events_loop.primary_monitor()) 29 | } else { 30 | None 31 | }; 32 | 33 | winit::window::WindowBuilder::new() 34 | .with_title(self.title) 35 | .with_inner_size(winit::dpi::PhysicalSize { 36 | width: self.size.0, 37 | height: self.size.1, 38 | }) 39 | .with_resizable(self.resizable) 40 | .with_fullscreen(monitor.map(winit::window::Fullscreen::Borderless)) 41 | .with_maximized(self.maximized) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/input.rs: -------------------------------------------------------------------------------- 1 | //! Allow players to interact with your game. 2 | 3 | pub mod gamepad; 4 | pub mod keyboard; 5 | pub mod mouse; 6 | pub mod window; 7 | 8 | mod event; 9 | mod keyboard_and_mouse; 10 | 11 | pub use crate::graphics::window::winit::event::ElementState as ButtonState; 12 | pub use event::Event; 13 | pub use keyboard::Keyboard; 14 | pub use keyboard_and_mouse::KeyboardAndMouse; 15 | pub use mouse::Mouse; 16 | 17 | /// The input of your [`Game`]. 18 | /// 19 | /// If you just want simple access to the keyboard and mouse, check out the 20 | /// built-in [`KeyboardAndMouse`] type. 21 | /// 22 | /// [`Game`]: ../trait.Game.html 23 | /// [`KeyboardAndMouse`]: struct.KeyboardAndMouse.html 24 | pub trait Input { 25 | /// Creates a new [`Input`]. 26 | /// 27 | /// [`Input`]: trait.Input.html 28 | fn new() -> Self; 29 | 30 | /// Processes an input event. 31 | /// 32 | /// This function may be called multiple times during event processing, 33 | /// before [`Game::interact`]. 34 | /// 35 | /// [`Game::interact`]: ../trait.Game.html#method.interact 36 | fn update(&mut self, event: Event); 37 | 38 | /// Clears any temporary state that should be consumed by [`Game::interact`] 39 | /// and could accumulate otherwise. 40 | /// 41 | /// This method will be called after each [`Game::interact`]. 42 | /// 43 | /// [`Game::interact`]: ../trait.Game.html#method.interact 44 | fn clear(&mut self); 45 | } 46 | 47 | impl Input for () { 48 | fn new() {} 49 | 50 | fn update(&mut self, _event: Event) {} 51 | 52 | fn clear(&mut self) {} 53 | } 54 | -------------------------------------------------------------------------------- /src/input/event.rs: -------------------------------------------------------------------------------- 1 | use crate::input::{gamepad, keyboard, mouse, window}; 2 | 3 | use std::time::SystemTime; 4 | 5 | /// An input event. 6 | /// 7 | /// Input events in your [`Game`] are processed by the [`Game::Input`] associated 8 | /// type. 9 | /// 10 | /// You can use your own input handler by implementing the [`Input`] trait. 11 | /// 12 | /// [`Game`]: ../trait.Game.html 13 | /// [`Game::Input`]: ../trait.Game.html#associatedtype.Input 14 | /// [`Input`]: trait.Input.html 15 | #[derive(PartialEq, Clone, Copy, Debug)] 16 | pub enum Event { 17 | /// A keyboard event 18 | Keyboard(keyboard::Event), 19 | 20 | /// A mouse event 21 | Mouse(mouse::Event), 22 | 23 | /// A gamepad event 24 | Gamepad { 25 | /// The gamepad identifier 26 | id: gamepad::Id, 27 | 28 | /// The gamepad event 29 | event: gamepad::Event, 30 | 31 | /// The time of the event 32 | time: SystemTime, 33 | }, 34 | 35 | /// A window event 36 | Window(window::Event), 37 | } 38 | -------------------------------------------------------------------------------- /src/input/gamepad.rs: -------------------------------------------------------------------------------- 1 | //! Listen to gamepad events. 2 | 3 | mod event; 4 | 5 | pub use event::Event; 6 | 7 | pub use gilrs::Axis; 8 | pub use gilrs::Button; 9 | 10 | use gilrs::Gilrs; 11 | use std::convert::TryInto; 12 | use std::time::SystemTime; 13 | 14 | /// A gamepad identifier. 15 | #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] 16 | pub struct Id(gilrs::GamepadId); 17 | 18 | pub(crate) struct Tracker { 19 | context: Gilrs, 20 | } 21 | 22 | impl Tracker { 23 | pub fn new() -> Option { 24 | match Gilrs::new() { 25 | Ok(context) => Some(Tracker { context }), 26 | Err(gilrs::Error::NotImplemented(dummy_context)) => { 27 | // Use the dummy context as a fallback on unsupported platforms 28 | Some(Tracker { 29 | context: dummy_context, 30 | }) 31 | } 32 | _ => { 33 | // Either `gilrs::error::InvalidAxisToBtn` has occured, or a 34 | // platform specific error has occured. 35 | None 36 | } 37 | } 38 | } 39 | 40 | pub fn next_event(&mut self) -> Option<(Id, Event, SystemTime)> { 41 | while let Some(gilrs::Event { id, event, time }) = 42 | self.context.next_event() 43 | { 44 | match event.try_into() { 45 | Ok(gamepad_event) => { 46 | return Some((Id(id), gamepad_event, time)); 47 | } 48 | Err(_) => {} 49 | } 50 | } 51 | 52 | None 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/input/gamepad/event.rs: -------------------------------------------------------------------------------- 1 | use super::{Axis, Button}; 2 | 3 | use std::convert::TryFrom; 4 | 5 | /// A gamepad event. 6 | #[derive(Debug, Clone, Copy, PartialEq)] 7 | pub enum Event { 8 | /// A gamepad was connected. 9 | Connected, 10 | 11 | /// A gamepad was disconnected. 12 | Disconnected, 13 | 14 | /// A button was pressed. 15 | ButtonPressed(Button), 16 | 17 | /// A button was released. 18 | ButtonReleased(Button), 19 | 20 | /// The value of a button was changed. 21 | ButtonChanged(Button, f32), 22 | 23 | /// The value of an axis was changed. 24 | AxisChanged(Axis, f32), 25 | } 26 | 27 | impl TryFrom for Event { 28 | type Error = (); 29 | 30 | fn try_from(event_type: gilrs::EventType) -> Result { 31 | match event_type { 32 | gilrs::EventType::Connected => Ok(Event::Connected), 33 | gilrs::EventType::Disconnected => Ok(Event::Disconnected), 34 | gilrs::EventType::ButtonPressed(button, _) => { 35 | Ok(Event::ButtonPressed(button)) 36 | } 37 | gilrs::EventType::ButtonReleased(button, _) => { 38 | Ok(Event::ButtonReleased(button)) 39 | } 40 | gilrs::EventType::ButtonChanged(button, value, _) => { 41 | Ok(Event::ButtonChanged(button, value)) 42 | } 43 | gilrs::EventType::AxisChanged(axis, value, _) => { 44 | Ok(Event::AxisChanged(axis, value)) 45 | } 46 | gilrs::EventType::ButtonRepeated(_, _) => Err(()), 47 | gilrs::EventType::Dropped => Err(()), 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/input/keyboard.rs: -------------------------------------------------------------------------------- 1 | //! Listen to keyboard events. 2 | 3 | mod event; 4 | 5 | pub use crate::graphics::window::winit::event::VirtualKeyCode as KeyCode; 6 | pub use event::Event; 7 | 8 | use super::{ButtonState, Event as InputEvent, Input}; 9 | 10 | use std::collections::HashSet; 11 | 12 | /// A simple keyboard input tracker. 13 | /// 14 | /// You can use this as your [`Game::Input`] directly! 15 | /// 16 | /// [`Game::Input`]: ../trait.Game.html#associatedtype.Input 17 | #[derive(Debug, Clone)] 18 | pub struct Keyboard { 19 | pressed_keys: HashSet, 20 | released_keys: HashSet, 21 | } 22 | 23 | impl Keyboard { 24 | /// Returns true if the given key is currently pressed. 25 | pub fn is_key_pressed(&self, key_code: KeyCode) -> bool { 26 | self.pressed_keys.contains(&key_code) 27 | } 28 | 29 | /// Returns true if the given key was released during the last interaction. 30 | pub fn was_key_released(&self, key_code: KeyCode) -> bool { 31 | self.released_keys.contains(&key_code) 32 | } 33 | } 34 | 35 | impl Input for Keyboard { 36 | fn new() -> Keyboard { 37 | Keyboard { 38 | pressed_keys: HashSet::new(), 39 | released_keys: HashSet::new(), 40 | } 41 | } 42 | 43 | fn update(&mut self, event: InputEvent) { 44 | match event { 45 | InputEvent::Mouse { .. } => { 46 | // Ignore mouse events... 47 | } 48 | InputEvent::Keyboard(keyboard_event) => match keyboard_event { 49 | Event::Input { key_code, state } => { 50 | match state { 51 | ButtonState::Pressed => { 52 | let _ = self.pressed_keys.insert(key_code); 53 | } 54 | ButtonState::Released => { 55 | let _ = self.pressed_keys.remove(&key_code); 56 | let _ = self.released_keys.insert(key_code); 57 | } 58 | }; 59 | } 60 | Event::TextEntered { .. } => {} 61 | }, 62 | InputEvent::Gamepad { .. } => { 63 | // Ignore gamepad events... 64 | } 65 | InputEvent::Window { .. } => { 66 | // Ignore window events... 67 | } 68 | } 69 | } 70 | 71 | fn clear(&mut self) { 72 | self.released_keys.clear(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/input/keyboard/event.rs: -------------------------------------------------------------------------------- 1 | use super::KeyCode; 2 | use crate::input::ButtonState; 3 | 4 | #[derive(Debug, Clone, Copy, PartialEq)] 5 | /// A keyboard event. 6 | pub enum Event { 7 | /// A keyboard key was pressed or released. 8 | Input { 9 | /// The state of the key 10 | state: ButtonState, 11 | 12 | /// The key identifier 13 | key_code: KeyCode, 14 | }, 15 | 16 | /// Text was entered. 17 | TextEntered { 18 | /// The character entered 19 | character: char, 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /src/input/keyboard_and_mouse.rs: -------------------------------------------------------------------------------- 1 | use super::keyboard::Keyboard; 2 | use super::mouse::Mouse; 3 | use super::{Event, Input}; 4 | 5 | /// A simple keyboard and mouse input tracker. 6 | /// 7 | /// You can use this as your [`Game::Input`] directly! 8 | /// 9 | /// [`Game::Input`]: ../trait.Game.html#associatedtype.Input 10 | #[derive(Debug, Clone)] 11 | pub struct KeyboardAndMouse { 12 | mouse: Mouse, 13 | keyboard: Keyboard, 14 | } 15 | 16 | impl KeyboardAndMouse { 17 | /// Returns the [`Mouse`] input. 18 | /// 19 | /// [`Mouse`]: mouse/struct.Mouse.html 20 | pub fn mouse(&self) -> &Mouse { 21 | &self.mouse 22 | } 23 | 24 | /// Returns the [`Keyboard`] input. 25 | /// 26 | /// [`Keyboard`]: keyboard/struct.Keyboard.html 27 | pub fn keyboard(&self) -> &Keyboard { 28 | &self.keyboard 29 | } 30 | } 31 | 32 | impl Input for KeyboardAndMouse { 33 | fn new() -> KeyboardAndMouse { 34 | KeyboardAndMouse { 35 | mouse: Mouse::new(), 36 | keyboard: Keyboard::new(), 37 | } 38 | } 39 | 40 | fn update(&mut self, event: Event) { 41 | self.mouse.update(event); 42 | self.keyboard.update(event); 43 | } 44 | 45 | fn clear(&mut self) { 46 | self.mouse.clear(); 47 | self.keyboard.clear(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/input/mouse.rs: -------------------------------------------------------------------------------- 1 | //! Listen to mouse events. 2 | 3 | mod event; 4 | mod wheel_movement; 5 | 6 | pub use crate::graphics::window::winit::event::MouseButton as Button; 7 | pub use event::Event; 8 | pub use wheel_movement::WheelMovement; 9 | 10 | use super::{ButtonState, Event as InputEvent, Input}; 11 | use crate::graphics::Point; 12 | 13 | use std::collections::{HashMap, HashSet}; 14 | 15 | /// A simple mouse input tracker. 16 | /// 17 | /// You can use this as your [`Game::Input`] directly! 18 | /// 19 | /// [`Game::Input`]: ../trait.Game.html#associatedtype.Input 20 | #[derive(Debug, Clone)] 21 | pub struct Mouse { 22 | cursor_position: Point, 23 | wheel_movement: WheelMovement, 24 | is_cursor_taken: bool, 25 | is_cursor_within_window: bool, 26 | button_clicks: HashMap>, 27 | pressed_buttons: HashSet