Compare commits
1 Commits
master
...
text_sprit
| Author | SHA1 | Date | |
|---|---|---|---|
| 983f892895 |
@ -1,5 +0,0 @@
|
||||
[*.rs]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
max_line_length = 100
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,6 +1,3 @@
|
||||
/target
|
||||
*.log
|
||||
*.bkp
|
||||
*.perf
|
||||
*.data
|
||||
*.html
|
||||
|
||||
241
Cargo.lock
generated
241
Cargo.lock
generated
@ -147,7 +147,7 @@ dependencies = [
|
||||
"cgmath",
|
||||
"chrono",
|
||||
"fern",
|
||||
"fontdue",
|
||||
"font-kit",
|
||||
"image",
|
||||
"log",
|
||||
"pollster",
|
||||
@ -207,6 +207,15 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db34956e100b30725f2eb215f90d4871051239535632f84fea3bc92722c66b7c"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cocoa"
|
||||
version = "0.24.1"
|
||||
@ -265,6 +274,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-cstr"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6"
|
||||
|
||||
[[package]]
|
||||
name = "copyless"
|
||||
version = "0.1.5"
|
||||
@ -340,6 +355,18 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-text"
|
||||
version = "19.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25"
|
||||
dependencies = [
|
||||
"core-foundation 0.9.3",
|
||||
"core-graphics 0.22.3",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-video-sys"
|
||||
version = "0.1.4"
|
||||
@ -458,6 +485,27 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-next"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch"
|
||||
version = "0.2.0"
|
||||
@ -479,6 +527,18 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
|
||||
|
||||
[[package]]
|
||||
name = "dwrote"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"winapi",
|
||||
"wio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fern"
|
||||
version = "0.6.1"
|
||||
@ -499,6 +559,12 @@ dependencies = [
|
||||
"miniz_oxide 0.5.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "float-ord"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@ -506,13 +572,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "fontdue"
|
||||
version = "0.7.2"
|
||||
name = "font-kit"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253"
|
||||
checksum = "21fe28504d371085fae9ac7a3450f0b289ab71e07c8e57baa3fb68b9e57d6ce5"
|
||||
dependencies = [
|
||||
"hashbrown 0.11.2",
|
||||
"ttf-parser",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"core-foundation 0.9.3",
|
||||
"core-graphics 0.22.3",
|
||||
"core-text",
|
||||
"dirs-next",
|
||||
"dwrote",
|
||||
"float-ord",
|
||||
"freetype",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"pathfinder_geometry",
|
||||
"pathfinder_simd",
|
||||
"walkdir",
|
||||
"winapi",
|
||||
"yeslogic-fontconfig-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -530,6 +611,27 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "freetype"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bee38378a9e3db1cc693b4f88d166ae375338a0ff75cb8263e1c601d51f35dc6"
|
||||
dependencies = [
|
||||
"freetype-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "freetype-sys"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a"
|
||||
dependencies = [
|
||||
"cmake",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxhash"
|
||||
version = "0.2.1"
|
||||
@ -589,7 +691,7 @@ checksum = "0b0c02e1ba0bdb14e965058ca34e09c020f8e507a760df1121728e0aef68d57a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"gpu-descriptor-types",
|
||||
"hashbrown 0.12.3",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -601,15 +703,6 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
@ -686,7 +779,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1104,12 +1197,41 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathfinder_geometry"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pathfinder_simd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathfinder_simd"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39fe46acc5503595e5949c17b818714d26fdf9b4920eacf3b2947f0199f4a6ff"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a528564cc62c19a7acac4d81e01f39e53e25e17b934878f4c6d25cc2836e62f8"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.26"
|
||||
@ -1193,6 +1315,17 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"redox_syscall",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "renderdoc-sys"
|
||||
version = "0.7.1"
|
||||
@ -1205,6 +1338,24 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
@ -1223,6 +1374,24 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
|
||||
dependencies = [
|
||||
"pest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.147"
|
||||
@ -1340,10 +1509,10 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.15.2"
|
||||
name = "ucd-trie"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd"
|
||||
checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
@ -1369,6 +1538,17 @@ version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
@ -1745,6 +1925,15 @@ dependencies = [
|
||||
"x11-dl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wio"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11-dl"
|
||||
version = "2.20.0"
|
||||
@ -1770,3 +1959,15 @@ name = "xml-rs"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
||||
|
||||
[[package]]
|
||||
name = "yeslogic-fontconfig-sys"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2bbd69036d397ebbff671b1b8e4d918610c181c5a16073b96f984a38d08c386"
|
||||
dependencies = [
|
||||
"const-cstr",
|
||||
"dlib",
|
||||
"once_cell",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
@ -13,7 +13,7 @@ fern = { version = "^0.6.1", features = ["colored"] }
|
||||
bitflags = "^1.3.2"
|
||||
cgmath = { version = "^0.18.0", features = ["bytemuck"] }
|
||||
pollster = "^0.2.5"
|
||||
fontdue = "0.7.2"
|
||||
font-kit = "^0.11.0"
|
||||
|
||||
# surface creation
|
||||
winit = "^0.26.1"
|
||||
@ -34,6 +34,3 @@ features = ["png", "jpeg"]
|
||||
[patch.crates-io]
|
||||
cgmath = { path = "cgmath" }
|
||||
|
||||
[profile.release]
|
||||
debug = 1
|
||||
|
||||
|
||||
Binary file not shown.
@ -28,7 +28,7 @@ pub trait Application<S> {
|
||||
//--Canvas struct-----------------------------------------------------------------------------------
|
||||
pub struct Canvas {
|
||||
default_texture: TextureHandle,
|
||||
clear_color: Color,
|
||||
clear_color: Pixel,
|
||||
|
||||
renderer: WgpuRenderer,
|
||||
}
|
||||
@ -80,12 +80,10 @@ impl Canvas {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn create_text_sprite(&mut self, text: &'static str, size: Size, text_size: f32)
|
||||
-> TextSprite {
|
||||
pub fn create_text_sprite(&mut self, text: &'static str, size: Size) -> TextSprite {
|
||||
TextSprite::new(
|
||||
text,
|
||||
size,
|
||||
text_size,
|
||||
&self.renderer,
|
||||
)
|
||||
}
|
||||
@ -149,7 +147,7 @@ impl Canvas {
|
||||
sprite.render(&mut self.renderer);
|
||||
}
|
||||
|
||||
pub fn set_clear_color(&mut self, color: Color) {
|
||||
pub fn set_clear_color(&mut self, color: Pixel) {
|
||||
self.clear_color = color;
|
||||
}
|
||||
|
||||
|
||||
@ -106,7 +106,7 @@ mod tests {
|
||||
message
|
||||
))
|
||||
})
|
||||
.level(log::LevelFilter::Debug)
|
||||
.level(log::LevelFilter::Trace)
|
||||
.chain(std::io::stdout())
|
||||
.chain(fern::log_file("output.log")?)
|
||||
.apply()?;
|
||||
|
||||
89
src/main.rs
89
src/main.rs
@ -40,13 +40,11 @@ struct ExampleState {
|
||||
pub texture: TextureHandle,
|
||||
pub tex_sprite: TextureSprite,
|
||||
pub sub_sprite: TextureSprite,
|
||||
pub sub_sprite2: TextureSprite,
|
||||
pub txt_sprite: TextSprite,
|
||||
pub last_instant: Instant,
|
||||
pub last_offset: u32,
|
||||
pub last_pos: Position,
|
||||
pub last_rot: f32,
|
||||
pub frame_counter: u8,
|
||||
}
|
||||
|
||||
struct ExampleApp {}
|
||||
@ -54,35 +52,18 @@ struct ExampleApp {}
|
||||
impl Application<ExampleState> for ExampleApp {
|
||||
|
||||
fn init(canvas: &mut Canvas) -> Result<ExampleState, &'static str> {
|
||||
use canvas::{
|
||||
utils::{Size, Color},
|
||||
sprite::{Sprite, Center},
|
||||
};
|
||||
use canvas::utils::Size;
|
||||
|
||||
//// 20 x 20 sprite of a picture
|
||||
let texture = canvas.create_texture_from_file("assets/camel.jpg", None, None, None)
|
||||
.unwrap();
|
||||
let mut tex_sprite = canvas.create_texture_sprite(Size {w: 1280, h: 720});
|
||||
tex_sprite.set_texture(texture.clone(), Some(Position {x: 0, y: 0}), 1.0);
|
||||
tex_sprite.set_center(Center::BotLeft);
|
||||
tex_sprite.set_position(Position {x: 0, y: 0});
|
||||
|
||||
let mut sub_sprite = canvas.create_texture_sprite(Size {w: 100, h: 100});
|
||||
sub_sprite.set_texture(texture.clone(), Some(Position {x: 0, y: 0}), 1.0);
|
||||
sub_sprite.set_position(Position {x: 100, y: 100});
|
||||
sub_sprite.set_scale(2.0);
|
||||
let mut sub_sprite = canvas.create_texture_sprite(Size {w: 200, h: 200});
|
||||
sub_sprite.set_texture(texture.clone(), Some(Position {x: 350, y: 0}), 1.0);
|
||||
|
||||
let mut sub_sprite2 = canvas.create_texture_sprite(Size {w: 200, h: 200});
|
||||
sub_sprite2.set_texture(texture.clone(), Some(Position {x: 100, y: 0}), 1.0);
|
||||
sub_sprite2.set_center(Center::Custom(Position {x:100, y:100}));
|
||||
sub_sprite2.set_rotation(0.0);
|
||||
sub_sprite2.set_position(Position {x: 100, y: 100});
|
||||
|
||||
let mut txt_sprite = canvas.create_text_sprite("00", Size {w: 100, h: 100}, 22.0);
|
||||
txt_sprite.set_center(Center::BotLeft);
|
||||
txt_sprite.set_position(Position {x:100, y:100});
|
||||
txt_sprite.set_bg_color(Color::WHITE);
|
||||
txt_sprite.set_scale(1.0);
|
||||
let txt_sprite = canvas.create_text_sprite(".", Size {w: 10, h: 10});
|
||||
|
||||
canvas.clear();
|
||||
canvas.update();
|
||||
@ -93,54 +74,33 @@ impl Application<ExampleState> for ExampleApp {
|
||||
texture,
|
||||
tex_sprite,
|
||||
sub_sprite,
|
||||
sub_sprite2,
|
||||
txt_sprite,
|
||||
last_instant,
|
||||
last_offset: 0,
|
||||
last_pos: Position {x: 0, y: 0},
|
||||
last_pos: Position::origin(),
|
||||
last_rot: 0.0,
|
||||
frame_counter: 0,
|
||||
})
|
||||
}
|
||||
|
||||
fn tick(state: &mut ExampleState, canvas: &mut Canvas) -> Result<(), &'static str> {
|
||||
use canvas::{
|
||||
sprite::Sprite,
|
||||
utils::Color,
|
||||
};
|
||||
use canvas::sprite::Sprite;
|
||||
|
||||
let now = Instant::now();
|
||||
let elapsed = now.duration_since(state.last_instant).as_millis();
|
||||
state.last_instant = now;
|
||||
debug!("frame time: {}ms", elapsed);
|
||||
state.frame_counter += 1;
|
||||
if state.frame_counter >= 60 {
|
||||
state.frame_counter = 0;
|
||||
state.txt_sprite.set_text(&format!("{}", elapsed));
|
||||
state.txt_sprite.set_text_size(elapsed as f32);
|
||||
}
|
||||
match state.frame_counter {
|
||||
0 => state.txt_sprite.set_fg_color(Color::RED),
|
||||
20 => state.txt_sprite.set_fg_color(Color::BLUE),
|
||||
40 => state.txt_sprite.set_fg_color(Color::GREEN),
|
||||
_ => (),
|
||||
}
|
||||
//state.sub_sprite.for_each(|pix| unsafe {pix.flat = pix.flat.wrapping_add(1);});
|
||||
//let now = Instant::now();
|
||||
//let elapsed = now.duration_since(state.last_instant).as_millis();
|
||||
//state.last_instant = now;
|
||||
//debug!("frame time: {}ms", elapsed);
|
||||
|
||||
// state.last_offset += 1;
|
||||
//state.last_pos.x += 1;
|
||||
////state.sub_sprite.for_each(|pix| unsafe {pix.flat = pix.flat.wrapping_add(1);});
|
||||
|
||||
//state.last_offset += 1;
|
||||
//state.last_pos.y += 1;
|
||||
state.last_rot += 1.0;
|
||||
debug!("{:#?}", state.last_pos);
|
||||
state.tex_sprite.set_texture(state.texture.clone(), None, state.last_rot/100.0);
|
||||
// state.sub_sprite.set_texture(state.texture.clone(),
|
||||
// Some(Position {x: state.last_offset, y: 0}), 1.0);
|
||||
// state.sub_sprite.set_position(state.last_pos);
|
||||
// state.sub_sprite2.set_position(state.last_pos);
|
||||
state.sub_sprite.set_rotation(state.last_rot);
|
||||
state.sub_sprite2.set_rotation(state.last_rot + 45.0);
|
||||
//state.last_rot += 1.0;
|
||||
//state.tex_sprite.set_texture(state.texture.clone(), None, state.last_rot/100.0);
|
||||
//state.sub_sprite.set_texture(state.texture.clone(),
|
||||
// Some(Position {x: state.last_offset, y: 0}), 1.0);
|
||||
//state.sub_sprite.set_position(state.last_pos);
|
||||
//state.sub_sprite.set_rotation(state.last_rot);
|
||||
|
||||
// state.txt_sprite.set_rotation(state.last_rot);
|
||||
//state.sub_sprite.set_scale(state.last_rot/1000.0);
|
||||
|
||||
// inputs
|
||||
@ -152,7 +112,6 @@ impl Application<ExampleState> for ExampleApp {
|
||||
// unimplemented!();
|
||||
//}
|
||||
//
|
||||
//
|
||||
//match canvas.get_key_presses() {
|
||||
// Key::A => unimplemented!(),
|
||||
// _ => (),
|
||||
@ -187,7 +146,7 @@ impl Application<ExampleState> for ExampleApp {
|
||||
//canvas.draw(&pix_sprite);
|
||||
|
||||
//// floating text
|
||||
//let mut txt_sprite = canvas.create_text_sprite(Size {w: 10, h: 10}, 5.0);
|
||||
//let mut txt_sprite = canvas.create_text_sprite(".", Size {w: 10, h: 10});
|
||||
//txt_sprite.set_text("text");
|
||||
//txt_sprite.set_color(Color::BLACK);
|
||||
|
||||
@ -199,9 +158,9 @@ impl Application<ExampleState> for ExampleApp {
|
||||
//canvas.draw(&txt_sprite);
|
||||
|
||||
|
||||
canvas.draw(&mut state.tex_sprite);
|
||||
canvas.draw(&mut state.sub_sprite);
|
||||
canvas.draw(&mut state.sub_sprite2);
|
||||
//canvas.draw(&mut state.tex_sprite);
|
||||
//canvas.draw(&mut state.sub_sprite);
|
||||
canvas.clear();
|
||||
canvas.draw(&mut state.txt_sprite);
|
||||
canvas.update();
|
||||
|
||||
@ -214,6 +173,6 @@ fn main() -> Result<(), &'static str> {
|
||||
|
||||
setup_logger()
|
||||
.map_err(|_| "Failed to setup logger")?;
|
||||
canvas::run_canvas("vk_example", Size {w: 1280, h: 720}, ExampleApp {});
|
||||
canvas::run_canvas("vk_example", Size {w: 1920, h: 1080}, ExampleApp {});
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ use log::{debug, error, info, trace, warn};
|
||||
use raw_window_handle::HasRawWindowHandle;
|
||||
use wgpu;
|
||||
|
||||
use crate::utils::{Color, Size};
|
||||
use crate::utils::{Pixel, Size};
|
||||
|
||||
pub mod utils;
|
||||
|
||||
@ -64,11 +64,18 @@ impl WgpuRenderer {
|
||||
None, // Trace path
|
||||
).await.unwrap();
|
||||
|
||||
debug!("formats: {:#?}", surface.get_supported_formats(&adapter));
|
||||
let format = *surface.get_supported_formats(&adapter)
|
||||
.into_iter()
|
||||
.filter(|f| f == &wgpu::TextureFormat::Rgba8UnormSrgb)
|
||||
.collect::<Vec<wgpu::TextureFormat>>()
|
||||
.first()
|
||||
.ok_or_else(|| "Current adapter incompatible with required surface format")?;
|
||||
|
||||
let config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
//first format in the list is preferred
|
||||
format: *surface.get_supported_formats(&adapter).first()
|
||||
.ok_or_else(|| { "Surface is incompatible with current adapter" })?,
|
||||
format,
|
||||
width: size.w,
|
||||
height: size.h,
|
||||
present_mode: wgpu::PresentMode::Fifo,
|
||||
@ -96,11 +103,7 @@ impl WgpuRenderer {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_surface_size(&self) -> Size {
|
||||
self.surface_size
|
||||
}
|
||||
|
||||
pub fn clear(&mut self, color: Color) {
|
||||
pub fn clear(&mut self, color: Pixel) {
|
||||
|
||||
let view = self.create_texture_view();
|
||||
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
@ -225,23 +228,16 @@ impl WgpuRenderer {
|
||||
|
||||
//--Renderer struct utils---------------------------------------------------------------------------
|
||||
|
||||
//impl From<Pixel> for wgpu::Color {
|
||||
//
|
||||
// fn from(value: Pixel) -> Self {
|
||||
// Self {
|
||||
// r: f64::from(value.r) / 255.0,
|
||||
// g: f64::from(value.g) / 255.0,
|
||||
// b: f64::from(value.b) / 255.0,
|
||||
// a: f64::from(value.a) / 255.0,
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
impl From<Pixel> for wgpu::Color {
|
||||
|
||||
impl From<Color> for wgpu::Color {
|
||||
|
||||
fn from(value: Color) -> Self {
|
||||
value.into_wgpu_color()
|
||||
}
|
||||
fn from(pix: Pixel) -> Self {
|
||||
let rgba = pix.to_rgba();
|
||||
Self {
|
||||
r: (rgba.r as f64) / 255.0,
|
||||
g: (rgba.g as f64) / 255.0,
|
||||
b: (rgba.b as f64) / 255.0,
|
||||
a: (rgba.a as f64) / 255.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -16,10 +16,8 @@ pub const MATRIX_SIZE: usize = 48;//std::mem::size_of::<Matrix3<f32>>();
|
||||
//--ModelMatrix struct------------------------------------------------------------------------------
|
||||
pub struct ModelMatrix {
|
||||
position: Position,
|
||||
center: Position,
|
||||
rotation: Deg<f32>,
|
||||
scale: f32,
|
||||
aspect_matrix: Matrix3<f32>,
|
||||
|
||||
uniform: Uniform<MATRIX_SIZE>,
|
||||
is_synced: bool,
|
||||
@ -28,27 +26,19 @@ pub struct ModelMatrix {
|
||||
impl ModelMatrix {
|
||||
|
||||
pub fn new(renderer: &WgpuRenderer,
|
||||
position: Position,
|
||||
rotation: f32,
|
||||
pos: Position,
|
||||
rot: f32,
|
||||
scale: f32)
|
||||
-> Self
|
||||
{
|
||||
let surface_size: Vector2<f32> = renderer.get_surface_size().into();
|
||||
//A factor 2 is used since the gpu coords go from -1.0 to 1.0 while the engine coords only
|
||||
//go from 0 to 1
|
||||
let aspect_matrix = Matrix3::from_nonuniform_scale(2.0 / surface_size.x,
|
||||
2.0 / surface_size.y)
|
||||
* Matrix3::from_translation(-surface_size / 2.0);
|
||||
|
||||
Self {
|
||||
position,
|
||||
center: Position::origin(),
|
||||
rotation: Deg(rotation),
|
||||
scale,
|
||||
aspect_matrix,
|
||||
Self {
|
||||
position: pos,
|
||||
rotation: Deg (rot),
|
||||
scale,
|
||||
|
||||
uniform: Uniform::create(renderer, &renderer.matrix_layout),
|
||||
is_synced: false,
|
||||
uniform: Uniform::create(renderer, &renderer.matrix_layout),
|
||||
is_synced: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,8 +46,13 @@ impl ModelMatrix {
|
||||
Self::new(renderer, Position::origin(), 0.0, 1.0)
|
||||
}
|
||||
|
||||
pub fn set_position(&mut self, position: Position) {
|
||||
self.position = position;
|
||||
pub fn set_position(&mut self, pos: Position) {
|
||||
self.position = pos;
|
||||
self.is_synced = false;
|
||||
}
|
||||
|
||||
pub fn set_rotation(&mut self, rot: f32) {
|
||||
self.rotation = Deg (rot);
|
||||
self.is_synced = false;
|
||||
}
|
||||
|
||||
@ -66,37 +61,22 @@ impl ModelMatrix {
|
||||
self.is_synced = false;
|
||||
}
|
||||
|
||||
pub fn set_rotation(&mut self, rotation: f32) {
|
||||
self.rotation = Deg(rotation);
|
||||
self.is_synced = false;
|
||||
}
|
||||
|
||||
pub fn set_center(&mut self, center: Position) {
|
||||
self.center = center;
|
||||
self.is_synced = false;
|
||||
}
|
||||
|
||||
pub fn get_uniform(&mut self) -> &mut Uniform<MATRIX_SIZE> {
|
||||
use cgmath::{Basis3, Rotation3};
|
||||
|
||||
if self.is_synced == false {
|
||||
|
||||
let position = Matrix3::from_translation(self.position.into());
|
||||
let scale = Matrix3::from_scale(self.scale);
|
||||
let center = Matrix3::from_translation((self.center * -1.0).into());
|
||||
let rotation = Matrix3::from(Basis3::from_angle_z(self.rotation));
|
||||
|
||||
//to be read in reverse: scale is applied first, then the center offset is applied in
|
||||
//reverse, so that it lines up with the world's origin. The rotation is then applied
|
||||
//around the world's origin (and ence, the center). FInally, the translation is applied
|
||||
//before the aspect matrix can move use from window-space (w x h) to normalized space
|
||||
//(-1.0 to 1.0)
|
||||
let matrix = self.aspect_matrix * position * rotation * center * scale;
|
||||
let pos_vec = Vector2 {
|
||||
x: self.position.x as f32,
|
||||
y: self.position.y as f32,
|
||||
};
|
||||
let rotation_mat = Matrix3::from(Basis3::from_angle_z(self.rotation));
|
||||
let scale_mat = Matrix3::from_scale(self.scale);
|
||||
let translation_mat = Matrix3::from_translation(pos_vec);
|
||||
let aspect_mat = Matrix3::from_nonuniform_scale(1.0/1280.0, 1.0/720.0);
|
||||
|
||||
let matrix = aspect_mat * translation_mat * rotation_mat * scale_mat;
|
||||
let mat_bytes: [u8; 36] = bytemuck::bytes_of(&matrix).try_into().unwrap();
|
||||
|
||||
//wgsl uses 16 bytes-aligned matrixes, but ours is 12 bytes-aligned. Fix that by adding
|
||||
//4 paddings bytes after each 12 bytes
|
||||
let mut bytes = [0; 48];
|
||||
for i in 0..12 {
|
||||
bytes[i] = mat_bytes[i];
|
||||
|
||||
@ -123,8 +123,7 @@ where
|
||||
}
|
||||
);
|
||||
|
||||
let index_buffer = device.create_buffer(
|
||||
&wgpu_types::BufferDescriptor {
|
||||
let index_buffer = device.create_buffer( &wgpu_types::BufferDescriptor {
|
||||
label: Some("Index Buffer"),
|
||||
size: (I_NB * size_of::<u16>()) as u64,
|
||||
usage: Usages::INDEX | Usages::COPY_DST,
|
||||
|
||||
@ -19,16 +19,6 @@ use crate::{
|
||||
|
||||
//--External imports--------------------------------------------------------------------------------
|
||||
|
||||
//--Center enum-------------------------------------------------------------------------------------
|
||||
pub enum Center {
|
||||
Geometric,
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BotLeft,
|
||||
BotRight,
|
||||
Custom(Position),
|
||||
}
|
||||
|
||||
//--Sprite trait------------------------------------------------------------------------------------
|
||||
|
||||
/// The interface for everything that can be rendered by the engine.
|
||||
@ -37,22 +27,19 @@ pub enum Center {
|
||||
/// well as the unique function necessary for it to be rendered
|
||||
pub trait Sprite {
|
||||
|
||||
/// Sets the position of the [`Sprite`] on the screen
|
||||
fn set_position(&mut self, position: Position);
|
||||
/// Set the position of the [Sprite] on the screen
|
||||
fn set_position(&mut self, pos: Position);
|
||||
|
||||
/// Sets the center of the [`Sprite`] for rotations as well as translations
|
||||
fn set_center(&mut self, center: Center);
|
||||
/// Set the rotation of the [Sprite] on the screen
|
||||
fn set_rotation(&mut self, rot: f32);
|
||||
|
||||
/// Sets the rotation of the [`Sprite`] on the screen
|
||||
fn set_rotation(&mut self, rotation: f32);
|
||||
|
||||
/// Sets the alpha of the [`Sprite`] on the screen
|
||||
/// Set the alpha of the [Sprite] on the screen
|
||||
fn set_alpha(&mut self, alpha: u8);
|
||||
|
||||
/// Sets the scale of the [`Sprite`] on the screen
|
||||
/// Set the scale of the [Sprite] on the screen
|
||||
fn set_scale(&mut self, scale: f32);
|
||||
|
||||
/// Renders the [`Sprite`] using the given rendering context
|
||||
/// Renders the [Sprite] using the given rendering context
|
||||
fn render(&mut self, renderer: &mut WgpuRenderer);
|
||||
}
|
||||
|
||||
|
||||
38
src/sprite/shaders/text.wgsl
Normal file
38
src/sprite/shaders/text.wgsl
Normal file
@ -0,0 +1,38 @@
|
||||
// Vertex shader
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) position: vec2<f32>,
|
||||
@location(1) tex_coords: vec2<f32>,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) clip_position: vec4<f32>,
|
||||
@location(0) tex_coords: vec2<f32>,
|
||||
}
|
||||
|
||||
@group(0)@binding(0)
|
||||
var<uniform> model_matrix: mat3x3<f32>;
|
||||
|
||||
@vertex
|
||||
fn vs_main(model: VertexInput) -> VertexOutput {
|
||||
|
||||
var out: VertexOutput;
|
||||
out.tex_coords = model.tex_coords;
|
||||
out.clip_position
|
||||
= vec4<f32>(model_matrix * vec3<f32>(model.position, 1.0), 1.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Fragment shader
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
|
||||
var pos = in.tex_coords;
|
||||
if abs(pos.x * pos.x - pos.y) < 0.8 {
|
||||
return vec4<f32>(0.0, 1.0, 0.0, 1.0);
|
||||
} else {
|
||||
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,12 +14,12 @@ struct VertexOutput {
|
||||
var<uniform> model_matrix: mat3x3<f32>;
|
||||
|
||||
@vertex
|
||||
fn vs_main(vertex: VertexInput) -> VertexOutput {
|
||||
fn vs_main(model: VertexInput) -> VertexOutput {
|
||||
|
||||
var out: VertexOutput;
|
||||
out.tex_coords = vertex.tex_coords;
|
||||
out.tex_coords = model.tex_coords;
|
||||
out.clip_position
|
||||
= vec4<f32>(model_matrix * vec3<f32>(vertex.position, 1.0), 1.0);
|
||||
= vec4<f32>(model_matrix * vec3<f32>(model.position, 1.0), 1.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ use log::{debug, error, info, trace, warn};
|
||||
|
||||
use crate::{
|
||||
renderer::WgpuRenderer,
|
||||
sprite::{Sprite, Center},
|
||||
sprite::Sprite,
|
||||
shape::Shape,
|
||||
utils::{Pixel, Position, Size},
|
||||
};
|
||||
@ -35,15 +35,11 @@ impl ShapeSprite {
|
||||
|
||||
impl Sprite for ShapeSprite {
|
||||
|
||||
fn set_position(&mut self, _position: Position) {
|
||||
fn set_position(&mut self, _pos: Position) {
|
||||
todo!();
|
||||
}
|
||||
|
||||
fn set_center(&mut self, _center: Center) {
|
||||
todo!();
|
||||
}
|
||||
|
||||
fn set_rotation(&mut self, _rotation: f32) {
|
||||
fn set_rotation(&mut self, _rot: f32) {
|
||||
todo!();
|
||||
}
|
||||
|
||||
|
||||
@ -4,169 +4,289 @@ use log::{debug, error, info, trace, warn};
|
||||
//--Internal imports--------------------------------------------------------------------------------
|
||||
|
||||
use crate::{
|
||||
renderer::WgpuRenderer,
|
||||
sprite::{Sprite, Center},
|
||||
utils::{Pixel, Color, Position, Size},
|
||||
renderer::{
|
||||
utils::Mesh,
|
||||
WgpuRenderer, ModelMatrix
|
||||
},
|
||||
sprite::Sprite,
|
||||
utils::{Pixel, Position, Size},
|
||||
};
|
||||
use super::TextureSprite;
|
||||
|
||||
//--External imports--------------------------------------------------------------------------------
|
||||
|
||||
use wgpu;
|
||||
use std::cell::RefCell;
|
||||
|
||||
|
||||
thread_local!(static PIPELINE : RefCell<Option<wgpu::RenderPipeline>> = RefCell::new(None));
|
||||
|
||||
//--TextSprite struct-------------------------------------------------------------------------------
|
||||
#[allow(dead_code)]
|
||||
pub struct TextSprite {
|
||||
texture_sprite: TextureSprite,
|
||||
matrix: ModelMatrix,
|
||||
|
||||
text: String,
|
||||
text_size: f32,
|
||||
fg_color: Color,
|
||||
bg_color: Color,
|
||||
mesh: Mesh<TextVertex, 100, 6>,
|
||||
|
||||
font: fontdue::Font,
|
||||
layout: fontdue::layout::Layout,
|
||||
text_generation_needed: bool,
|
||||
size: Size,
|
||||
text: &'static str, //TODO: temporary
|
||||
}
|
||||
|
||||
impl TextSprite {
|
||||
|
||||
pub fn new(text: &'static str, size: Size, text_size: f32, renderer: &WgpuRenderer) -> Self {
|
||||
use crate::{
|
||||
renderer::Texture,
|
||||
texture::TextureHandle,
|
||||
pub fn new(text: &'static str, size: Size, renderer: &WgpuRenderer) -> Self {
|
||||
use font_kit::{
|
||||
family_name::FamilyName,
|
||||
source::SystemSource,
|
||||
properties::Properties,
|
||||
outline::{OutlineBuilder, Outline},
|
||||
};
|
||||
use fontdue::layout::{CoordinateSystem, Layout, TextStyle};
|
||||
|
||||
let font = Self::configure_font(text_size);
|
||||
let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
|
||||
// initialize pipeline if needed
|
||||
PIPELINE.with(|cell| {
|
||||
if cell.borrow().is_none() {
|
||||
cell.replace(Some(initialize_pipeline(renderer)));
|
||||
}
|
||||
});
|
||||
|
||||
//configure text layout to fit sprite size, keep default settings
|
||||
layout.append(&[&font], &TextStyle::new(text, text_size, 0));
|
||||
let font = SystemSource::new()
|
||||
.select_best_match(&[FamilyName::SansSerif], &Properties::new())
|
||||
.unwrap()
|
||||
.load()
|
||||
.unwrap();
|
||||
|
||||
//draw text to the texture
|
||||
let raw_texture : Vec::<Pixel>
|
||||
= vec![Color::NONE.into(); (size.h * size.w).try_into().unwrap()];
|
||||
let mut outline_builder = OutlineBuilder::new();
|
||||
|
||||
//create the texture
|
||||
let texture = Texture::create(renderer, bytemuck::cast_slice(&raw_texture), size);
|
||||
let texture_handle = TextureHandle::from_texture(texture);
|
||||
let outlines : Vec<Outline> = text.chars().map(|char| {
|
||||
use font_kit::hinting::HintingOptions;
|
||||
|
||||
let texture_sprite = TextureSprite::new(texture_handle, size, &renderer);
|
||||
let glyph = font.glyph_for_char(char).unwrap();
|
||||
let _ = font.outline(glyph, HintingOptions::None, &mut outline_builder).unwrap();
|
||||
|
||||
Self {
|
||||
texture_sprite,
|
||||
text: text.to_string(),
|
||||
text_size,
|
||||
fg_color: Color::BLACK,
|
||||
bg_color: Color::NONE,
|
||||
font,
|
||||
layout,
|
||||
text_generation_needed: true,
|
||||
outline_builder.take_outline()
|
||||
}).collect();
|
||||
|
||||
//TODO improve that
|
||||
let mut mesh = Vec::<TextVertex>::new();
|
||||
for outline in &outlines {
|
||||
for contour in &outline.contours {
|
||||
for i in 0..(contour.positions.len() - 1) {
|
||||
use font_kit::outline::PointFlags;
|
||||
|
||||
match contour.flags[i] {
|
||||
PointFlags::CONTROL_POINT_0 => (),
|
||||
_ => {
|
||||
|
||||
mesh.push(TextVertex {
|
||||
position: [
|
||||
contour.positions[i].x(),
|
||||
contour.positions[i].y(),
|
||||
],
|
||||
tex_coords: [0.0, 0.5],
|
||||
});
|
||||
|
||||
match contour.flags[i+1] {
|
||||
PointFlags::CONTROL_POINT_0 => {
|
||||
mesh.push(TextVertex {
|
||||
position: [
|
||||
contour.positions[i+1].x(),
|
||||
contour.positions[i+1].y(),
|
||||
],
|
||||
tex_coords: [0.0, 1.0],
|
||||
});
|
||||
mesh.push(TextVertex {
|
||||
position: [
|
||||
contour.positions[i+2].x(),
|
||||
contour.positions[i+2].y(),
|
||||
],
|
||||
tex_coords: [1.0, 1.0],
|
||||
});
|
||||
},
|
||||
_ => {
|
||||
mesh.push(TextVertex {
|
||||
position: [
|
||||
contour.positions[i].x(),
|
||||
contour.positions[i].y(),
|
||||
],
|
||||
tex_coords: [0.0, 1.0],
|
||||
});
|
||||
mesh.push(TextVertex {
|
||||
position: [
|
||||
contour.positions[i+1].x(),
|
||||
contour.positions[i+1].y(),
|
||||
],
|
||||
tex_coords: [1.0, 1.0],
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, text: &str) {
|
||||
self.text = text.to_string();
|
||||
self.text_generation_needed = true;
|
||||
}
|
||||
debug!("outlines: {:#?}", outlines);
|
||||
debug!("mesh: {:#?}", mesh);
|
||||
|
||||
pub fn set_text_size(&mut self, text_size: f32) {
|
||||
self.text_size = text_size;
|
||||
self.text_generation_needed = true;
|
||||
}
|
||||
|
||||
pub fn set_fg_color(&mut self, color: Color) {
|
||||
if self.fg_color != color {
|
||||
self.fg_color = color;
|
||||
self.text_generation_needed = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_bg_color(&mut self, color: Color) {
|
||||
if self.bg_color != color {
|
||||
self.bg_color = color;
|
||||
self.text_generation_needed = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_text(&mut self) {
|
||||
|
||||
self.layout.clear();
|
||||
self.layout.append(&[&self.font],
|
||||
&fontdue::layout::TextStyle::new(&self.text, self.text_size, 0));
|
||||
|
||||
let size = self.texture_sprite.get_size();
|
||||
self.texture_sprite.fill(self.bg_color.into());
|
||||
|
||||
//draw each glyph at the corresponding position on the texture
|
||||
for glyph in self.layout.glyphs() {
|
||||
let (_metrics, bitmap) = self.font.rasterize_config(glyph.key);
|
||||
|
||||
let glyph_x = glyph.x as usize;
|
||||
let glyph_y = glyph.y as usize;
|
||||
let size_w = size.w as usize;
|
||||
let size_h = size.h as usize;
|
||||
|
||||
//only try to draw a glyph if its origin fits in the texture
|
||||
if glyph_x < size_w && glyph_y < size_h {
|
||||
|
||||
//glyphs that do not fit on the texture are cropped at the edge of the latter
|
||||
for x in 0..std::cmp::min(glyph.width, size_w - glyph_x) {
|
||||
for y in 0..std::cmp::min(glyph.height, size_h - glyph_y) {
|
||||
|
||||
self.fg_color.set_alpha(bitmap[x + y * glyph.width]);
|
||||
|
||||
self.texture_sprite.set_pixel(
|
||||
Position {
|
||||
x: (glyph_x + x) as i32,
|
||||
y: (glyph_y + y) as i32,
|
||||
},
|
||||
|
||||
Color::alpha_blend(&self.fg_color, &self.bg_color).into()
|
||||
);
|
||||
} } } }
|
||||
}
|
||||
|
||||
fn configure_font(text_size: f32) -> fontdue::Font
|
||||
{
|
||||
let font = include_bytes!("../../assets/DejaVuSansMono.ttf");
|
||||
let settings = fontdue::FontSettings {
|
||||
scale: text_size,
|
||||
..fontdue::FontSettings::default()
|
||||
let mut sprite = Self {
|
||||
matrix: ModelMatrix::default(renderer),
|
||||
mesh: Mesh::new(&renderer.device),
|
||||
size,
|
||||
text,
|
||||
};
|
||||
fontdue::Font::from_bytes(&font[..], settings).unwrap()
|
||||
|
||||
sprite.mesh.set_vertices(renderer, &mesh[..]);
|
||||
|
||||
sprite
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, _text: &'static str) {
|
||||
todo!();
|
||||
}
|
||||
|
||||
pub fn set_color(&mut self, _color: Pixel) {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
||||
impl Sprite for TextSprite {
|
||||
|
||||
fn set_position(&mut self, position: Position) {
|
||||
self.texture_sprite.set_position(position);
|
||||
fn set_position(&mut self, _pos: Position) {
|
||||
todo!();
|
||||
}
|
||||
|
||||
fn set_center(&mut self, center: Center) {
|
||||
self.texture_sprite.set_center(center);
|
||||
fn set_rotation(&mut self, _rot: f32) {
|
||||
todo!();
|
||||
}
|
||||
|
||||
fn set_rotation(&mut self, rotation: f32) {
|
||||
self.texture_sprite.set_rotation(rotation);
|
||||
fn set_alpha(&mut self, _alpha: u8) {
|
||||
todo!();
|
||||
}
|
||||
|
||||
fn set_alpha(&mut self, alpha: u8) {
|
||||
self.texture_sprite.set_alpha(alpha);
|
||||
}
|
||||
|
||||
fn set_scale(&mut self, scale: f32) {
|
||||
self.texture_sprite.set_scale(scale);
|
||||
fn set_scale(&mut self, _scale: f32) {
|
||||
todo!();
|
||||
}
|
||||
|
||||
fn render(&mut self, renderer: &mut WgpuRenderer) {
|
||||
|
||||
// generate text if any setting changed
|
||||
if self.text_generation_needed == true {
|
||||
self.generate_text();
|
||||
self.text_generation_needed = false;
|
||||
}
|
||||
// create command encoder
|
||||
let mut encoder = renderer.device
|
||||
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("Render Encoder"),
|
||||
});
|
||||
|
||||
PIPELINE.with(|pipeline| {
|
||||
let pipeline = pipeline.borrow();
|
||||
|
||||
let view = renderer.create_texture_view();
|
||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Render Pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: &view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load, store: true, },
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
});
|
||||
|
||||
render_pass.set_pipeline(pipeline.as_ref().unwrap());
|
||||
render_pass.set_vertex_buffer(0, self.mesh.get_vertex_buffer_slice());
|
||||
//render_pass.set_index_buffer(self.mesh.get_index_buffer_slice(),
|
||||
// wgpu::IndexFormat::Uint16);
|
||||
render_pass.set_bind_group(0,
|
||||
self.matrix.get_uniform().get_bind_group(&renderer.queue),
|
||||
&[]);
|
||||
render_pass.draw(0..100, 0..1);
|
||||
//render_pass.draw_indexed(0..6, 0, 0..1);
|
||||
|
||||
drop(render_pass);
|
||||
});
|
||||
|
||||
renderer.queue.submit(std::iter::once(encoder.finish()));
|
||||
|
||||
self.texture_sprite.render(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
//--Pipeline initialization-------------------------------------------------------------------------
|
||||
fn initialize_pipeline(renderer: &WgpuRenderer) -> wgpu::RenderPipeline {
|
||||
|
||||
let shader = renderer.device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("text shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("shaders/text.wgsl").into()),
|
||||
});
|
||||
|
||||
let render_pipeline_layout =
|
||||
renderer.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("texture render pipeline layout"),
|
||||
bind_group_layouts: &[
|
||||
&renderer.matrix_layout,
|
||||
],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
renderer.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("texture render pipeline"),
|
||||
layout: Some(&render_pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[
|
||||
TextVertex::layout(),
|
||||
],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: renderer.config.format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
strip_index_format: None,
|
||||
front_face: wgpu::FrontFace::Ccw,
|
||||
cull_mode: None,//Some(wgpu::Face::Back),
|
||||
// Setting this to anything other than Fill requires
|
||||
// Features::NON_FILL_POLYGON_MODE
|
||||
polygon_mode: wgpu::PolygonMode::Fill,
|
||||
// Requires Features::DEPTH_CLIP_CONTROL
|
||||
unclipped_depth: false,
|
||||
// Requires Features::CONSERVATIVE_RASTERIZATION
|
||||
conservative: false,
|
||||
},
|
||||
depth_stencil: None,
|
||||
// multisampling, we don't need it
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
multiview: None,
|
||||
})
|
||||
}
|
||||
|
||||
//--TextVertex struct-------------------------------------------------------------------------------
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct TextVertex {
|
||||
pub position: [f32; 2],
|
||||
pub tex_coords: [f32; 2],
|
||||
}
|
||||
|
||||
impl TextVertex {
|
||||
|
||||
const ATTRIBS: [wgpu::VertexAttribute; 2] =
|
||||
wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2];
|
||||
|
||||
pub fn layout<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<TextVertex>() as wgpu::BufferAddress,
|
||||
step_mode: wgpu::VertexStepMode::Vertex,
|
||||
attributes: &Self::ATTRIBS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ use log::{debug, error, info, trace, warn};
|
||||
|
||||
//--Internal imports--------------------------------------------------------------------------------
|
||||
|
||||
use super::{Sprite, Center};
|
||||
use super::Sprite;
|
||||
|
||||
use crate::{
|
||||
texture::TextureHandle,
|
||||
@ -91,20 +91,11 @@ impl TextureSprite {
|
||||
self.texture.for_each_in_area(func, self.offset, self.inner_size);
|
||||
}
|
||||
|
||||
pub fn fill(&mut self, pixel: Pixel) {
|
||||
self.texture.fill(pixel);
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> Size {
|
||||
self.texture.get_size()
|
||||
}
|
||||
|
||||
fn update_vertices(&mut self, renderer: &WgpuRenderer) {
|
||||
|
||||
debug!("Updating vertices...");
|
||||
let size = self.texture.get_size();
|
||||
|
||||
|
||||
// compute normalized coordinates
|
||||
let x_size = self.inner_size.w as f32 / size.w as f32 * self.scale;
|
||||
let y_size = self.inner_size.h as f32 / size.h as f32 * self.scale;
|
||||
@ -112,10 +103,8 @@ impl TextureSprite {
|
||||
let y_offset = self.offset.y as f32 / size.h as f32;
|
||||
|
||||
// compute mesh size
|
||||
|
||||
//divide by 2 since the quad's coords go from -0.5 to 0.5
|
||||
let w = self.inner_size.w as f32 / 2.0;
|
||||
let h = self.inner_size.h as f32 / 2.0;
|
||||
let w = self.inner_size.w as f32;
|
||||
let h = self.inner_size.h as f32;
|
||||
|
||||
let mesh = [
|
||||
TextureVertex {
|
||||
@ -144,36 +133,12 @@ impl TextureSprite {
|
||||
|
||||
impl Sprite for TextureSprite {
|
||||
|
||||
fn set_position(&mut self, position: Position) {
|
||||
self.matrix.set_position(position);
|
||||
fn set_position(&mut self, pos: Position) {
|
||||
self.matrix.set_position(pos);
|
||||
}
|
||||
|
||||
fn set_center(&mut self, center: Center) {
|
||||
let center_pos = match center {
|
||||
Center::Geometric => Position {x: 0, y: 0},
|
||||
Center::TopLeft => Position {
|
||||
x: -(self.inner_size.w as i32) / 2,
|
||||
y: (self.inner_size.h as i32) / 2
|
||||
},
|
||||
Center::TopRight => Position {
|
||||
x: (self.inner_size.w as i32) / 2,
|
||||
y: (self.inner_size.h as i32) / 2
|
||||
},
|
||||
Center::BotLeft => Position {
|
||||
x: -(self.inner_size.w as i32) / 2,
|
||||
y: -(self.inner_size.h as i32) / 2
|
||||
},
|
||||
Center::BotRight => Position {
|
||||
x: (self.inner_size.w as i32) / 2,
|
||||
y: -(self.inner_size.h as i32) / 2
|
||||
},
|
||||
Center::Custom(pos) => pos,
|
||||
};
|
||||
self.matrix.set_center(center_pos);
|
||||
}
|
||||
|
||||
fn set_rotation(&mut self, rotation: f32) {
|
||||
self.matrix.set_rotation(rotation);
|
||||
fn set_rotation(&mut self, rot: f32) {
|
||||
self.matrix.set_rotation(rot);
|
||||
}
|
||||
|
||||
fn set_alpha(&mut self, _alpha: u8) {
|
||||
@ -270,7 +235,7 @@ fn initialize_pipeline(renderer: &WgpuRenderer) -> wgpu::RenderPipeline {
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: renderer.config.format,
|
||||
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
@ -310,7 +275,7 @@ pub struct TextureVertex {
|
||||
impl TextureVertex {
|
||||
|
||||
const ATTRIBS: [wgpu::VertexAttribute; 2] =
|
||||
wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2];
|
||||
wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x2];
|
||||
|
||||
pub fn layout<'a>() -> wgpu::VertexBufferLayout<'a> {
|
||||
wgpu::VertexBufferLayout {
|
||||
|
||||
@ -30,7 +30,7 @@ impl TextureHandle {
|
||||
|
||||
//TODO check pos ?
|
||||
let mut texture = self.texture.borrow_mut();
|
||||
let width = texture.size.w as i32;
|
||||
let width = texture.size.w;
|
||||
texture.buffer[(pos.x + pos.y*width) as usize] = pix;
|
||||
texture.is_synced = false;
|
||||
}
|
||||
@ -51,21 +51,15 @@ impl TextureHandle {
|
||||
{
|
||||
//TODO check offset and pos ?
|
||||
let mut texture = self.texture.borrow_mut();
|
||||
let width = texture.size.w as i32;
|
||||
for x in offset.x..(offset.x + size.w as i32) {
|
||||
for y in offset.y..(offset.y + size.h as i32) {
|
||||
let width = texture.size.w;
|
||||
for x in offset.x..(offset.x + size.w) {
|
||||
for y in offset.y..(offset.y + size.h) {
|
||||
func(&mut texture.buffer[(x + y*width) as usize]);
|
||||
}
|
||||
}
|
||||
texture.is_synced = false;
|
||||
}
|
||||
|
||||
pub fn fill (&mut self, pixel: Pixel) {
|
||||
let mut texture = self.texture.borrow_mut();
|
||||
texture.buffer.fill(pixel);
|
||||
texture.is_synced = false;
|
||||
}
|
||||
|
||||
pub fn texture(&self) -> &Rc<RefCell<Texture>> {
|
||||
//TODO improve that
|
||||
&self.texture
|
||||
@ -76,3 +70,4 @@ impl TextureHandle {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
160
src/utils.rs
160
src/utils.rs
@ -3,13 +3,13 @@ use log::{debug, error, info, trace, warn};
|
||||
|
||||
use winit;
|
||||
|
||||
use cgmath::{Vector3, Vector2};
|
||||
use cgmath::Vector3;
|
||||
|
||||
//--Position struct---------------------------------------------------------------------------------
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Position {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub x: u32,
|
||||
pub y: u32,
|
||||
}
|
||||
|
||||
impl Position {
|
||||
@ -23,19 +23,12 @@ impl Position {
|
||||
}
|
||||
|
||||
impl From<Position> for Vector3<f32> {
|
||||
|
||||
|
||||
fn from(pos: Position) -> Self {
|
||||
Self::new(pos.x as f32, pos.y as f32, 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Position> for Vector2<f32> {
|
||||
|
||||
fn from(pos: Position) -> Self {
|
||||
Self::new(pos.x as f32, pos.y as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add for Position {
|
||||
type Output = Self;
|
||||
|
||||
@ -47,17 +40,6 @@ impl std::ops::Add for Position {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<f32> for Position {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: f32) -> Self::Output {
|
||||
Position {
|
||||
x: ((self.x as f32) * rhs) as i32,
|
||||
y: ((self.y as f32) * rhs) as i32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--Size struct-------------------------------------------------------------------------------------
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Size {
|
||||
@ -70,7 +52,7 @@ impl From<Size> for winit::dpi::Size {
|
||||
fn from(size: Size) -> Self {
|
||||
winit::dpi::Size::Physical (
|
||||
winit::dpi::PhysicalSize {
|
||||
width: size.w,
|
||||
width: size.w,
|
||||
height: size.h,
|
||||
})
|
||||
}
|
||||
@ -97,118 +79,48 @@ impl From<winit::dpi::PhysicalSize<u32>> for Size {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size> for Vector2<f32> {
|
||||
|
||||
fn from(size: Size) -> Self {
|
||||
Self::new(size.w as f32, size.h as f32)
|
||||
}
|
||||
}
|
||||
|
||||
//--Pixel struct------------------------------------------------------------------------------------
|
||||
#[repr(packed, C)]
|
||||
#[derive(Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct Pixel {
|
||||
pub r: u8,
|
||||
pub g: u8,
|
||||
pub b: u8,
|
||||
pub a: u8,
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct Rgba {
|
||||
pub r: u8,
|
||||
pub g: u8,
|
||||
pub b: u8,
|
||||
pub a: u8,
|
||||
}
|
||||
|
||||
impl From<Color> for Pixel {
|
||||
|
||||
fn from(value: Color) -> Self {
|
||||
value.into_pixel()
|
||||
}
|
||||
#[derive(Copy, Clone, bytemuck::Zeroable)]
|
||||
pub union Pixel {
|
||||
pub flat: u32,
|
||||
rgba: Rgba,
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Pod for Pixel {}
|
||||
|
||||
impl Pixel {
|
||||
|
||||
pub fn rgb(r: u8, g: u8, b: u8) -> Pixel {
|
||||
Pixel {
|
||||
rgba: Rgba{r, g, b, a: 255},
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from_hex(hex: u32) -> Self {
|
||||
let bytes = hex.to_be_bytes();
|
||||
Self { r: bytes[0], g: bytes[1], b: bytes[2], a: bytes[3] }
|
||||
}
|
||||
|
||||
pub fn to_rgba(&self) -> Rgba {
|
||||
unsafe { self.rgba }
|
||||
}
|
||||
}
|
||||
|
||||
//--Color values------------------------------------------------------------------------------------
|
||||
#[non_exhaustive]
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub struct Color {
|
||||
r: f32,
|
||||
g: f32,
|
||||
b: f32,
|
||||
a: f32,
|
||||
}
|
||||
|
||||
impl From<Pixel> for Color {
|
||||
|
||||
fn from(value: Pixel) -> Self {
|
||||
Self {
|
||||
r: f32::from(value.r) / 255.0,
|
||||
g: f32::from(value.g) / 255.0,
|
||||
b: f32::from(value.b) / 255.0,
|
||||
a: f32::from(value.a) / 255.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct Color;
|
||||
|
||||
impl Color {
|
||||
|
||||
pub fn set_alpha(&mut self, a: u8) {
|
||||
self.a = f32::from(a) / 255.0;
|
||||
}
|
||||
|
||||
pub fn from_rgb(r: u8, g: u8, b: u8) -> Self {
|
||||
Pixel { r, g, b, a: 255 }.into()
|
||||
}
|
||||
|
||||
pub fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
Pixel { r, g, b, a }.into()
|
||||
}
|
||||
|
||||
pub fn from_hex(hex: u32) -> Self {
|
||||
Pixel::from_hex(hex).into()
|
||||
}
|
||||
|
||||
pub fn into_pixel(self) -> Pixel {
|
||||
//unsafe block is used here for "to_int_unchecked" since the struct's invariant garentees
|
||||
//that the values are within [0.0, 1.0], meaning we won't ever overflow an u8
|
||||
unsafe {
|
||||
Pixel {
|
||||
r: self.r.to_int_unchecked::<u8>() * 255,
|
||||
g: self.g.to_int_unchecked::<u8>() * 255,
|
||||
b: self.b.to_int_unchecked::<u8>() * 255,
|
||||
a: self.a.to_int_unchecked::<u8>() * 255,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_wgpu_color(self) -> wgpu::Color {
|
||||
wgpu::Color {
|
||||
r: f64::from(self.r),
|
||||
g: f64::from(self.g),
|
||||
b: f64::from(self.b),
|
||||
a: f64::from(self.a),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alpha_blend(&self, other: &Self) -> Self {
|
||||
//apply alpha compisting's "over" operator, see
|
||||
//[https://en.wikipedia.org/wiki/Alpha_compositing] for more detail
|
||||
let a = self.a + other.a * (1.0 - self.a);
|
||||
Self {
|
||||
r: (self.r * self.a + other.r * other.a * (1.0 - self.a)) / a,
|
||||
g: (self.g * self.a + other.g * other.a * (1.0 - self.a)) / a,
|
||||
b: (self.b * self.a + other.b * other.a * (1.0 - self.a)) / a,
|
||||
a,
|
||||
}
|
||||
}
|
||||
|
||||
pub const NONE: Self = Self {r: 0.0, g: 0.0, b: 0.0, a: 0.0};
|
||||
pub const WHITE: Self = Self {r: 1.0, g: 1.0, b: 1.0, a: 1.0};
|
||||
pub const BLACK: Self = Self {r: 0.0, g: 0.0, b: 0.0, a: 1.0};
|
||||
pub const RED: Self = Self {r: 1.0, g: 0.0, b: 0.0, a: 1.0};
|
||||
pub const GREEN: Self = Self {r: 0.0, g: 1.0, b: 0.0, a: 1.0};
|
||||
pub const BLUE: Self = Self {r: 0.0, g: 0.0, b: 1.0, a: 1.0};
|
||||
pub const NONE: Pixel = Pixel {flat: 0x00000000};
|
||||
pub const WHITE: Pixel = Pixel {flat: 0xffffffff};
|
||||
pub const BLACK: Pixel = Pixel {flat: 0x000000ff};
|
||||
pub const RED: Pixel = Pixel {flat: 0xff0000ff};
|
||||
pub const GREEN: Pixel = Pixel {flat: 0x00ff00ff};
|
||||
pub const BLUE: Pixel = Pixel {flat: 0x0000ffff};
|
||||
}
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user