├── .gitignore ├── web ├── .gitignore ├── bootstrap.js ├── webpack.config.js ├── index.html ├── package.json └── index.js ├── core ├── crates │ ├── math │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── lib.rs │ │ │ ├── rect.rs │ │ │ ├── vector3.rs │ │ │ ├── vector4.rs │ │ │ └── vector2.rs │ ├── layout │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── lib.rs │ │ │ ├── widget.rs │ │ │ ├── base.rs │ │ │ ├── decoration.rs │ │ │ ├── container.rs │ │ │ └── tree.rs │ ├── platform │ │ ├── .gitignore │ │ ├── src │ │ │ ├── browser │ │ │ │ ├── mod.rs │ │ │ │ ├── shaders │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── standard.rs │ │ │ │ ├── driver.rs │ │ │ │ ├── util.rs │ │ │ │ └── webgl.rs │ │ │ └── lib.rs │ │ └── Cargo.toml │ └── test_util │ │ ├── .gitignore │ │ ├── target │ │ ├── rls │ │ │ ├── debug │ │ │ │ ├── .cargo-lock │ │ │ │ ├── deps │ │ │ │ │ ├── libtest_util-cf59899279081ef4.rmeta │ │ │ │ │ ├── libtest_util-ca7e05eeee4dcd01.rmeta │ │ │ │ │ ├── test_util-ca7e05eeee4dcd01.d │ │ │ │ │ ├── test_util-cf59899279081ef4.d │ │ │ │ │ └── save-analysis │ │ │ │ │ │ ├── libtest_util-ca7e05eeee4dcd01.json │ │ │ │ │ │ └── test_util-cf59899279081ef4.json │ │ │ │ └── .fingerprint │ │ │ │ │ ├── test_util-ca7e05eeee4dcd01 │ │ │ │ │ ├── lib-test_util │ │ │ │ │ ├── dep-lib-test_util │ │ │ │ │ ├── invoked.timestamp │ │ │ │ │ └── lib-test_util.json │ │ │ │ │ └── test_util-cf59899279081ef4 │ │ │ │ │ ├── test-lib-test_util │ │ │ │ │ ├── dep-test-lib-test_util │ │ │ │ │ ├── invoked.timestamp │ │ │ │ │ └── test-lib-test_util.json │ │ │ ├── CACHEDIR.TAG │ │ │ └── .rustc_info.json │ │ └── CACHEDIR.TAG │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── .gitignore ├── tests │ └── web.rs ├── src │ ├── lib.rs │ └── app.rs └── Cargo.toml ├── docs ├── README.md ├── render-pipeline.md └── accessibility.md ├── bin ├── pre-commit.sh ├── start.sh └── build.sh ├── .github └── workflows │ └── rust.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /core/crates/math/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /core/crates/layout/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /core/crates/platform/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /core/crates/test_util/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /core/crates/test_util/target/rls/debug/.cargo-lock: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/crates/test_util/target/rls/debug/deps/libtest_util-cf59899279081ef4.rmeta: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /target 3 | **/*.rs.bk 4 | Cargo.lock 5 | bin/ 6 | wasm-pack.log 7 | -------------------------------------------------------------------------------- /core/crates/test_util/target/rls/debug/.fingerprint/test_util-ca7e05eeee4dcd01/lib-test_util: -------------------------------------------------------------------------------- 1 | f47a0d0ede2737ae -------------------------------------------------------------------------------- /core/crates/test_util/target/rls/debug/.fingerprint/test_util-cf59899279081ef4/test-lib-test_util: -------------------------------------------------------------------------------- 1 | 57bc992f470fa4bb -------------------------------------------------------------------------------- /core/crates/test_util/target/rls/debug/.fingerprint/test_util-ca7e05eeee4dcd01/dep-lib-test_util: -------------------------------------------------------------------------------- 1 |  2 | src/lib.rs -------------------------------------------------------------------------------- /core/crates/test_util/target/rls/debug/.fingerprint/test_util-cf59899279081ef4/dep-test-lib-test_util: -------------------------------------------------------------------------------- 1 |  2 | src/lib.rs -------------------------------------------------------------------------------- /core/crates/test_util/target/rls/debug/.fingerprint/test_util-ca7e05eeee4dcd01/invoked.timestamp: -------------------------------------------------------------------------------- 1 | This file has an mtime of when this was started. -------------------------------------------------------------------------------- /core/crates/test_util/target/rls/debug/.fingerprint/test_util-cf59899279081ef4/invoked.timestamp: -------------------------------------------------------------------------------- 1 | This file has an mtime of when this was started. -------------------------------------------------------------------------------- /core/crates/platform/src/browser/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod shaders; 2 | pub mod util; 3 | 4 | mod driver; 5 | pub use driver::BrowserDriver; 6 | 7 | mod webgl; 8 | pub use webgl::WebGl; 9 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | This library is still *heavily* a work-in-progress. There is no real 4 | documentation. This directory serves mostly as a scratchpad for my thinking. -------------------------------------------------------------------------------- /core/crates/platform/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::all)] 2 | use layout::Layout; 3 | 4 | pub mod browser; 5 | 6 | pub trait AppDriver { 7 | fn tick(&mut self, time: f32) -> Box; 8 | } 9 | -------------------------------------------------------------------------------- /bin/pre-commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This is the Git precommit hook script. It runs before any changes are 4 | # committed. It is symlinked as `.git/hooks/precommit`. 5 | 6 | cd core 7 | cargo fmt 8 | -------------------------------------------------------------------------------- /core/crates/test_util/target/rls/debug/deps/libtest_util-ca7e05eeee4dcd01.rmeta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harrisonturton/wasm-ui/HEAD/core/crates/test_util/target/rls/debug/deps/libtest_util-ca7e05eeee4dcd01.rmeta -------------------------------------------------------------------------------- /core/crates/test_util/target/CACHEDIR.TAG: -------------------------------------------------------------------------------- 1 | Signature: 8a477f597d28d172789f06886806bc55 2 | # This file is a cache directory tag created by cargo. 3 | # For information about cache directory tags see https://bford.info/cachedir/ 4 | -------------------------------------------------------------------------------- /core/crates/test_util/target/rls/CACHEDIR.TAG: -------------------------------------------------------------------------------- 1 | Signature: 8a477f597d28d172789f06886806bc55 2 | # This file is a cache directory tag created by cargo. 3 | # For information about cache directory tags see https://bford.info/cachedir/ 4 | -------------------------------------------------------------------------------- /core/crates/test_util/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test_util" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /core/crates/math/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "math" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | bytemuck = { version = "1.4", features = [ "derive" ] } 8 | log = "0.4" 9 | console_log = { version = "0.2.0" } 10 | -------------------------------------------------------------------------------- /core/crates/math/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::all, clippy::pedantic)] 2 | 3 | mod vector2; 4 | pub use vector2::*; 5 | 6 | mod vector3; 7 | pub use vector3::*; 8 | 9 | mod vector4; 10 | pub use vector4::*; 11 | 12 | mod rect; 13 | pub use rect::*; 14 | -------------------------------------------------------------------------------- /core/crates/layout/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "layout" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | math = { path = "../math" } 8 | log = "0.4" 9 | console_log = { version = "0.2.0" } 10 | 11 | [dev-dependencies] 12 | test_util = { path = "../test_util" } -------------------------------------------------------------------------------- /web/bootstrap.js: -------------------------------------------------------------------------------- 1 | // A dependency graph that contains any wasm must all be imported 2 | // asynchronously. This `bootstrap.js` file does the single async import, so 3 | // that no one else needs to worry about it again. 4 | import("./index.js") 5 | .catch(e => console.error("Error importing `index.js`:", e)); 6 | -------------------------------------------------------------------------------- /core/tests/web.rs: -------------------------------------------------------------------------------- 1 | //! Test suite for the Web and headless browsers. 2 | 3 | #![cfg(target_arch = "wasm32")] 4 | 5 | extern crate wasm_bindgen_test; 6 | use wasm_bindgen_test::*; 7 | 8 | wasm_bindgen_test_configure!(run_in_browser); 9 | 10 | #[wasm_bindgen_test] 11 | fn pass() { 12 | assert_eq!(1 + 1, 2); 13 | } 14 | -------------------------------------------------------------------------------- /core/crates/layout/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::all, clippy::pedantic)] 2 | #![allow(clippy::similar_names)] 3 | 4 | mod base; 5 | pub use base::*; 6 | 7 | mod container; 8 | pub use container::*; 9 | 10 | mod decoration; 11 | pub use decoration::*; 12 | 13 | mod tree; 14 | pub use tree::*; 15 | 16 | mod widget; 17 | pub use widget::*; 18 | 19 | mod flex; 20 | pub use flex::*; 21 | -------------------------------------------------------------------------------- /bin/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script starts a webserver with a debug build of the site. If there is no 4 | # build/core directory, it will build the core WASM libraries first. 5 | 6 | ROOT_PATH="$(dirname `which $0`)/../" 7 | cd $ROOT_PATH 8 | 9 | if [ ! -d "build/core" ] 10 | then 11 | wasm-pack build --out-dir ../build/core core 12 | fi 13 | 14 | cd web 15 | npm run start 16 | -------------------------------------------------------------------------------- /web/webpack.config.js: -------------------------------------------------------------------------------- 1 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | entry: "./bootstrap.js", 6 | output: { 7 | path: path.resolve(__dirname, "..", "build", "web"), 8 | filename: "bootstrap.js", 9 | }, 10 | mode: "development", 11 | plugins: [ 12 | new CopyWebpackPlugin(['index.html']) 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /core/crates/test_util/target/rls/debug/deps/test_util-ca7e05eeee4dcd01.d: -------------------------------------------------------------------------------- 1 | /Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/target/rls/debug/deps/test_util-ca7e05eeee4dcd01.rmeta: crates/test_util/src/lib.rs 2 | 3 | /Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/target/rls/debug/deps/test_util-ca7e05eeee4dcd01.d: crates/test_util/src/lib.rs 4 | 5 | crates/test_util/src/lib.rs: 6 | -------------------------------------------------------------------------------- /core/crates/test_util/target/rls/debug/deps/test_util-cf59899279081ef4.d: -------------------------------------------------------------------------------- 1 | /Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/target/rls/debug/deps/test_util-cf59899279081ef4.rmeta: crates/test_util/src/lib.rs 2 | 3 | /Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/target/rls/debug/deps/test_util-cf59899279081ef4.d: crates/test_util/src/lib.rs 4 | 5 | crates/test_util/src/lib.rs: 6 | -------------------------------------------------------------------------------- /core/crates/test_util/target/rls/debug/.fingerprint/test_util-ca7e05eeee4dcd01/lib-test_util.json: -------------------------------------------------------------------------------- 1 | {"rustc":5636649477299549325,"features":"[]","target":13422979543232180819,"profile":17483045194147818835,"path":13416773304188335474,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/test_util-ca7e05eeee4dcd01/dep-lib-test_util"}}],"rustflags":[],"metadata":7797948686568424061,"config":2202906307356721367,"compile_kind":0} -------------------------------------------------------------------------------- /core/crates/test_util/target/rls/debug/.fingerprint/test_util-cf59899279081ef4/test-lib-test_util.json: -------------------------------------------------------------------------------- 1 | {"rustc":5636649477299549325,"features":"[]","target":13422979543232180819,"profile":11506243869495082934,"path":13416773304188335474,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug/.fingerprint/test_util-cf59899279081ef4/dep-test-lib-test_util"}}],"rustflags":[],"metadata":7797948686568424061,"config":2202906307356721367,"compile_kind":0} -------------------------------------------------------------------------------- /bin/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script will generate a build/web directory (among other artefacts) that 4 | # provides the functioning website when served from a webserver. 5 | 6 | ROOT_PATH="$(dirname `which $0`)/../" 7 | cd $ROOT_PATH 8 | 9 | echo $1 10 | 11 | if [[ $1 -eq core ]]; then 12 | wasm-pack build --out-dir ../build/core core 13 | exit 0 14 | fi 15 | 16 | if [[ $1 -eq web ]]; then 17 | npm run build --prefix web 18 | exit 0 19 | fi 20 | 21 | wasm-pack build --out-dir ../build/core core 22 | npm run build --prefix web 23 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebGl 6 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /core/crates/platform/src/browser/shaders/mod.rs: -------------------------------------------------------------------------------- 1 | use super::WebGl; 2 | use anyhow::Error; 3 | use math::Vector3; 4 | use std::rc::Rc; 5 | 6 | mod standard; 7 | pub use standard::*; 8 | 9 | pub struct ShaderLibrary { 10 | pub standard: StandardShader, 11 | } 12 | 13 | impl ShaderLibrary { 14 | pub fn try_new(gl: &Rc) -> Result { 15 | super::util::log("before standard shader"); 16 | let standard = StandardShader::try_new(gl)?; 17 | super::util::log("after standard shader"); 18 | Ok(ShaderLibrary { standard }) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Install 15 | run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 16 | 17 | - run: cargo test --workspace 18 | working-directory: ./core 19 | - run: wasm-pack test --headless --chrome 20 | working-directory: ./core 21 | - run: wasm-pack test --headless --firefox 22 | working-directory: ./core 23 | -------------------------------------------------------------------------------- /core/crates/platform/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "platform" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | math = { path = "../math" } 8 | layout = { path = "../layout" } 9 | wasm-bindgen = "0.2.63" 10 | js-sys = "0.3.50" 11 | anyhow = "1.0.44" 12 | bytemuck = { version = "1.4", features = [ "derive" ] } 13 | log = "0.4" 14 | console_log = { version = "0.2.0" } 15 | 16 | [dependencies.web-sys] 17 | version = "0.3.4" 18 | features = [ 19 | "Window", 20 | "Document", 21 | "HtmlCanvasElement", 22 | "WebGlRenderingContext", 23 | "WebGlShader", 24 | "WebGlProgram", 25 | "WebGlBuffer", 26 | "WebGlUniformLocation", 27 | ] 28 | -------------------------------------------------------------------------------- /core/crates/test_util/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | /// # Panics 4 | /// 5 | /// Will panic when the slices are different. 6 | pub fn assert_slice_eq(expected: &[T], actual: &[T]) { 7 | assert!( 8 | expected.len() == actual.len(), 9 | "\nVectors do not match. Expected length {} but got {}", 10 | expected.len(), 11 | actual.len(), 12 | ); 13 | for (i, expected) in expected.iter().enumerate() { 14 | let actual = &actual[i]; 15 | assert!( 16 | *expected == *actual, 17 | "\nVectors do not match. Unexpected item at index {}.\nExpected: {:?}\nActual: {:?}", 18 | i, 19 | expected, 20 | actual, 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /docs/render-pipeline.md: -------------------------------------------------------------------------------- 1 | # Render Pipeline 2 | 3 | The render pipeline is roughly based on [this tech talk](https://www.youtube.com/watch?v=UUfXWzp0-DU) 4 | about Flutter's render pipeline. However, it is *much* simpler and probably less 5 | efficient. 6 | 7 | There are two stages: 8 | 9 | 1. Generate widget tree 10 | 2. Layout 11 | 3. Paint 12 | 13 | A more detailed time looks like this: 14 | 15 | 1. The platform-specific code calls the `tick` method on the `AppDriver` instance. 16 | 2. This returns the widget tree. 17 | 3. This widget tree is used to generate a `LayoutTree`, which contains a hierarchy of `LayoutBox` elements. 18 | 4. These boxes are passed to the platform driver, which then paints them to the screen. 19 | 20 | `tick` is called up to 60 times a second, which means steps 1-4 must be 21 | completed in under 16ms. -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | use log::Level; 2 | use platform::browser::BrowserDriver; 3 | use wasm_bindgen::prelude::wasm_bindgen; 4 | 5 | mod app; 6 | use app::App; 7 | 8 | // Use `wee_alloc` as the global allocator, because it is smaller. 9 | #[cfg(feature = "wee_alloc")] 10 | #[global_allocator] 11 | static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; 12 | 13 | /// This is called from the browser as soon as the WASM package is loaded. It is 14 | /// the main entrypoint to the application. This is similar to `index.js` in 15 | /// React. 16 | #[wasm_bindgen] 17 | pub fn start(canvas_id: &str) -> BrowserDriver { 18 | // Forward panic messages to console.error 19 | #[cfg(feature = "console_error_panic_hook")] 20 | std::panic::set_hook(Box::new(console_error_panic_hook::hook)); 21 | 22 | console_log::init_with_level(Level::Debug).unwrap(); 23 | 24 | let app = App::new(); 25 | BrowserDriver::try_new(canvas_id, Box::new(app)).unwrap() 26 | } 27 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-wasm-app", 3 | "version": "0.1.0", 4 | "description": "create an app to consume rust-generated wasm packages", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack --config webpack.config.js", 8 | "start": "webpack-dev-server" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/rustwasm/create-wasm-app.git" 13 | }, 14 | "keywords": [ 15 | "webassembly", 16 | "wasm", 17 | "rust", 18 | "webpack" 19 | ], 20 | "author": "Ashley Williams ", 21 | "license": "(MIT OR Apache-2.0)", 22 | "bugs": { 23 | "url": "https://github.com/rustwasm/create-wasm-app/issues" 24 | }, 25 | "homepage": "https://github.com/rustwasm/create-wasm-app#readme", 26 | "dependencies": { 27 | "core": "file:../build/core" 28 | }, 29 | "devDependencies": { 30 | "hello-wasm-pack": "^0.1.0", 31 | "webpack": "^4.29.3", 32 | "webpack-cli": "^3.1.0", 33 | "webpack-dev-server": "^3.1.5", 34 | "copy-webpack-plugin": "^5.0.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /web/index.js: -------------------------------------------------------------------------------- 1 | import * as wasm from "core"; 2 | 3 | const canvas = document.getElementById("app"); 4 | if (!canvas) { 5 | console.error("failed to get canvas element"); 6 | } 7 | 8 | const gl = canvas.getContext("webgl"); 9 | if (!gl) { 10 | console.error("failed to get webgl context") 11 | } 12 | 13 | let app = wasm.start("app"); 14 | 15 | function update(now) { 16 | app.tick(now); 17 | requestAnimationFrame(update); 18 | } 19 | requestAnimationFrame(update); 20 | 21 | function resizeCanvasToDisplaySize(canvas, multiplier) { 22 | const width = window.innerWidth; 23 | const height = window.innerHeight; 24 | canvas.width = width; 25 | canvas.height = height; 26 | gl.viewport(0, 0, width, height); 27 | } 28 | 29 | resizeCanvasToDisplaySize(canvas, window.devicePixelRatio); 30 | window.addEventListener("resize", function(e) { 31 | debounce(function() { 32 | resizeCanvasToDisplaySize(canvas, window.devicePixelRatio); 33 | }, 250); 34 | }); 35 | 36 | var timerId; 37 | function debounce(func, delay) { 38 | clearTimeout(timerId); 39 | timerId = setTimeout(() => func(), delay); 40 | } -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "crates/math", 4 | "crates/platform", 5 | "crates/layout", 6 | "crates/test_util", 7 | ] 8 | 9 | [package] 10 | name = "core" 11 | version = "0.1.0" 12 | authors = ["Harrison Turton "] 13 | edition = "2018" 14 | 15 | [lib] 16 | crate-type = ["cdylib", "rlib"] 17 | 18 | [features] 19 | default = ["console_error_panic_hook"] 20 | 21 | [dependencies] 22 | wasm-bindgen = "0.2.63" 23 | js-sys = "0.3.50" 24 | # TODO(@harrisonturton): remove for prod builds 25 | console_error_panic_hook = { version = "0.1.6", optional = true } 26 | wee_alloc = { version = "0.4.5", optional = true } 27 | anyhow = "1.0.44" 28 | math = { path = "crates/math" } 29 | platform = { path = "crates/platform" } 30 | layout = { path = "crates/layout" } 31 | log = "0.4" 32 | console_log = { version = "0.2.0" } 33 | 34 | [dependencies.web-sys] 35 | version = "0.3.4" 36 | features = [ 37 | "Window", 38 | "Document", 39 | "HtmlCanvasElement", 40 | "WebGlRenderingContext", 41 | "WebGlShader", 42 | "WebGlProgram", 43 | "WebGlBuffer", 44 | ] 45 | 46 | [dev-dependencies] 47 | wasm-bindgen-test = "0.3.13" 48 | 49 | [profile.release] 50 | # Tell `rustc` to optimize for small code size. 51 | opt-level = "s" 52 | -------------------------------------------------------------------------------- /core/crates/test_util/target/rls/.rustc_info.json: -------------------------------------------------------------------------------- 1 | {"rustc_fingerprint":15453958237282004561,"outputs":{"17598535894874457435":{"success":true,"status":"","code":0,"stdout":"rustc 1.57.0-nightly (aa8f2d432 2021-09-18)\nbinary: rustc\ncommit-hash: aa8f2d432b23575929a48f87b8746f41ba723318\ncommit-date: 2021-09-18\nhost: x86_64-apple-darwin\nrelease: 1.57.0-nightly\nLLVM version: 13.0.0\n","stderr":""},"931469667778813386":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n/Users/harrisonturton/.rustup/toolchains/nightly-x86_64-apple-darwin\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_family=\"unix\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_feature=\"ssse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_equal_alignment=\"16\"\ntarget_has_atomic_equal_alignment=\"32\"\ntarget_has_atomic_equal_alignment=\"64\"\ntarget_has_atomic_equal_alignment=\"8\"\ntarget_has_atomic_equal_alignment=\"ptr\"\ntarget_has_atomic_load_store=\"128\"\ntarget_has_atomic_load_store=\"16\"\ntarget_has_atomic_load_store=\"32\"\ntarget_has_atomic_load_store=\"64\"\ntarget_has_atomic_load_store=\"8\"\ntarget_has_atomic_load_store=\"ptr\"\ntarget_os=\"macos\"\ntarget_pointer_width=\"64\"\ntarget_thread_local\ntarget_vendor=\"apple\"\nunix\n","stderr":""},"15537503139010883884":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n","stderr":""},"2797684049618456168":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.dylib\nlib___.dylib\nlib___.a\nlib___.dylib\n","stderr":""}},"successes":{}} -------------------------------------------------------------------------------- /core/crates/math/src/rect.rs: -------------------------------------------------------------------------------- 1 | use crate::Vector2; 2 | 3 | /// A 2-dimensional rectangle. 4 | #[repr(C)] 5 | #[derive(PartialEq, Copy, Clone, Debug, Default)] 6 | pub struct Rect { 7 | pub min: Vector2, 8 | pub max: Vector2, 9 | } 10 | 11 | impl Rect { 12 | /// Construct a new vector that starts at the point described by `min` and 13 | /// ends at the point described by `max`. 14 | #[must_use] 15 | pub fn new(min: Vector2, max: Vector2) -> Self { 16 | Self { min, max } 17 | } 18 | 19 | #[must_use] 20 | pub fn zero() -> Self { 21 | Self { 22 | min: Vector2::zero(), 23 | max: Vector2::zero(), 24 | } 25 | } 26 | 27 | #[must_use] 28 | pub fn from_pos>(pos: I, size: I) -> Self { 29 | let pos = pos.into(); 30 | Self { 31 | min: pos, 32 | max: pos + size.into(), 33 | } 34 | } 35 | 36 | #[must_use] 37 | pub fn from_size>(max: I) -> Self { 38 | Self { 39 | min: Vector2::zero(), 40 | max: max.into(), 41 | } 42 | } 43 | 44 | /// Get the width and height of the rectangle. 45 | #[must_use] 46 | pub fn size(self) -> Vector2 { 47 | let width = self.max.x - self.min.x; 48 | let height = self.max.y - self.min.y; 49 | Vector2::new(width, height) 50 | } 51 | 52 | /// Move the rectangle by the provided amount. 53 | #[must_use] 54 | pub fn translate(self, amount: Vector2) -> Rect { 55 | Rect::new(self.min + amount, self.max + amount) 56 | } 57 | 58 | /// Check if the rectangle intersects a point. 59 | #[must_use] 60 | pub fn intersects(self, point: Vector2) -> bool { 61 | let intersects_x = self.min.x < point.x && point.x < self.max.x; 62 | let intersects_y = self.min.y < point.y && point.y < self.max.y; 63 | intersects_x && intersects_y 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /docs/accessibility.md: -------------------------------------------------------------------------------- 1 | # Accessibility 2 | 3 | Accessibility is required for production release. I expect this to be one of the 4 | most platform-specific parts of the implementation. I've mostly focused on 5 | backend work, so I haven't dived deep into accessibility before. Time to learn 6 | something new! 7 | 8 | ## Web 9 | 10 | On the web accessibility is done through a set of attributes in the DOM, [called ARIA.](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA) These attributes introduce more metadata so that an [accessibility tree](https://developer.mozilla.org/en-US/docs/Glossary/Accessibility_tree) can be generated from the DOM structure, which is then consumed by screenreaders. 11 | 12 | So on the web, I think the only option is to maintain a parallel DOM structure. 13 | This won't need to be rendered, so I assume this will be pretty efficient. It 14 | could be set as `display: none` so it doesn't undergo layout or painting. 15 | 16 | This would have to be generated from the component tree *before* layout, because 17 | once we have a `LayoutTree`, we've lost all the semantic information about the 18 | components. So this would have to be an additional part of the render pipeline 19 | between creating the widget tree and layout. 20 | 21 | ## Mac OSX + iOS 22 | 23 | [Apple seems to have great support.](https://developer.apple.com/accessibility/macos/) There is a lot of inspiration to be taken from their work. 24 | 25 | I wonder if we can do something similar to accessibility on the web, but instead 26 | of maintaining a parallel DOM structure, we do it with native OSX components? 27 | 28 | ## Windows 29 | 30 | *Todo* 31 | 32 | ## Linux 33 | 34 | From the tiny bit of searching I've done, it seems like accessibility on Linux 35 | is not in the greatest state. It'd be easy to include some things, like 36 | increasing constrast for visual impairment, but interop with screenreaders might 37 | be difficult. Maybe it'd be best to provide two builds, one that renders 38 | directly to OpenGL without accessibility built in, and one that uses a webview 39 | like Electron that can use the same accessibility tooling that would be built 40 | for the web? -------------------------------------------------------------------------------- /core/crates/layout/src/widget.rs: -------------------------------------------------------------------------------- 1 | use super::{BoxConstraints, Layout, LayoutBox, LayoutTree, SizedLayoutBox}; 2 | use crate::decoration::{Color, Material}; 3 | use math::Vector2; 4 | use std::fmt::Debug; 5 | 6 | // -------------------------------------------------- 7 | // Center 8 | // -------------------------------------------------- 9 | 10 | #[derive(Debug)] 11 | pub struct Center { 12 | pub child: Box, 13 | } 14 | 15 | impl Layout for Center { 16 | fn layout(&self, tree: &mut LayoutTree, constraints: &BoxConstraints) -> SizedLayoutBox { 17 | let sbox = self.child.layout(tree, constraints); 18 | let pos = (constraints.max / 2.0) - (sbox.size / 2.0); 19 | let lbox = LayoutBox::from_child(sbox, pos); 20 | let id = tree.insert(lbox); 21 | SizedLayoutBox { 22 | size: constraints.max, 23 | children: vec![id], 24 | material: None, 25 | ..SizedLayoutBox::default() 26 | } 27 | } 28 | } 29 | 30 | // -------------------------------------------------- 31 | // Stack 32 | // -------------------------------------------------- 33 | 34 | #[derive(Debug)] 35 | pub struct Stack { 36 | pub children: Vec, 37 | } 38 | 39 | impl Layout for Stack { 40 | fn layout(&self, tree: &mut LayoutTree, constraints: &BoxConstraints) -> SizedLayoutBox { 41 | let mut children = Vec::new(); 42 | for child in &self.children { 43 | let child = child.layout(tree, constraints); 44 | let lbox = LayoutBox::from_child(child, Vector2::zero()); 45 | let id = tree.insert(lbox); 46 | children.push(id); 47 | } 48 | SizedLayoutBox { 49 | size: constraints.max, 50 | children, 51 | material: None, 52 | ..Default::default() 53 | } 54 | } 55 | } 56 | 57 | // -------------------------------------------------- 58 | // Positioned 59 | // -------------------------------------------------- 60 | 61 | #[derive(Debug)] 62 | pub struct Positioned { 63 | pub position: Vector2, 64 | pub child: Box, 65 | } 66 | 67 | impl Layout for Positioned { 68 | fn layout(&self, tree: &mut LayoutTree, constraints: &BoxConstraints) -> SizedLayoutBox { 69 | let child_constraints = BoxConstraints { 70 | min: Vector2::zero(), 71 | max: constraints.max - self.position, 72 | }; 73 | let sbox = self.child.layout(tree, &child_constraints); 74 | let lbox = LayoutBox::from_child(sbox, self.position); 75 | let child_id = tree.insert(lbox); 76 | SizedLayoutBox { 77 | size: constraints.max, 78 | children: vec![child_id], 79 | material: Some(Material::filled(Color::black().alpha(0.1))), 80 | ..Default::default() 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /core/crates/platform/src/browser/driver.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use layout::BoxConstraints; 3 | use std::rc::Rc; 4 | use wasm_bindgen::prelude::wasm_bindgen; 5 | use web_sys::HtmlCanvasElement; 6 | 7 | use super::shaders::ShaderLibrary; 8 | use super::util::try_get_canvas; 9 | use super::WebGl; 10 | use crate::AppDriver; 11 | use layout::{Color, LayoutBox, LayoutTree, Material}; 12 | use math::{Rect, Vector2, Vector3}; 13 | 14 | #[wasm_bindgen] 15 | extern "C" { 16 | // `console.log` in javascript 17 | #[wasm_bindgen(js_namespace = console)] 18 | pub fn log(s: &str); 19 | 20 | // `console.error` in javascript 21 | #[wasm_bindgen(js_namespace = console)] 22 | pub fn error(s: &str); 23 | } 24 | 25 | #[wasm_bindgen] 26 | pub struct BrowserDriver { 27 | canvas: HtmlCanvasElement, 28 | gl: Rc, 29 | shaders: ShaderLibrary, 30 | app: Box, 31 | } 32 | 33 | #[wasm_bindgen] 34 | impl BrowserDriver { 35 | pub fn tick(&mut self, time: f32) { 36 | self.try_tick(time).unwrap(); 37 | } 38 | } 39 | 40 | impl BrowserDriver { 41 | pub fn try_new(canvas_id: &str, app: Box) -> Result { 42 | let canvas = try_get_canvas(canvas_id)?; 43 | let gl = WebGl::try_new(&canvas)?; 44 | let gl = Rc::new(gl); 45 | let shaders = ShaderLibrary::try_new(&gl)?; 46 | Ok(BrowserDriver { 47 | canvas, 48 | gl, 49 | shaders, 50 | app, 51 | }) 52 | } 53 | 54 | pub fn clear(&self, color: Color) { 55 | self.gl.clear(color.r, color.g, color.b, color.a); 56 | } 57 | 58 | pub fn try_tick(&mut self, time: f32) -> Result<(), Error> { 59 | self.clear(Color::black()); 60 | 61 | let width = self.canvas.client_width() as f32; 62 | let height = self.canvas.client_height() as f32; 63 | let viewport = Vector2::new(width, height); 64 | self.shaders.standard.set_viewport(viewport); 65 | 66 | self.paint(time, viewport)?; 67 | Ok(()) 68 | } 69 | 70 | pub fn paint(&mut self, time: f32, viewport: Vector2) -> Result<(), Error> { 71 | let mut tree = LayoutTree::new(); 72 | 73 | let widget_tree = self.app.tick(time); 74 | let constraints = BoxConstraints { 75 | min: Vector2::zero(), 76 | max: viewport, 77 | }; 78 | let root_sbox = widget_tree.layout(&mut tree, &constraints); 79 | let root_lbox = LayoutBox::from_child(root_sbox, (0.0, 0.0)); 80 | let root_id = tree.insert(root_lbox); 81 | tree.set_root(Some(root_id)); 82 | 83 | if time % 5000.0 < 50.0 { 84 | //super::util::log(&format!("{:#?}", tree)); 85 | } 86 | 87 | for (_, child, offset) in tree.iter() { 88 | let min = child.bounds.min + offset + child.margin.min(); 89 | let max = child.bounds.max + offset - child.margin.max(); 90 | let rect = Rect::new(min, max); 91 | self.draw_rect(rect, child.material)?; 92 | } 93 | Ok(()) 94 | } 95 | 96 | pub fn draw_rect(&mut self, rect: Rect, material: Option) -> Result<(), Error> { 97 | match material { 98 | Some(material) => self.shaders.standard.paint_rect(rect, material), 99 | None => Ok(()), 100 | } 101 | } 102 | 103 | pub fn draw_line(&mut self, start: Vector2, end: Vector2, color: Color) -> Result<(), Error> { 104 | let vertices = [ 105 | Vector3::new(start.x, start.y, 0.0), 106 | Vector3::new(end.x, end.y, 0.0), 107 | ]; 108 | self.shaders.standard.set_color(color.to_linear()); 109 | self.shaders.standard.paint_line(&vertices)?; 110 | Ok(()) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /core/crates/layout/src/base.rs: -------------------------------------------------------------------------------- 1 | use math::Vector2; 2 | 3 | // The position of the center of a widget as a fraction of the available area. 4 | // The widget should not overflow at (0.0, 0.0) or at (1.0, 1.0), it should be 5 | // clamped to the edges of the screen. 6 | #[derive(PartialEq, Copy, Clone, Debug, Default)] 7 | pub struct Alignment { 8 | pub x: f32, 9 | pub y: f32, 10 | } 11 | 12 | impl Alignment { 13 | #[must_use] 14 | pub fn new(x: f32, y: f32) -> Alignment { 15 | Alignment { x, y } 16 | } 17 | 18 | #[must_use] 19 | pub fn top_left() -> Alignment { 20 | Alignment::new(0.0, 0.0) 21 | } 22 | 23 | #[must_use] 24 | pub fn top_center() -> Alignment { 25 | Alignment::new(0.5, 0.0) 26 | } 27 | 28 | #[must_use] 29 | pub fn top_right() -> Alignment { 30 | Alignment::new(0.5, 0.0) 31 | } 32 | 33 | #[must_use] 34 | pub fn center_left() -> Alignment { 35 | Alignment::new(0.0, 0.5) 36 | } 37 | 38 | #[must_use] 39 | pub fn center() -> Alignment { 40 | Alignment::new(0.5, 0.5) 41 | } 42 | 43 | #[must_use] 44 | pub fn center_right() -> Alignment { 45 | Alignment::new(1.0, 0.5) 46 | } 47 | 48 | #[must_use] 49 | pub fn bottom_left() -> Alignment { 50 | Alignment::new(0.0, 1.0) 51 | } 52 | 53 | #[must_use] 54 | pub fn bottom_center() -> Alignment { 55 | Alignment::new(0.5, 1.0) 56 | } 57 | 58 | #[must_use] 59 | pub fn bottom_right() -> Alignment { 60 | Alignment::new(1.0, 1.0) 61 | } 62 | 63 | #[must_use] 64 | pub fn to_vector(&self) -> Vector2 { 65 | Vector2::new(self.x, self.y) 66 | } 67 | } 68 | 69 | #[derive(PartialEq, Copy, Clone, Default, Debug)] 70 | pub struct EdgeInsets { 71 | pub top: f32, 72 | pub bottom: f32, 73 | pub left: f32, 74 | pub right: f32, 75 | } 76 | 77 | impl EdgeInsets { 78 | #[must_use] 79 | pub fn zero() -> EdgeInsets { 80 | EdgeInsets::all(0.0) 81 | } 82 | 83 | #[must_use] 84 | pub fn all(inset: f32) -> EdgeInsets { 85 | EdgeInsets { 86 | top: inset, 87 | bottom: inset, 88 | left: inset, 89 | right: inset, 90 | } 91 | } 92 | 93 | #[must_use] 94 | pub fn vertical(inset: f32) -> EdgeInsets { 95 | EdgeInsets { 96 | top: inset, 97 | bottom: inset, 98 | left: 0.0, 99 | right: 0.0, 100 | } 101 | } 102 | 103 | #[must_use] 104 | pub fn horizontal(inset: f32) -> EdgeInsets { 105 | EdgeInsets { 106 | top: 0.0, 107 | bottom: 0.0, 108 | left: inset, 109 | right: inset, 110 | } 111 | } 112 | 113 | #[must_use] 114 | pub fn top(inset: f32) -> EdgeInsets { 115 | EdgeInsets { 116 | top: inset, 117 | bottom: 0.0, 118 | left: 0.0, 119 | right: 0.0, 120 | } 121 | } 122 | 123 | #[must_use] 124 | pub fn bottom(inset: f32) -> EdgeInsets { 125 | EdgeInsets { 126 | top: 0.0, 127 | bottom: inset, 128 | left: 0.0, 129 | right: 0.0, 130 | } 131 | } 132 | 133 | #[must_use] 134 | pub fn left(inset: f32) -> EdgeInsets { 135 | EdgeInsets { 136 | top: 0.0, 137 | bottom: 0.0, 138 | left: inset, 139 | right: 0.0, 140 | } 141 | } 142 | 143 | #[must_use] 144 | pub fn right(inset: f32) -> EdgeInsets { 145 | EdgeInsets { 146 | top: 0.0, 147 | bottom: 0.0, 148 | left: 0.0, 149 | right: inset, 150 | } 151 | } 152 | 153 | #[must_use] 154 | pub fn total_height(&self) -> f32 { 155 | self.left + self.right 156 | } 157 | 158 | #[must_use] 159 | pub fn total_width(&self) -> f32 { 160 | self.top + self.bottom 161 | } 162 | 163 | #[must_use] 164 | pub fn total(&self) -> Vector2 { 165 | let x = self.left + self.right; 166 | let y = self.top + self.bottom; 167 | Vector2::new(x, y) 168 | } 169 | 170 | #[must_use] 171 | pub fn min(&self) -> Vector2 { 172 | Vector2::new(self.left, self.top) 173 | } 174 | 175 | #[must_use] 176 | pub fn max(&self) -> Vector2 { 177 | Vector2::new(self.right, self.bottom) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /core/crates/platform/src/browser/util.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Error}; 2 | use wasm_bindgen::prelude::wasm_bindgen; 3 | use wasm_bindgen::JsCast; 4 | use web_sys::{HtmlCanvasElement, WebGlProgram, WebGlRenderingContext, WebGlShader}; 5 | 6 | #[wasm_bindgen] 7 | extern "C" { 8 | // `console.log` in javascript 9 | #[wasm_bindgen(js_namespace = console)] 10 | pub fn log(s: &str); 11 | 12 | // `console.error` in javascript 13 | #[wasm_bindgen(js_namespace = console)] 14 | pub fn error(s: &str); 15 | } 16 | 17 | // The JS object passsed into the [get_context_with_context_options] 18 | // WebGlRenderingContext constructor. For some reason cargo sees this as dead 19 | // code. 20 | #[wasm_bindgen] 21 | struct WebGlOptions { 22 | #[allow(dead_code)] 23 | alpha: bool, 24 | #[allow(dead_code)] 25 | premultiplied_alpha: bool, 26 | } 27 | 28 | /// Try to get a reference to the [WebGlCanvasElement] identified by the provided ID. 29 | pub fn try_get_canvas(canvas_id: &str) -> Result { 30 | let window = web_sys::window().ok_or_else(|| anyhow!("could not get window"))?; 31 | let document = window 32 | .document() 33 | .ok_or_else(|| anyhow!("could not get document"))?; 34 | document 35 | .get_element_by_id(canvas_id) 36 | .ok_or_else(|| anyhow!("failed to get canvas"))? 37 | .dyn_into::() 38 | .map_err(|_| anyhow!("failed to get canvas")) 39 | } 40 | 41 | /// Try to get a [WebGlRenderingContext] from a reference to a [HtmlCanvasElement]. 42 | pub fn try_get_webgl_context(canvas: &HtmlCanvasElement) -> Result { 43 | let options = WebGlOptions { 44 | alpha: false, 45 | // This is needed otherwise semi-transparent colors are assumed to have 46 | // transparency multiplied into their color, and are rendered weirdly. 47 | premultiplied_alpha: false, 48 | }; 49 | canvas 50 | .get_context_with_context_options("webgl", &options.into()) 51 | .map_err(|_| anyhow::anyhow!("could not get webgl context"))? 52 | .ok_or_else(|| anyhow::anyhow!("could not get webgl context"))? 53 | .dyn_into::() 54 | .map_err(|_| anyhow::anyhow!("could not get webgl context")) 55 | } 56 | 57 | /// This will try to create a shader program that uses the provided vertex and 58 | /// fragment shader sourcecodes. 59 | pub fn try_create_shader_program( 60 | gl: &WebGlRenderingContext, 61 | vertex_shader_src: &str, 62 | fragment_shader_src: &str, 63 | ) -> Result { 64 | crate::browser::util::log("before try compile vertex shader"); 65 | let vertex_shader = 66 | try_compile_shader(gl, WebGlRenderingContext::VERTEX_SHADER, vertex_shader_src)?; 67 | crate::browser::util::log("after try compile vertex shader"); 68 | let fragment_shader = try_compile_shader( 69 | gl, 70 | WebGlRenderingContext::FRAGMENT_SHADER, 71 | fragment_shader_src, 72 | )?; 73 | crate::browser::util::log("after try compile frag shader"); 74 | try_link_program(gl, &vertex_shader, &fragment_shader) 75 | } 76 | 77 | /// Try to link the shader program. If linking fails, this will attempt to get 78 | /// the error message from the program info log. If that also fails, it 79 | /// will return a generic error. 80 | pub fn try_link_program( 81 | gl: &WebGlRenderingContext, 82 | vertex_shader: &WebGlShader, 83 | fragment_shader: &WebGlShader, 84 | ) -> Result { 85 | let program = gl 86 | .create_program() 87 | .ok_or_else(|| anyhow!("could not create shader program"))?; 88 | gl.attach_shader(&program, vertex_shader); 89 | gl.attach_shader(&program, fragment_shader); 90 | gl.link_program(&program); 91 | 92 | let maybe_link_status = gl.get_program_parameter(&program, WebGlRenderingContext::LINK_STATUS); 93 | if !maybe_link_status.as_bool().unwrap_or(false) { 94 | return match gl.get_program_info_log(&program) { 95 | Some(log) => Err(anyhow!(log)), 96 | None => Err(anyhow!("unknown error occured when linking shader program")), 97 | }; 98 | } 99 | Ok(program) 100 | } 101 | 102 | /// Attempt to compile the shader. If compilation fails, this will attempt to 103 | /// get the error message from the shader info log. If that also fails, it will 104 | /// return a generic error. 105 | pub fn try_compile_shader( 106 | gl: &WebGlRenderingContext, 107 | shader_type: u32, 108 | src: &str, 109 | ) -> Result { 110 | let shader = gl 111 | .create_shader(shader_type) 112 | .ok_or_else(|| anyhow!("failed to create shader"))?; 113 | gl.shader_source(&shader, src); 114 | gl.compile_shader(&shader); 115 | 116 | let maybe_compile_status = 117 | gl.get_shader_parameter(&shader, WebGlRenderingContext::COMPILE_STATUS); 118 | if !maybe_compile_status.as_bool().unwrap_or(false) { 119 | return match gl.get_shader_info_log(&shader) { 120 | Some(log) => Err(anyhow!(log)), 121 | None => Err(anyhow!("unknown error occured when compiling shader")), 122 | }; 123 | } 124 | Ok(shader) 125 | } 126 | -------------------------------------------------------------------------------- /core/crates/platform/src/browser/shaders/standard.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use math::{Rect, Vector2, Vector3, Vector4}; 3 | use web_sys::WebGlProgram; 4 | 5 | use super::WebGl; 6 | use layout::{BorderSide, Borders, Color, Material}; 7 | use std::rc::Rc; 8 | 9 | const VERTEX_SHADER: &str = r#" 10 | // Position of the vertex 11 | attribute vec2 a_position; 12 | 13 | // Pixel dimensions of the canvas 14 | uniform vec2 u_viewport; 15 | 16 | varying vec2 v_position; 17 | 18 | void main() { 19 | v_position = a_position; 20 | vec2 zero_to_one = a_position / u_viewport; 21 | vec2 zero_to_two = zero_to_one * 2.0; 22 | vec2 clip_space = zero_to_two - 1.0; 23 | vec2 origin_top_left = vec2(1.0, -1.0) * clip_space; 24 | gl_Position = vec4(origin_top_left, 0.0, 1.0); 25 | } 26 | "#; 27 | 28 | const FRAGMENT_SHADER: &str = r#" 29 | precision mediump float; 30 | 31 | // Color of the rect 32 | uniform vec4 u_color; 33 | 34 | struct border_side { 35 | bool enabled; 36 | float width; 37 | vec4 color; 38 | }; 39 | 40 | uniform border_side u_borders[4]; 41 | 42 | uniform vec2 u_rect_min; 43 | uniform vec2 u_rect_max; 44 | 45 | // Current position 46 | varying vec2 v_position; 47 | 48 | void main() { 49 | border_side top_border = u_borders[0]; 50 | border_side bottom_border = u_borders[1]; 51 | border_side left_border = u_borders[2]; 52 | border_side right_border = u_borders[3]; 53 | 54 | // Probably need to do something here during resizing on fractional values 55 | if (top_border.enabled && v_position.y < u_rect_min.y + top_border.width) { 56 | gl_FragColor = top_border.color; 57 | return; 58 | } else if (bottom_border.enabled && v_position.y > u_rect_max.y - bottom_border.width) { 59 | gl_FragColor = bottom_border.color; 60 | return; 61 | } else if (left_border.enabled && v_position.x < u_rect_min.x + left_border.width) { 62 | gl_FragColor = left_border.color; 63 | return; 64 | } else if (right_border.enabled && v_position.x > u_rect_max.x - right_border.width) { 65 | gl_FragColor = right_border.color; 66 | return; 67 | } 68 | gl_FragColor = u_color; 69 | } 70 | "#; 71 | 72 | pub struct StandardShader { 73 | gl: Rc, 74 | program: WebGlProgram, 75 | 76 | viewport: Vector2, 77 | color: Vector4, 78 | } 79 | 80 | impl StandardShader { 81 | pub fn try_new(gl: &Rc) -> Result { 82 | crate::browser::util::log("before create shader program"); 83 | let program = gl.try_create_shader_program(VERTEX_SHADER, FRAGMENT_SHADER)?; 84 | crate::browser::util::log("after create shader program"); 85 | Ok(StandardShader { 86 | gl: Rc::clone(gl), 87 | program, 88 | viewport: Vector2::zero(), 89 | color: Vector4::new(1.0, 1.0, 1.0, 1.0), 90 | }) 91 | } 92 | 93 | pub fn set_viewport(&mut self, viewport: Vector2) { 94 | self.viewport = viewport; 95 | } 96 | 97 | pub fn set_color(&mut self, color: Vector4) { 98 | self.color = color; 99 | } 100 | 101 | fn set_uniforms(&self) -> Result<(), Error> { 102 | self.gl 103 | .set_uniform_vec2(&self.program, "u_viewport", self.viewport)?; 104 | Ok(()) 105 | } 106 | 107 | pub fn paint_mesh(&self, vertices: &[Vector3]) -> Result<(), Error> { 108 | self.set_uniforms()?; 109 | let buffer = self.gl.new_array_buffer(vertices)?; 110 | self.gl.draw_mesh(&self.program, "a_position", &buffer)?; 111 | Ok(()) 112 | } 113 | 114 | pub fn paint_line(&self, vertices: &[Vector3]) -> Result<(), Error> { 115 | self.set_uniforms()?; 116 | let buffer = self.gl.new_array_buffer(vertices)?; 117 | self.gl.draw_line(&self.program, "a_position", &buffer)?; 118 | Ok(()) 119 | } 120 | 121 | pub fn paint_rect(&mut self, rect: Rect, material: Material) -> Result<(), Error> { 122 | self.set_color(material.fill.to_linear()); 123 | let (min_x, min_y) = rect.min.into(); 124 | let (max_x, max_y) = rect.max.into(); 125 | self.gl 126 | .set_uniform_vec4(&self.program, "u_color", self.color)?; 127 | self.set_borders(material.borders)?; 128 | self.gl 129 | .set_uniform_vec2(&self.program, "u_rect_max", rect.max)?; 130 | self.gl 131 | .set_uniform_vec2(&self.program, "u_rect_min", rect.min)?; 132 | let vertices: [Vector3; 6] = [ 133 | Vector3::new(min_x, min_y, 0.0), 134 | Vector3::new(min_x, max_y, 0.0), 135 | Vector3::new(max_x, min_y, 0.0), 136 | Vector3::new(min_x, max_y, 0.0), 137 | Vector3::new(max_x, min_y, 0.0), 138 | Vector3::new(max_x, max_y, 0.0), 139 | ]; 140 | self.paint_mesh(&vertices)?; 141 | Ok(()) 142 | } 143 | 144 | fn set_borders(&mut self, borders: Borders) -> Result<(), Error> { 145 | self.set_border(0, borders.top)?; 146 | self.set_border(1, borders.bottom)?; 147 | self.set_border(2, borders.left)?; 148 | self.set_border(3, borders.right)?; 149 | Ok(()) 150 | } 151 | 152 | fn set_border(&mut self, i: usize, maybe_border: Option) -> Result<(), Error> { 153 | let BorderSide { color, width } = match maybe_border { 154 | Some(border) => border, 155 | None => { 156 | self.gl.set_uniform_i32( 157 | &self.program, 158 | &format!("u_borders[{:?}].enabled", i), 159 | 0, 160 | )?; 161 | return Ok(()); 162 | } 163 | }; 164 | self.gl 165 | .set_uniform_i32(&self.program, &format!("u_borders[{:?}].enabled", i), 1)?; 166 | self.gl 167 | .set_uniform_f32(&self.program, &format!("u_borders[{:?}].width", i), width)?; 168 | self.gl.set_uniform_vec4( 169 | &self.program, 170 | &format!("u_borders[{:?}].color", i), 171 | color.to_linear(), 172 | )?; 173 | Ok(()) 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /core/crates/layout/src/decoration.rs: -------------------------------------------------------------------------------- 1 | use math::{Vector2, Vector4}; 2 | 3 | #[derive(PartialEq, Clone, Copy, Debug)] 4 | pub struct Material { 5 | pub borders: Borders, 6 | pub fill: Color, 7 | } 8 | 9 | impl Material { 10 | #[must_use] 11 | pub fn filled(fill: Color) -> Material { 12 | Material { 13 | borders: Borders::none(), 14 | fill, 15 | } 16 | } 17 | } 18 | 19 | #[derive(PartialEq, Clone, Copy, Debug)] 20 | pub struct BorderSide { 21 | pub color: Color, 22 | pub width: f32, 23 | } 24 | 25 | impl BorderSide { 26 | #[must_use] 27 | pub fn new(color: Color, width: f32) -> BorderSide { 28 | BorderSide { color, width } 29 | } 30 | } 31 | 32 | #[derive(PartialEq, Clone, Copy, Default, Debug)] 33 | pub struct Borders { 34 | pub top: Option, 35 | pub bottom: Option, 36 | pub left: Option, 37 | pub right: Option, 38 | } 39 | 40 | impl Borders { 41 | #[must_use] 42 | pub fn all(color: Color, width: f32) -> Borders { 43 | Borders { 44 | top: Some(BorderSide::new(color, width)), 45 | bottom: Some(BorderSide::new(color, width)), 46 | left: Some(BorderSide::new(color, width)), 47 | right: Some(BorderSide::new(color, width)), 48 | } 49 | } 50 | 51 | #[must_use] 52 | pub fn none() -> Borders { 53 | Borders { 54 | top: None, 55 | bottom: None, 56 | left: None, 57 | right: None, 58 | } 59 | } 60 | 61 | #[must_use] 62 | pub fn top(color: Color, width: f32) -> Borders { 63 | Borders { 64 | top: Some(BorderSide::new(color, width)), 65 | ..Borders::default() 66 | } 67 | } 68 | 69 | #[must_use] 70 | pub fn bottom(color: Color, width: f32) -> Borders { 71 | Borders { 72 | bottom: Some(BorderSide::new(color, width)), 73 | ..Borders::default() 74 | } 75 | } 76 | 77 | #[must_use] 78 | pub fn left(color: Color, width: f32) -> Borders { 79 | Borders { 80 | left: Some(BorderSide::new(color, width)), 81 | ..Borders::default() 82 | } 83 | } 84 | 85 | #[must_use] 86 | pub fn right(color: Color, width: f32) -> Borders { 87 | Borders { 88 | right: Some(BorderSide::new(color, width)), 89 | ..Borders::default() 90 | } 91 | } 92 | 93 | #[must_use] 94 | pub fn min(&self) -> Vector2 { 95 | let top = match self.top { 96 | Some(border) => border.width, 97 | None => 0.0, 98 | }; 99 | let left = match self.left { 100 | Some(border) => border.width, 101 | None => 0.0, 102 | }; 103 | Vector2::new(left, top) 104 | } 105 | 106 | #[must_use] 107 | pub fn max(&self) -> Vector2 { 108 | let top = match self.top { 109 | Some(border) => border.width, 110 | None => 0.0, 111 | }; 112 | let right = match self.right { 113 | Some(border) => border.width, 114 | None => 0.0, 115 | }; 116 | Vector2::new(right, top) 117 | } 118 | 119 | #[must_use] 120 | pub fn total_width(&self) -> f32 { 121 | let left = match self.left { 122 | Some(border) => border.width, 123 | None => 0.0, 124 | }; 125 | let right = match self.right { 126 | Some(border) => border.width, 127 | None => 0.0, 128 | }; 129 | left + right 130 | } 131 | 132 | #[must_use] 133 | pub fn total_height(&self) -> f32 { 134 | let top = match self.top { 135 | Some(border) => border.width, 136 | None => 0.0, 137 | }; 138 | let bottom = match self.bottom { 139 | Some(border) => border.width, 140 | None => 0.0, 141 | }; 142 | top + bottom 143 | } 144 | } 145 | 146 | impl Default for Material { 147 | fn default() -> Material { 148 | Material::filled(Color::transparent()) 149 | } 150 | } 151 | 152 | /// A color stored as RGBA components, each ranging from 0 - 255. 153 | #[derive(PartialEq, Copy, Clone, Debug)] 154 | pub struct Color { 155 | pub r: f32, 156 | pub g: f32, 157 | pub b: f32, 158 | pub a: f32, 159 | } 160 | 161 | impl Default for Color { 162 | fn default() -> Color { 163 | Color::transparent() 164 | } 165 | } 166 | 167 | impl Color { 168 | #[must_use] 169 | pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Color { 170 | Color { r, g, b, a } 171 | } 172 | 173 | #[must_use] 174 | pub fn transparent() -> Color { 175 | Color::rgba(0.0, 0.0, 0.0, 0.0) 176 | } 177 | 178 | #[must_use] 179 | pub fn red() -> Color { 180 | Color::rgba(255.0, 0.0, 0.0, 255.0) 181 | } 182 | 183 | #[must_use] 184 | pub fn green() -> Color { 185 | Color::rgba(0.0, 255.0, 0.0, 255.0) 186 | } 187 | 188 | #[must_use] 189 | pub fn blue() -> Color { 190 | Color::rgba(0.0, 0.0, 255.0, 255.0) 191 | } 192 | 193 | #[must_use] 194 | pub fn yellow() -> Color { 195 | Color::rgba(255.0, 255.0, 0.0, 255.0) 196 | } 197 | 198 | #[must_use] 199 | pub fn white() -> Color { 200 | Color::rgba(255.0, 255.0, 255.0, 255.0) 201 | } 202 | 203 | #[must_use] 204 | pub fn black() -> Color { 205 | Color::rgba(0.0, 0.0, 0.0, 255.0) 206 | } 207 | 208 | // The alpha is between 0 and 1 209 | #[must_use] 210 | pub fn alpha(self, alpha: f32) -> Color { 211 | Color::rgba(self.r, self.g, self.b, alpha * 255.0) 212 | } 213 | 214 | #[must_use] 215 | pub fn to_linear(&self) -> Vector4 { 216 | let r = self.r / 255.0; 217 | let g = self.g / 255.0; 218 | let b = self.b / 255.0; 219 | let a = self.a / 255.0; 220 | Vector4::new(r, g, b, a) 221 | } 222 | } 223 | 224 | #[cfg(test)] 225 | mod tests { 226 | use super::*; 227 | 228 | #[test] 229 | fn color_partial_eq_with_same_color_returns_true() { 230 | let red_lhs = Color::red(); 231 | let red_rhs = Color::red(); 232 | assert_eq!(red_lhs, red_rhs); 233 | } 234 | 235 | #[test] 236 | fn color_partial_eq_with_different_color_returns_false() { 237 | let red = Color::red(); 238 | let green = Color::green(); 239 | assert_ne!(red, green); 240 | } 241 | 242 | #[test] 243 | fn material_partial_eq_with_different_color_returns_false() { 244 | let red = Material::filled(Color::red()); 245 | let green = Material::filled(Color::green()); 246 | assert_ne!(red, green); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /core/crates/platform/src/browser/webgl.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Error}; 2 | use bytemuck::cast_slice; 3 | use js_sys::WebAssembly; 4 | use math::{Vector2, Vector3, Vector4}; 5 | use wasm_bindgen::JsCast; 6 | use web_sys::{HtmlCanvasElement, WebGlBuffer, WebGlProgram, WebGlRenderingContext}; 7 | 8 | pub struct WebGl { 9 | pub gl: WebGlRenderingContext, 10 | } 11 | 12 | impl WebGl { 13 | pub fn try_new(canvas: &HtmlCanvasElement) -> Result { 14 | let gl = super::util::try_get_webgl_context(canvas)?; 15 | Ok(WebGl { gl }) 16 | } 17 | 18 | pub fn clear(&self, r: f32, g: f32, b: f32, a: f32) { 19 | self.gl.clear_color(r, g, b, a); 20 | self.gl.clear(WebGlRenderingContext::COLOR_BUFFER_BIT); 21 | } 22 | 23 | pub fn draw_line( 24 | &self, 25 | program: &WebGlProgram, 26 | vertex_attribute_name: &str, 27 | buffer: &Buffer, 28 | ) -> Result<(), Error> { 29 | self.gl.use_program(Some(program)); 30 | 31 | self.gl 32 | .bind_buffer(WebGlRenderingContext::ARRAY_BUFFER, Some(&buffer.buffer)); 33 | let location = self.gl.get_attrib_location(program, vertex_attribute_name) as u32; 34 | self.gl.vertex_attrib_pointer_with_i32( 35 | location, 36 | buffer.element_size as i32, 37 | WebGlRenderingContext::FLOAT, 38 | false, 39 | 0, 40 | 0, 41 | ); 42 | self.gl.enable_vertex_attrib_array(location); 43 | self.gl 44 | .bind_buffer(WebGlRenderingContext::ARRAY_BUFFER, None); 45 | 46 | self.gl.draw_arrays( 47 | WebGlRenderingContext::LINES, 48 | 0, 49 | (buffer.len / buffer.element_size) as i32, 50 | ); 51 | 52 | Ok(()) 53 | } 54 | 55 | pub fn draw_mesh( 56 | &self, 57 | program: &WebGlProgram, 58 | vertex_attribute_name: &str, 59 | buffer: &Buffer, 60 | ) -> Result<(), Error> { 61 | self.gl.enable(WebGlRenderingContext::BLEND); 62 | self.gl.blend_func( 63 | WebGlRenderingContext::SRC_ALPHA, 64 | WebGlRenderingContext::ONE_MINUS_SRC_ALPHA, 65 | ); 66 | self.gl 67 | .bind_buffer(WebGlRenderingContext::ARRAY_BUFFER, Some(&buffer.buffer)); 68 | 69 | // Upload to GPU 70 | self.gl.use_program(Some(program)); 71 | let location = self.gl.get_attrib_location(program, vertex_attribute_name) as u32; 72 | self.gl.vertex_attrib_pointer_with_i32( 73 | location, 74 | buffer.element_size as i32, 75 | WebGlRenderingContext::FLOAT, 76 | false, 77 | 0, 78 | 0, 79 | ); 80 | self.gl.enable_vertex_attrib_array(location); 81 | self.gl.draw_arrays( 82 | WebGlRenderingContext::TRIANGLES, 83 | 0, 84 | (buffer.len / buffer.element_size) as i32, 85 | ); 86 | self.gl 87 | .bind_buffer(WebGlRenderingContext::ARRAY_BUFFER, None); 88 | Ok(()) 89 | } 90 | 91 | pub fn new_array_buffer(&self, values: &[Vector3]) -> Result { 92 | let bytes: &[f32] = cast_slice(values); 93 | let memory_buffer = wasm_bindgen::memory() 94 | .dyn_into::() 95 | .map_err(|_| anyhow!("could not get web assembly memory"))? 96 | .buffer(); 97 | // Divide by 4 to get an index to individual f32 elements, because an f32 98 | // is 4 bytes long. 99 | let arr_location = bytes.as_ptr() as u32 / 4; 100 | let js_array = js_sys::Float32Array::new(&memory_buffer) 101 | .subarray(arr_location, arr_location + bytes.len() as u32); 102 | 103 | let buffer = self 104 | .gl 105 | .create_buffer() 106 | .ok_or_else(|| anyhow!("could not create buffer"))?; 107 | self.gl 108 | .bind_buffer(WebGlRenderingContext::ARRAY_BUFFER, Some(&buffer)); 109 | self.gl.buffer_data_with_array_buffer_view( 110 | WebGlRenderingContext::ARRAY_BUFFER, 111 | &js_array, 112 | WebGlRenderingContext::STATIC_DRAW, 113 | ); 114 | self.gl 115 | .bind_buffer(WebGlRenderingContext::ARRAY_BUFFER, None); 116 | Ok(Buffer { 117 | buffer, 118 | len: bytes.len() as u32, 119 | element_size: 3, 120 | }) 121 | } 122 | 123 | pub fn set_uniform_i32( 124 | &self, 125 | program: &WebGlProgram, 126 | field: &str, 127 | value: i32, 128 | ) -> Result<(), Error> { 129 | self.gl.use_program(Some(program)); 130 | let location = self 131 | .gl 132 | .get_uniform_location(program, field) 133 | .ok_or_else(|| anyhow::anyhow!("could not get location for uniform float {}", field))?; 134 | self.gl.uniform1i(Some(&location), value); 135 | Ok(()) 136 | } 137 | 138 | pub fn set_uniform_f32( 139 | &self, 140 | program: &WebGlProgram, 141 | field: &str, 142 | value: f32, 143 | ) -> Result<(), Error> { 144 | self.gl.use_program(Some(program)); 145 | let location = self 146 | .gl 147 | .get_uniform_location(program, field) 148 | .ok_or_else(|| anyhow::anyhow!("could not get location for uniform float {}", field))?; 149 | self.gl.uniform1f(Some(&location), value); 150 | Ok(()) 151 | } 152 | 153 | pub fn set_uniform_vec2( 154 | &self, 155 | program: &WebGlProgram, 156 | field: &str, 157 | value: Vector2, 158 | ) -> Result<(), Error> { 159 | self.gl.use_program(Some(program)); 160 | let location = self 161 | .gl 162 | .get_uniform_location(program, field) 163 | .ok_or_else(|| anyhow::anyhow!("could not get location for uniform vec2 {}", field))?; 164 | let value: [f32; 2] = value.into(); 165 | self.gl.uniform2fv_with_f32_array(Some(&location), &value); 166 | Ok(()) 167 | } 168 | 169 | pub fn set_uniform_vec4( 170 | &self, 171 | program: &WebGlProgram, 172 | field: &str, 173 | value: Vector4, 174 | ) -> Result<(), Error> { 175 | self.gl.use_program(Some(program)); 176 | let location = self 177 | .gl 178 | .get_uniform_location(program, field) 179 | .ok_or_else(|| anyhow::anyhow!("could not get location for uniform vec4 {}", field))?; 180 | let value: [f32; 4] = value.into(); 181 | self.gl.uniform4fv_with_f32_array(Some(&location), &value); 182 | Ok(()) 183 | } 184 | 185 | pub fn try_create_shader_program( 186 | &self, 187 | vertex_shader_src: &str, 188 | fragment_shader_src: &str, 189 | ) -> Result { 190 | super::util::try_create_shader_program(&self.gl, vertex_shader_src, fragment_shader_src) 191 | } 192 | } 193 | 194 | /// Represents an arbitrary amount of data held in the GPU. 195 | #[derive(Clone, Debug)] 196 | pub struct Buffer { 197 | /// The [WebGlBuffer] that holds the data. 198 | pub buffer: WebGlBuffer, 199 | /// The number of elements in the buffer. 200 | pub len: u32, 201 | /// The number of elements per type. For example, Vector2 is size 2. 202 | pub element_size: u32, 203 | } 204 | -------------------------------------------------------------------------------- /core/crates/layout/src/container.rs: -------------------------------------------------------------------------------- 1 | use crate::base::{Alignment, EdgeInsets}; 2 | use crate::decoration::{Borders, Color, Material}; 3 | use crate::tree::{BoxConstraints, Layout, LayoutBox, LayoutTree, SizedLayoutBox}; 4 | use math::Vector2; 5 | use std::fmt::Debug; 6 | 7 | #[derive(Debug)] 8 | pub struct Spacer {} 9 | 10 | impl Layout for Spacer { 11 | fn layout(&self, _: &mut LayoutTree, constraints: &BoxConstraints) -> SizedLayoutBox { 12 | SizedLayoutBox { 13 | size: constraints.max, 14 | children: vec![], 15 | material: None, 16 | margin: EdgeInsets::zero(), 17 | } 18 | } 19 | } 20 | 21 | #[derive(Debug, Default)] 22 | pub struct Container { 23 | pub width: Option, 24 | pub height: Option, 25 | pub alignment: Alignment, 26 | pub padding: EdgeInsets, 27 | pub borders: Borders, 28 | pub margin: EdgeInsets, 29 | pub color: Color, 30 | pub child: Option>, 31 | } 32 | 33 | impl Layout for Container { 34 | fn layout(&self, tree: &mut LayoutTree, constraints: &BoxConstraints) -> SizedLayoutBox { 35 | match &self.child { 36 | Some(child) => self.layout_with_child(tree, constraints, child.as_ref()), 37 | None => self.layout_without_child(constraints), 38 | } 39 | } 40 | } 41 | 42 | impl Container { 43 | fn layout_with_child( 44 | &self, 45 | tree: &mut LayoutTree, 46 | constraints: &BoxConstraints, 47 | child: &dyn Layout, 48 | ) -> SizedLayoutBox { 49 | let h_axis_constraints = constraints.horizontal(); 50 | let v_axis_constraints = constraints.vertical(); 51 | let width = Container::calculate_size(self.width, h_axis_constraints); 52 | let height = Container::calculate_size(self.height, v_axis_constraints); 53 | 54 | let h_padding = self.padding.left + self.padding.right + self.borders.total_width(); 55 | let v_padding = self.padding.top + self.padding.bottom + self.borders.total_height(); 56 | let child_h_constraints = match width { 57 | Some(width) => Vector2::new(0.0, width - h_padding), 58 | None => h_axis_constraints - Vector2::new(h_padding, 0.0), 59 | }; 60 | let child_v_constraints = match height { 61 | Some(height) => Vector2::new(0.0, height - v_padding), 62 | None => v_axis_constraints - Vector2::new(0.0, v_padding), 63 | }; 64 | let child_constraints = BoxConstraints { 65 | min: Vector2::new(child_h_constraints.x, child_v_constraints.x), 66 | max: Vector2::new(child_h_constraints.y, child_v_constraints.y), 67 | }; 68 | let sbox = child.layout(tree, &child_constraints); 69 | let child_size = sbox.size; 70 | 71 | let mut pos_x = 0.0; 72 | let mut pos_y = 0.0; 73 | let size_x = match width { 74 | Some(width) => { 75 | pos_x = width * self.alignment.x; 76 | width 77 | } 78 | None => child_size.x, 79 | }; 80 | let size_y = match height { 81 | Some(height) => { 82 | pos_y = height * self.alignment.y; 83 | height 84 | } 85 | None => child_size.y, 86 | }; 87 | let size = Vector2::new(size_x, size_y) + self.margin.total(); 88 | 89 | let pos_x = (pos_x - child_size.x * 0.5).clamp(0.0, constraints.max.x - child_size.x); 90 | let pos_y = (pos_y - child_size.y * 0.5).clamp(0.0, constraints.max.y - child_size.y); 91 | let pos = Vector2::new(pos_x, pos_y); 92 | let lbox = LayoutBox::from_child( 93 | sbox, 94 | pos + self.margin.min() + self.padding.min() + self.borders.min(), 95 | ); 96 | let id = tree.insert(lbox); 97 | 98 | SizedLayoutBox { 99 | size, 100 | children: vec![id], 101 | material: Some(Material { 102 | fill: self.color, 103 | borders: self.borders, 104 | }), 105 | margin: self.margin, 106 | } 107 | } 108 | 109 | fn layout_without_child(&self, constraints: &BoxConstraints) -> SizedLayoutBox { 110 | let h_axis_constraints = constraints.horizontal(); 111 | let v_axis_constraints = constraints.vertical(); 112 | let width = Container::calculate_size(self.width, h_axis_constraints).unwrap_or(0.0); 113 | let height = Container::calculate_size(self.height, v_axis_constraints).unwrap_or(0.0); 114 | let size = Vector2::new(width, height) + self.margin.total(); 115 | SizedLayoutBox { 116 | size, 117 | children: vec![], 118 | material: Some(Material { 119 | fill: self.color, 120 | borders: self.borders, 121 | }), 122 | margin: self.margin, 123 | } 124 | } 125 | 126 | // If return none, then shrink to fit child 127 | fn calculate_size(desired_size: Option, axis_constraints: Vector2) -> Option { 128 | let (min_size, max_size) = axis_constraints.into(); 129 | match desired_size { 130 | Some(size) => Some(size.clamp(min_size, max_size)), 131 | None if max_size == f32::INFINITY => None, 132 | None => Some(max_size), 133 | } 134 | } 135 | } 136 | 137 | #[cfg(test)] 138 | mod tests { 139 | use super::*; 140 | use math::Rect; 141 | use test_util::assert_slice_eq; 142 | 143 | #[test] 144 | pub fn container_with_no_child_fills_constraints() { 145 | let container = Container { 146 | color: Color::green(), 147 | ..Container::default() 148 | }; 149 | 150 | let constraints = BoxConstraints::from_max(Vector2::new(100.0, 100.0)); 151 | let actual_layout = layout_with_constraints(&container, &constraints); 152 | let expected_layout = vec![LayoutBox { 153 | bounds: Rect::from_pos((0.0, 0.0), (100.0, 100.0)), 154 | ..fixed_child_lbox(Color::green()) 155 | }]; 156 | assert_slice_eq(&expected_layout, &actual_layout); 157 | } 158 | 159 | #[test] 160 | pub fn container_with_no_child_and_has_height_fills_container_width() { 161 | let container = Container { 162 | height: Some(50.0), 163 | color: Color::green(), 164 | ..Container::default() 165 | }; 166 | 167 | let constraints = BoxConstraints::from_max(Vector2::new(100.0, 100.0)); 168 | let actual_layout = layout_with_constraints(&container, &constraints); 169 | let expected_layout = vec![LayoutBox { 170 | bounds: Rect::from_pos((0.0, 0.0), (100.0, 50.0)), 171 | ..fixed_child_lbox(Color::green()) 172 | }]; 173 | assert_slice_eq(&expected_layout, &actual_layout); 174 | } 175 | 176 | #[test] 177 | pub fn container_with_no_child_and_has_width_fills_container_height() { 178 | let container = Container { 179 | width: Some(50.0), 180 | color: Color::green(), 181 | ..Container::default() 182 | }; 183 | 184 | let constraints = BoxConstraints::from_max(Vector2::new(100.0, 100.0)); 185 | let actual_layout = layout_with_constraints(&container, &constraints); 186 | let expected_layout = vec![LayoutBox { 187 | bounds: Rect::from_pos((0.0, 0.0), (50.0, 100.0)), 188 | ..fixed_child_lbox(Color::green()) 189 | }]; 190 | assert_slice_eq(&expected_layout, &actual_layout); 191 | } 192 | 193 | #[test] 194 | pub fn container_with_child_has_same_size_as_child_in_unbounded_parent() { 195 | let container = Container { 196 | color: Color::green(), 197 | child: Some(Box::new(Container { 198 | width: Some(50.0), 199 | height: Some(50.0), 200 | color: Color::red(), 201 | ..Container::default() 202 | })), 203 | ..Container::default() 204 | }; 205 | 206 | let constraints = BoxConstraints::from_max(Vector2::new(f32::INFINITY, f32::INFINITY)); 207 | let actual_layout = layout_with_constraints(&container, &constraints); 208 | let expected_layout = vec![ 209 | LayoutBox { 210 | bounds: Rect::from_pos((0.0, 0.0), (50.0, 50.0)), 211 | ..fixed_child_lbox(Color::red()) 212 | }, 213 | LayoutBox { 214 | bounds: Rect::from_pos((0.0, 0.0), (50.0, 50.0)), 215 | children: vec![0], 216 | ..fixed_child_lbox(Color::green()) 217 | }, 218 | ]; 219 | assert_slice_eq(&expected_layout, &actual_layout); 220 | } 221 | 222 | // -------------------------------------------------- 223 | // Helpers 224 | // -------------------------------------------------- 225 | 226 | fn layout_with_constraints( 227 | widget: &dyn Layout, 228 | constraints: &BoxConstraints, 229 | ) -> Vec { 230 | let mut tree = LayoutTree::new(); 231 | let sbox = widget.layout(&mut tree, constraints); 232 | let lbox = LayoutBox::from_child(sbox, Vector2::zero()); 233 | tree.insert(lbox); 234 | tree.boxes 235 | } 236 | 237 | fn fixed_child_lbox(color: Color) -> LayoutBox { 238 | LayoutBox { 239 | bounds: Rect::from_size((10.0, 10.0)), 240 | children: vec![], 241 | material: Some(Material::filled(color)), 242 | ..LayoutBox::default() 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `wasm-ui` 2 | 3 | Hey! Welcome to my little experiment. It's a Rust library for building user interfaces on the web. You write the interface in Rust, and `wasm-ui` compiles it to WebAssembly and renders to a [WebGL canvas](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API). 4 | 5 | See the [roadmap](#roadmap) for more info. Today, the library implements a box model, flex layout, and can render rectangles of single colors. 6 | 7 | **Why am I building this?** 8 | 9 | * Building the library is fun. Kinda. It's equal parts pain and enjoyment. 10 | * I was curious about how [Figma](https://www.figma.com/blog/webassembly-cut-figmas-load-time-by-3x/) and [Google Docs](https://workspaceupdates.googleblog.com/2021/05/Google-Docs-Canvas-Based-Rendering-Update.html) use WebGL for their interfaces. 11 | * WebAssembly can reduce the load time of certain web applications. 12 | * In theory, it could be ported to native platforms without the overhead of webviews components like Electron or Cordova. 13 | * There's potential for GPU-accelerating drawing. 14 | 15 | **Why should you be skeptical?** 16 | 17 | There are quite a lot of hurdles to overcome. These are the big ones: 18 | 19 | * This is (very) hard to build. I'm effectively rebuilding the layout + render pipeline of a browser. 20 | * It's harder to use and doesn't interop nicely with existing Javascript libraries. 21 | * Accessibility must be built from scratch using a parallel DOM structure in order for the browser to generate the [accessibility tree.](https://developer.mozilla.org/en-US/docs/Glossary/Accessibility_tree) 22 | * Most websites won't benefit from it. 23 | 24 | Again, this is an experiment. Very little works yet, but I still think it's pretty cool. Thanks for checking it out ❤️ 25 | 26 | ## Screenshots 27 | 28 | **VS Code Layout Experiment** 29 | 30 | This is an experiment to mimic the VS code editor layout. It demonstrates the user of flex layout, box borders, and fill colors. 31 | 32 | Notice that it's missing any text. That still needs to be implemented. 33 | 34 | Screen Shot 2021-11-25 at 10 55 38 pm 35 | 36 | It is roughly implemented like this (I've elided some widget definitions to keep this readable): 37 | 38 | ```rust 39 | let widgets = Box::new(Container { 40 | borders: Borders::bottom(status_bar_color, 10.0), 41 | child: Some(Box::new(Flex { 42 | axis: Axis::Horizontal, 43 | main_axis_size: MainAxisSize::Max, 44 | main_axis_alignment: MainAxisAlignment::Start, 45 | cross_axis_alignment: CrossAxisAlignment::Stretch, 46 | children: vec![ 47 | Box::new(/* First sidebar */), 48 | Box::new(/* Second sidebar */), 49 | Flexible { 50 | flex_factor: 1.0, 51 | child: Box::new(/* Empty center area */), 52 | }, 53 | Box::new(/* Third sidebar */), 54 | ] 55 | })), 56 | ..Container::default() 57 | }); 58 | ``` 59 | 60 | ## Usage 61 | 62 | This library isn't distributed yet. If you want to use it, you have to clone it and write code in the `core/src` directory. I've already written the boilerplate, and the library code lives in the `core/crates` subdirectory. 63 | 64 | The following commands will clone the project, build it, and begin serving the demo application on `localhost:8080`: 65 | 66 | ```bash 67 | $ git clone https://github.com/harrisonturton/wasm-ui.git 68 | $ cd wasm-ui && ./bin/build 69 | $ ./bin/start 70 | ``` 71 | 72 | ## Roadmap 73 | 74 | ### Figuring out what's possible 75 | 76 | These are messy one-off experiments to make sure I can actually build 77 | everything. 78 | 79 | - [x] Build + deploy Rust on the web 80 | - [x] Control a WebGL canvas with Rust 81 | - [x] Render glyphs from a font 82 | - [x] Implement a basic box layout algorithm (based on Flutter + CSS box model) 83 | - [x] Pass browser events to the Rust library (cross the JS-WASM boundary) 84 | 85 | ### Actual Roadmap 86 | 87 | Now that I've finished with the little experiments, I'm confident that building 88 | the UI library is *possible*. Not easy, but possible. The current challenge is 89 | to combine all the individual spike projects, ideally with some half-decent 90 | architecture. 91 | 92 | - [x] Build WebGL render driver 93 | - [x] Build data structure to represent the UI 94 | - [x] Implement box layout algorithm 95 | - [x] Implement flex layout 96 | - [ ] Write layout tests 🙈 97 | - [ ] Add box styles 98 | 99 | ## Documentation 100 | 101 | ### Minimal working web example 102 | 103 | ```rust 104 | use platform::browser::BrowserDriver; 105 | use wasm_bindgen::prelude::wasm_bindgen; 106 | 107 | // Use `wee_alloc` as the global allocator, because it is smaller. 108 | #[cfg(feature = "wee_alloc")] 109 | #[global_allocator] 110 | static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; 111 | 112 | /// This is called from the browser as soon as the WASM package is loaded. It is 113 | /// the main entrypoint to the application. 114 | #[wasm_bindgen] 115 | pub fn start(canvas_id: &str) -> BrowserDriver { 116 | // Forward panic messages to console.error 117 | #[cfg(feature = "console_error_panic_hook")] 118 | std::panic::set_hook(Box::new(console_error_panic_hook::hook)); 119 | 120 | let app = App::new(); 121 | BrowserDriver::try_new(canvas_id, Box::new(app)).unwrap() 122 | } 123 | 124 | pub struct App {} 125 | 126 | impl AppDriver for App { 127 | fn tick(&mut self, time: f32) -> Box { 128 | Box::new(Container { 129 | size: (100.0, 100.0).into(), 130 | color: Color::blue(), 131 | ..Default::default() 132 | }) 133 | } 134 | } 135 | ``` 136 | 137 | ### App Boilerplate 138 | 139 | The library only requires that your application implements the `AppDriver` 140 | trait. This allows your app to be "driven" by a variety of different platforms. 141 | 142 | ```rust 143 | pub trait AppDriver { 144 | fn tick(&mut self, time: f32) -> Box; 145 | } 146 | ``` 147 | 148 | This is called every frame. The `Layout` trait is implemented by widgets that 149 | can be rendered by the `wasm-ui` library. For example, a simple app might look 150 | like: 151 | 152 | ```rust 153 | pub struct App { 154 | position: f32, 155 | } 156 | 157 | impl AppDriver for App { 158 | fn tick(&mut self, time: f32) -> Box { 159 | self.position.x += 100.0 * time.sin(); 160 | Box::new(Container { 161 | size: self.position, 162 | color: Color::blue(), 163 | ..Default::default() 164 | }) 165 | } 166 | } 167 | ``` 168 | 169 | This will render a blue square that is 100 pixels wide and 100 pixels tall that 170 | moves back and forth on the screen. 171 | 172 | Note the usage of `Default::default()`. This allows us to only define the fields 173 | we need, rather than being forced to specify every single in a widget. In this case, `Container` is defined like this: 174 | 175 | ```rust 176 | pub struct Container { 177 | pub size: Vector2, 178 | pub color: Color, 179 | pub child: Option>, 180 | } 181 | ``` 182 | 183 | By using `..Default::default()`, it automatically sets `Container.child` to 184 | `None`. In this example it doesn't help us too much, but it's more useful with 185 | widgets that are highly configurable. 186 | 187 | ### Flex Containers 188 | 189 | `wasm-ui` has two main flex containers, `Row` and `Column`. 190 | 191 | ```rust 192 | Box::new(Row { 193 | cross_axis_alignment: CrossAxisAlignment::Center, 194 | main_axis_alignment: MainAxisAlignment::SpaceEvenly, 195 | children: vec![ 196 | Flex::Fixed { 197 | child: Box::new(Container{ 198 | size: (100.0, 200.0).into(), 199 | color: Color::red(), 200 | ..Default::default() 201 | }), 202 | }, 203 | Flex::Fixed { 204 | child: Box::new(Container{ 205 | size: (100.0, 100.0).into(), 206 | color: Color::green(), 207 | ..Default::default() 208 | }), 209 | }, 210 | Flex::Fixed { 211 | child: Box::new(Container{ 212 | size: (100.0, 100.0).into(), 213 | color: Color::blue(), 214 | ..Default::default() 215 | }), 216 | }, 217 | ] 218 | }) 219 | ``` 220 | 221 | This will position three rectangles – red, green and blue – horizontally in the 222 | center of the screen. The red rectangle will be twice as tall as the green and blue squares. 223 | 224 | Screen Shot 2021-11-14 at 2 11 00 pm 225 | 226 | If we change the green square to this: 227 | 228 | ```rust 229 | Flex::Flexible { 230 | flex: 1.0, 231 | child: Box::new(Container{ 232 | size: (100.0, 100.0).into(), 233 | color: Color::green(), 234 | ..Default::default() 235 | }), 236 | }, 237 | ``` 238 | 239 | Then it will expand to fill the screen in the horizontal direction, pushing the 240 | red and blue squares to the edges of the screen. 241 | 242 | Screen Shot 2021-11-14 at 2 11 15 pm 243 | 244 | ## The codebase 245 | 246 | `core/` is the Rust implementation. This gets compiled to WASM and async loaded 247 | in the browser. 248 | 249 | `core/crates/platform` implements the platform-specific rendering logic and app drivers. The drivers 250 | sit between the application logic and the deploy target. For instance, the 251 | browser driver translates browser events into something the application can 252 | understand. It also takes the render primitive output of the application and 253 | draws it on the WebGL canvas. 254 | 255 | Currently I've just implemented this for the browser, but I'm trying to keep it 256 | generic. There should be pretty easy to port to any OpenGL target, so I imagine 257 | it wouldn't be too much extra work to support native platforms. 258 | 259 | `core/crates/math` a generic linear algebra math library. Implements things like 260 | vectors and matrices. 261 | 262 | `core/src` is the demo application to demonstrate how the UI libraries could be 263 | used. 264 | 265 | `bin/` holds some helper scripts to build the project and run the dev server. 266 | 267 | `web/` contains the scaffolding for the web target. This is a small React 268 | project that does nothing except async load the WASM libraries and initialises 269 | some event listeners to pass browser events to the browser driver. 270 | 271 | ## Building 272 | 273 | `./bin/build` will build everything and shove it in the `/build` folder. You can 274 | run `./bin/build ` to only build the core or web parts. 275 | 276 | `./bin/start` will start the dev server on `:8080`. This will automatically 277 | detect changes in the web directory, but you'll have to manually rebuild any 278 | Rust changes with `./bin/build core`. 279 | -------------------------------------------------------------------------------- /core/crates/math/src/vector3.rs: -------------------------------------------------------------------------------- 1 | use bytemuck::{Pod, Zeroable}; 2 | use std::default::Default; 3 | use std::ops::{ 4 | Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, 5 | }; 6 | 7 | #[macro_export] 8 | macro_rules! vector3 { 9 | ($x: expr, $y: expr, $z: expr) => { 10 | Vector3 { 11 | x: $x, 12 | y: $y, 13 | z: $z, 14 | } 15 | }; 16 | } 17 | 18 | /// A 2-dimensional vector. 19 | #[repr(C)] 20 | #[derive(PartialEq, Copy, Clone, Debug, Pod, Zeroable)] 21 | pub struct Vector3 { 22 | /// The x component of the vector. 23 | pub x: f32, 24 | /// The y component of the vector. 25 | pub y: f32, 26 | /// The z component of the vector. 27 | pub z: f32, 28 | } 29 | 30 | impl Vector3 { 31 | /// Construct a new vector using the provided components. 32 | #[must_use] 33 | pub fn new(x: f32, y: f32, z: f32) -> Vector3 { 34 | Vector3 { x, y, z } 35 | } 36 | 37 | /// Construct a new vector where all components are 0. 38 | #[must_use] 39 | pub fn zero() -> Vector3 { 40 | Vector3::new(0.0, 0.0, 0.0) 41 | } 42 | 43 | /// Check if the vector is zero. 44 | #[must_use] 45 | pub fn is_zero(self) -> bool { 46 | self == Vector3::zero() 47 | } 48 | 49 | #[must_use] 50 | pub fn up() -> Vector3 { 51 | Vector3::new(0.0, 1.0, 0.0) 52 | } 53 | 54 | #[must_use] 55 | pub fn is_up(self) -> bool { 56 | self == Vector3::up() 57 | } 58 | 59 | #[must_use] 60 | pub fn down() -> Vector3 { 61 | Vector3::new(0.0, -1.0, 0.0) 62 | } 63 | 64 | #[must_use] 65 | pub fn is_down(self) -> bool { 66 | self == Vector3::down() 67 | } 68 | 69 | #[must_use] 70 | pub fn left() -> Vector3 { 71 | Vector3::new(-1.0, 0.0, 0.0) 72 | } 73 | 74 | #[must_use] 75 | pub fn is_left(self) -> bool { 76 | self == Vector3::left() 77 | } 78 | 79 | #[must_use] 80 | pub fn right() -> Vector3 { 81 | Vector3::new(1.0, 0.0, 0.0) 82 | } 83 | 84 | #[must_use] 85 | pub fn is_right(self) -> bool { 86 | self == Vector3::right() 87 | } 88 | 89 | #[must_use] 90 | pub fn forward() -> Vector3 { 91 | Vector3::new(0.0, 0.0, 1.0) 92 | } 93 | 94 | #[must_use] 95 | pub fn is_forward(self) -> bool { 96 | self == Vector3::forward() 97 | } 98 | 99 | #[must_use] 100 | pub fn backward() -> Vector3 { 101 | Vector3::new(0.0, 0.0, -1.0) 102 | } 103 | 104 | #[must_use] 105 | pub fn is_backward(self) -> bool { 106 | self == Vector3::backward() 107 | } 108 | 109 | /// Calculate the sum of the x and y components of the vector. 110 | #[must_use] 111 | pub fn sum(self) -> f32 { 112 | self.x + self.y + self.z 113 | } 114 | 115 | /// Calculate the product of the x and y components of the vector. 116 | #[must_use] 117 | pub fn product(self) -> f32 { 118 | self.x * self.y * self.z 119 | } 120 | 121 | /// Get the dot product of two vectors. 122 | #[must_use] 123 | pub fn dot(lhs: Vector3, rhs: Vector3) -> f32 { 124 | (lhs.x * rhs.x) + (lhs.y * rhs.y) + (lhs.z * rhs.z) 125 | } 126 | 127 | /// Get the magnitude, or length, of the vector. 128 | #[must_use] 129 | pub fn magnitude(self) -> f32 { 130 | (self.x.powi(2) + self.y.powi(2) + self.z.powi(2)).sqrt() 131 | } 132 | 133 | /// Return a vector with a magnitude of 1. 134 | #[must_use] 135 | pub fn normalized(self) -> Vector3 { 136 | let mag = self.magnitude(); 137 | Vector3::new(self.x / mag, self.y / mag, self.z / mag) 138 | } 139 | 140 | /// Get the smallest component of the vector. 141 | #[must_use] 142 | pub fn min(self) -> f32 { 143 | f32::min(f32::min(self.x, self.y), self.z) 144 | } 145 | 146 | /// Get the largest component of the vector. 147 | #[must_use] 148 | pub fn max(self) -> f32 { 149 | f32::max(f32::max(self.x, self.y), self.z) 150 | } 151 | } 152 | 153 | // -------------------------------------------------- 154 | // Vector operations 155 | // -------------------------------------------------- 156 | 157 | impl Default for Vector3 { 158 | /// Get the zero vector. 159 | #[must_use] 160 | fn default() -> Vector3 { 161 | Vector3::zero() 162 | } 163 | } 164 | 165 | impl Neg for Vector3 { 166 | type Output = Vector3; 167 | 168 | /// Flip the sign on all the components in the vector. 169 | #[must_use] 170 | fn neg(self) -> Vector3 { 171 | Vector3::new(-self.x, -self.y, -self.z) 172 | } 173 | } 174 | 175 | impl Add for Vector3 { 176 | type Output = Vector3; 177 | 178 | /// Add two vectors together. The result will have each component be the sum 179 | /// of the original two components. 180 | #[must_use] 181 | fn add(self, rhs: Vector3) -> Vector3 { 182 | Vector3::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z) 183 | } 184 | } 185 | 186 | impl AddAssign for Vector3 { 187 | /// Add the components of another vector. 188 | fn add_assign(&mut self, rhs: Vector3) { 189 | self.x += rhs.x; 190 | self.y += rhs.y; 191 | self.z += rhs.z; 192 | } 193 | } 194 | 195 | impl Sub for Vector3 { 196 | type Output = Vector3; 197 | 198 | /// Subtract two vectors together. The result will have each component be the difference 199 | /// of the original two components. 200 | #[must_use] 201 | fn sub(self, rhs: Vector3) -> Vector3 { 202 | Vector3::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) 203 | } 204 | } 205 | 206 | impl SubAssign for Vector3 { 207 | /// Subtract the components of another vector. 208 | fn sub_assign(&mut self, rhs: Vector3) { 209 | self.x -= rhs.x; 210 | self.y -= rhs.y; 211 | self.z -= rhs.z; 212 | } 213 | } 214 | 215 | impl Mul for Vector3 { 216 | type Output = Vector3; 217 | 218 | /// Multiply two vectors together. The result will have each component be the multiplication 219 | /// of the original two components. 220 | #[must_use] 221 | fn mul(self, rhs: Vector3) -> Vector3 { 222 | Vector3::new(self.x * rhs.x, self.y * rhs.y, self.z * rhs.z) 223 | } 224 | } 225 | 226 | impl MulAssign for Vector3 { 227 | /// Multiply by the components of another vector. 228 | fn mul_assign(&mut self, rhs: Vector3) { 229 | self.x *= rhs.x; 230 | self.y *= rhs.y; 231 | self.z *= rhs.z; 232 | } 233 | } 234 | 235 | impl Div for Vector3 { 236 | type Output = Vector3; 237 | 238 | /// Divide two vectors together. The result will have each component be the division 239 | /// of the original two components. 240 | #[must_use] 241 | fn div(self, rhs: Vector3) -> Vector3 { 242 | Vector3::new(self.x / rhs.x, self.y / rhs.y, self.z / rhs.z) 243 | } 244 | } 245 | 246 | impl DivAssign for Vector3 { 247 | /// Divide by the components of another vector. 248 | fn div_assign(&mut self, rhs: Vector3) { 249 | self.x /= rhs.x; 250 | self.y /= rhs.y; 251 | self.z /= rhs.z; 252 | } 253 | } 254 | 255 | impl Rem for Vector3 { 256 | type Output = Vector3; 257 | 258 | /// Get the elementwise remainder of two vectors. The result will have each component be 259 | /// the remainder of the original two components. 260 | #[must_use] 261 | fn rem(self, rhs: Vector3) -> Vector3 { 262 | Vector3::new(self.x % rhs.x, self.y % rhs.y, self.z % rhs.z) 263 | } 264 | } 265 | 266 | impl RemAssign for Vector3 { 267 | /// Get the elementwise remainder using the components of another vector. 268 | fn rem_assign(&mut self, rhs: Vector3) { 269 | self.x %= rhs.x; 270 | self.y %= rhs.y; 271 | self.z %= rhs.z; 272 | } 273 | } 274 | 275 | // -------------------------------------------------- 276 | // Scalar operations 277 | // -------------------------------------------------- 278 | 279 | impl Add for Vector3 { 280 | type Output = Vector3; 281 | 282 | /// Add a scalar to each component of the vector. 283 | #[must_use] 284 | fn add(self, rhs: f32) -> Vector3 { 285 | Vector3::new(self.x + rhs, self.y + rhs, self.z + rhs) 286 | } 287 | } 288 | 289 | impl AddAssign for Vector3 { 290 | /// Add a scalar to each component of the vector. 291 | fn add_assign(&mut self, rhs: f32) { 292 | self.x += rhs; 293 | self.y += rhs; 294 | self.z += rhs; 295 | } 296 | } 297 | 298 | impl Sub for Vector3 { 299 | type Output = Vector3; 300 | 301 | /// Subtract a scalar from each component of the vector. 302 | #[must_use] 303 | fn sub(self, rhs: f32) -> Vector3 { 304 | Vector3::new(self.x - rhs, self.y - rhs, self.z - rhs) 305 | } 306 | } 307 | 308 | impl SubAssign for Vector3 { 309 | /// Subtract a scalar from each component of the vector. 310 | fn sub_assign(&mut self, rhs: f32) { 311 | self.x -= rhs; 312 | self.y -= rhs; 313 | self.z -= rhs; 314 | } 315 | } 316 | 317 | impl Mul for Vector3 { 318 | type Output = Vector3; 319 | 320 | /// Multiply each component of the vector by a scalar. 321 | #[must_use] 322 | fn mul(self, rhs: f32) -> Vector3 { 323 | Vector3::new(self.x * rhs, self.y * rhs, self.z * rhs) 324 | } 325 | } 326 | 327 | impl MulAssign for Vector3 { 328 | /// Multiply each component of the vector by a scalar. 329 | fn mul_assign(&mut self, rhs: f32) { 330 | self.x *= rhs; 331 | self.y *= rhs; 332 | self.z *= rhs; 333 | } 334 | } 335 | 336 | impl Div for Vector3 { 337 | type Output = Vector3; 338 | 339 | /// Divide each component of the vector by a scalar. 340 | #[must_use] 341 | fn div(self, rhs: f32) -> Vector3 { 342 | Vector3::new(self.x / rhs, self.y / rhs, self.z / rhs) 343 | } 344 | } 345 | 346 | impl DivAssign for Vector3 { 347 | /// Divide each component of the vector by a scalar. 348 | fn div_assign(&mut self, rhs: f32) { 349 | self.x /= rhs; 350 | self.y /= rhs; 351 | self.z /= rhs; 352 | } 353 | } 354 | 355 | impl Rem for Vector3 { 356 | type Output = Vector3; 357 | 358 | /// Get the remainder of each component after dividing by a scalar. 359 | #[must_use] 360 | fn rem(self, rhs: f32) -> Vector3 { 361 | Vector3::new(self.x % rhs, self.y % rhs, self.z % rhs) 362 | } 363 | } 364 | 365 | impl RemAssign for Vector3 { 366 | /// Get the remainder after a component-wise division of a scalar. 367 | fn rem_assign(&mut self, rhs: f32) { 368 | self.x %= rhs; 369 | self.y %= rhs; 370 | self.z %= rhs; 371 | } 372 | } 373 | 374 | // -------------------------------------------------- 375 | // Transform into Vector3 376 | // -------------------------------------------------- 377 | 378 | impl From<[f32; 3]> for Vector3 { 379 | fn from(lhs: [f32; 3]) -> Vector3 { 380 | Vector3::new(lhs[0], lhs[1], lhs[2]) 381 | } 382 | } 383 | 384 | impl From<(f32, f32, f32)> for Vector3 { 385 | fn from(lhs: (f32, f32, f32)) -> Vector3 { 386 | let (x, y, z) = lhs; 387 | Vector3::new(x, y, z) 388 | } 389 | } 390 | 391 | // -------------------------------------------------- 392 | // Transform from Vector3 393 | // -------------------------------------------------- 394 | 395 | impl From for [f32; 3] { 396 | fn from(lhs: Vector3) -> [f32; 3] { 397 | [lhs.x, lhs.y, lhs.z] 398 | } 399 | } 400 | 401 | impl From for (f32, f32, f32) { 402 | fn from(lhs: Vector3) -> (f32, f32, f32) { 403 | (lhs.x, lhs.y, lhs.z) 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /core/crates/layout/src/tree.rs: -------------------------------------------------------------------------------- 1 | use crate::base::EdgeInsets; 2 | use crate::decoration::Material; 3 | use math::{Rect, Vector2}; 4 | use std::collections::VecDeque; 5 | use std::fmt::Debug; 6 | 7 | /// This is the essential trait of the box model. It is implemented by all 8 | /// components that undergo the box layout process. 9 | /// 10 | /// The `layout` method is called repeatedly to generate a `LayoutTree`. Each 11 | /// tree node is responsible for three things: 12 | /// 13 | /// 1. Calculating the position of it's children 14 | /// 2. Inserting it's children into the `LayoutTree` 15 | /// 3. Calculating and returning it's own size to it's parent node 16 | /// 17 | /// This allows the `LayoutTree` to be generated in one walk down and up the 18 | /// tree. It's how we can perform layout in O(2n) time. 19 | /// 20 | /// This process takes heavy inspiration from the [Flutter render 21 | /// pipeline](https://www.youtube.com/watch?v=UUfXWzp0-DU) and the CSS box 22 | /// model. 23 | pub trait Layout: Debug { 24 | fn layout(&self, tree: &mut LayoutTree, constraints: &BoxConstraints) -> SizedLayoutBox; 25 | } 26 | 27 | // The minimum and maximum dimensions that a [SizedLayoutBox] or a [LayoutBox] 28 | // can be. 29 | #[derive(PartialEq, Clone, Debug)] 30 | pub struct BoxConstraints { 31 | pub min: Vector2, 32 | pub max: Vector2, 33 | } 34 | 35 | impl BoxConstraints { 36 | #[must_use] 37 | pub fn from_max>(max: I) -> BoxConstraints { 38 | BoxConstraints { 39 | min: Vector2::zero(), 40 | max: max.into(), 41 | } 42 | } 43 | 44 | #[must_use] 45 | pub fn horizontal(&self) -> Vector2 { 46 | Vector2::new(self.min.x, self.max.x) 47 | } 48 | 49 | #[must_use] 50 | pub fn vertical(&self) -> Vector2 { 51 | Vector2::new(self.min.y, self.max.y) 52 | } 53 | 54 | #[must_use] 55 | pub fn has_unbounded_height(&self) -> bool { 56 | self.max.y != f32::INFINITY 57 | } 58 | 59 | #[must_use] 60 | pub fn has_unbounded_width(&self) -> bool { 61 | self.max.x != f32::INFINITY 62 | } 63 | } 64 | 65 | /// Used to get a `LayoutBox` from a `LayoutTree`. 66 | /// 67 | /// This is required because `LayoutTree` is implemented using a memory arena in 68 | /// order to play nice with the borrow-checker. It's easier to pass around a 69 | /// copyable value like `usize` than worry about balancing reference lifetimes 70 | /// and shared ownership, and it's more efficient than copying `LayoutBox`. 71 | pub type LayoutBoxId = usize; 72 | 73 | /// An element that has calculated it's own size, but has not been positioned 74 | /// by it's parent yet. This is the intermediate step during layout. 75 | #[derive(PartialEq, Clone, Default, Debug)] 76 | pub struct SizedLayoutBox { 77 | pub margin: EdgeInsets, 78 | pub size: Vector2, 79 | pub children: Vec, 80 | pub material: Option, 81 | } 82 | 83 | /// An element that has finished layout. It has been been sized and positioned. 84 | #[derive(PartialEq, Clone, Default, Debug)] 85 | pub struct LayoutBox { 86 | pub bounds: Rect, // Includes margins 87 | pub margin: EdgeInsets, 88 | pub children: Vec, 89 | pub material: Option, 90 | } 91 | 92 | impl Eq for LayoutBox {} 93 | 94 | impl LayoutBox { 95 | /// Convenience method to turn a `SizedLayoutBox` into a `LayoutBox`. This 96 | /// is handy when implementing the [Layout] trait. 97 | pub fn from_child(child: SizedLayoutBox, pos: I) -> LayoutBox 98 | where 99 | I: Into, 100 | { 101 | let min = pos.into(); 102 | let max = min + child.size; 103 | LayoutBox { 104 | bounds: Rect::new(min, max), 105 | margin: child.margin, 106 | children: child.children, 107 | material: child.material, 108 | } 109 | } 110 | } 111 | 112 | /// A tree of `LayoutBox` elements. The position of each `LayoutBox` is relative 113 | /// to it's parent. 114 | /// 115 | /// This is the data structure that is consumed by the render driver to show on 116 | /// the screen. It is intended to be generic across different deploy targets. 117 | /// 118 | /// The tree is implemented as a memory arena to be indexed into using a 119 | /// `LayoutBoxId`. This makes it much easier to use with the borrow checker. 120 | #[allow(clippy::module_name_repetitions)] 121 | #[derive(Clone, Default, Debug)] 122 | pub struct LayoutTree { 123 | pub root: Option, 124 | pub boxes: Vec, 125 | } 126 | 127 | impl LayoutTree { 128 | /// Create a new empty `LayoutTree`. 129 | #[must_use] 130 | pub fn new() -> LayoutTree { 131 | LayoutTree { 132 | root: None, 133 | boxes: Vec::new(), 134 | } 135 | } 136 | 137 | /// Set the root of the tree. This assumes that the `LayoutBoxId` provided 138 | /// by the caller points to a valid `LayoutBox`. 139 | pub fn set_root(&mut self, root: Option) { 140 | self.root = root; 141 | } 142 | 143 | /// Insert a `LayoutBox` into the tree and get a `LayoutBoxId` to fetch it 144 | /// again later. 145 | pub fn insert(&mut self, lbox: LayoutBox) -> LayoutBoxId { 146 | self.boxes.push(lbox); 147 | self.boxes.len() - 1 148 | } 149 | 150 | /// Get a reference to the `LayoutBox` indexed by a `LayoutBoxId`. 151 | #[must_use] 152 | pub fn get(&self, id: LayoutBoxId) -> Option<&LayoutBox> { 153 | self.boxes.get(id) 154 | } 155 | 156 | /// Get an iterator over a breadth-first search 157 | #[must_use] 158 | pub fn iter(&self) -> LayoutTreeIterator { 159 | LayoutTreeIterator { 160 | tree: self, 161 | parents: match self.root { 162 | Some(root) => VecDeque::from([root]), 163 | None => VecDeque::new(), 164 | }, 165 | offsets: match self.root { 166 | Some(_) => VecDeque::from([Vector2::zero()]), 167 | None => VecDeque::new(), 168 | }, 169 | remaining: match self.root { 170 | Some(root) => VecDeque::from([root]), 171 | None => VecDeque::new(), 172 | }, 173 | } 174 | } 175 | } 176 | 177 | pub struct LayoutTreeIterator<'a> { 178 | tree: &'a LayoutTree, 179 | parents: VecDeque, 180 | offsets: VecDeque, 181 | remaining: VecDeque, 182 | } 183 | 184 | impl<'a> Iterator for LayoutTreeIterator<'a> { 185 | type Item = (&'a LayoutBox, &'a LayoutBox, Vector2); 186 | 187 | fn next(&mut self) -> Option<(&'a LayoutBox, &'a LayoutBox, Vector2)> { 188 | let parent_id = self.parents.pop_front()?; 189 | let child_id = self.remaining.pop_front()?; 190 | let parent_offset = self.offsets.pop_front()?; 191 | let parent = self.tree.get(parent_id)?; 192 | let child = self.tree.get(child_id)?; 193 | let offset = child.bounds.min + parent_offset; 194 | for child in child.children.iter().rev() { 195 | self.parents.push_front(child_id); 196 | self.remaining.push_front(*child); 197 | self.offsets.push_front(offset); 198 | } 199 | Some((parent, child, parent_offset)) 200 | } 201 | } 202 | 203 | #[cfg(test)] 204 | mod tests { 205 | use super::*; 206 | use crate::decoration::{Color, Material}; 207 | 208 | #[test] 209 | fn layout_tree_iter_nested() { 210 | let mut tree = LayoutTree::new(); 211 | let a = LayoutBox { 212 | bounds: Rect::new((0.0, 0.0).into(), (0.0, 0.0).into()), 213 | children: vec![], 214 | material: None, 215 | ..Default::default() 216 | }; 217 | let a_id = tree.insert(a.clone()); 218 | let b = LayoutBox { 219 | bounds: Rect::new((1.0, 1.0).into(), (1.0, 1.0).into()), 220 | children: vec![a_id], 221 | material: None, 222 | ..Default::default() 223 | }; 224 | let b_id = tree.insert(b.clone()); 225 | let c = LayoutBox { 226 | bounds: Rect::new((2.0, 2.0).into(), (2.0, 2.0).into()), 227 | children: vec![b_id], 228 | material: None, 229 | ..Default::default() 230 | }; 231 | let c_id = tree.insert(c.clone()); 232 | let root = LayoutBox { 233 | bounds: Rect::new((3.0, 3.0).into(), (3.0, 3.0).into()), 234 | children: vec![c_id], 235 | material: None, 236 | ..Default::default() 237 | }; 238 | let root_id = tree.insert(root.clone()); 239 | tree.set_root(Some(root_id)); 240 | 241 | let mut actual = vec![]; 242 | for item in tree.iter() { 243 | actual.push(item); 244 | } 245 | 246 | let expected = vec![ 247 | (&root, &root, Vector2::new(0.0, 0.0)), 248 | (&root, &c, Vector2::new(3.0, 3.0)), 249 | (&c, &b, Vector2::new(5.0, 5.0)), 250 | (&b, &a, Vector2::new(6.0, 6.0)), 251 | ]; 252 | for (i, _) in actual.iter().enumerate() { 253 | assert_eq!(expected[i], actual[i]); 254 | } 255 | } 256 | 257 | #[test] 258 | fn layout_tree_iter_works() { 259 | let mut tree = LayoutTree::new(); 260 | let a_child = LayoutBox { 261 | bounds: Rect::new((4.0, 4.0).into(), (2.0, 2.0).into()), 262 | children: vec![], 263 | material: None, 264 | ..Default::default() 265 | }; 266 | let a_child_id = tree.insert(a_child.clone()); 267 | let a = LayoutBox { 268 | bounds: Rect::new((1.0, 1.0).into(), (2.0, 2.0).into()), 269 | children: vec![a_child_id], 270 | material: None, 271 | ..Default::default() 272 | }; 273 | let a_id = tree.insert(a.clone()); 274 | 275 | let b_child = LayoutBox { 276 | bounds: Rect::new((5.0, 5.0).into(), (2.0, 2.0).into()), 277 | children: vec![], 278 | material: None, 279 | ..Default::default() 280 | }; 281 | let b_child_id = tree.insert(b_child.clone()); 282 | let b = LayoutBox { 283 | bounds: Rect::new((2.0, 2.0).into(), (2.0, 2.0).into()), 284 | children: vec![b_child_id], 285 | material: None, 286 | ..Default::default() 287 | }; 288 | let b_id = tree.insert(b.clone()); 289 | let c = LayoutBox { 290 | bounds: Rect::new((3.0, 3.0).into(), (3.0, 3.0).into()), 291 | children: vec![a_id, b_id], 292 | material: None, 293 | ..Default::default() 294 | }; 295 | let c_id = tree.insert(c.clone()); 296 | tree.set_root(Some(c_id)); 297 | 298 | let mut actual = vec![]; 299 | for item in tree.iter() { 300 | actual.push(item); 301 | } 302 | 303 | let expected = vec![ 304 | (&c, &c, Vector2::new(0.0, 0.0)), 305 | (&c, &a, Vector2::new(3.0, 3.0)), 306 | (&a, &a_child, Vector2::new(4.0, 4.0)), 307 | (&c, &b, Vector2::new(3.0, 3.0)), 308 | (&b, &b_child, Vector2::new(5.0, 5.0)), 309 | ]; 310 | for (i, _) in actual.iter().enumerate() { 311 | assert_eq!(expected[i], actual[i]); 312 | } 313 | } 314 | 315 | #[test] 316 | fn lbox_partial_eq_with_different_materials_returns_false() { 317 | let lbox_a = LayoutBox { 318 | material: Some(Material::filled(Color::red())), 319 | ..a_layout_box() 320 | }; 321 | let lbox_b = LayoutBox { 322 | material: Some(Material::filled(Color::green())), 323 | ..a_layout_box() 324 | }; 325 | assert_ne!(lbox_a, lbox_b); 326 | } 327 | 328 | fn a_layout_box() -> LayoutBox { 329 | LayoutBox { 330 | bounds: Rect::from_size((10.0, 10.0)), 331 | children: vec![], 332 | material: Some(Material::filled(Color::transparent())), 333 | ..Default::default() 334 | } 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /core/crates/test_util/target/rls/debug/deps/save-analysis/libtest_util-ca7e05eeee4dcd01.json: -------------------------------------------------------------------------------- 1 | {"config":{"output_file":null,"full_docs":false,"pub_only":false,"reachable_only":false,"distro_crate":false,"signatures":false,"borrow_data":false},"version":"0.19.1","compilation":{"directory":"/Users/harrisonturton/Documents/dev/wasm-ui/core","program":"/Users/harrisonturton/.rustup/toolchains/nightly-x86_64-apple-darwin/bin/rls","arguments":[],"output":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/target/rls/debug/deps/libtest_util-ca7e05eeee4dcd01.rmeta"},"prelude":{"crate_id":{"name":"test_util","disambiguator":[942514808194662887,0]},"crate_root":"crates/test_util/src","external_crates":[{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":1,"id":{"name":"std","disambiguator":[8121778101813075280,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":2,"id":{"name":"core","disambiguator":[17620493357780722015,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":3,"id":{"name":"compiler_builtins","disambiguator":[18161881043014032950,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":4,"id":{"name":"rustc_std_workspace_core","disambiguator":[16830480400437399750,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":5,"id":{"name":"alloc","disambiguator":[7722068402736000666,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":6,"id":{"name":"libc","disambiguator":[1004281680224563355,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":7,"id":{"name":"unwind","disambiguator":[18155916040418436021,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":8,"id":{"name":"cfg_if","disambiguator":[9117696045549818189,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":9,"id":{"name":"hashbrown","disambiguator":[334768894470426301,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":10,"id":{"name":"rustc_std_workspace_alloc","disambiguator":[7725474617005895420,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":11,"id":{"name":"rustc_demangle","disambiguator":[17357724652728987651,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":12,"id":{"name":"std_detect","disambiguator":[9382970206070903742,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":13,"id":{"name":"addr2line","disambiguator":[2993262292404423595,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":14,"id":{"name":"gimli","disambiguator":[10596867704632839566,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":15,"id":{"name":"object","disambiguator":[16130277864861404901,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":16,"id":{"name":"memchr","disambiguator":[790493182071655123,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":17,"id":{"name":"panic_unwind","disambiguator":[4301512845898432394,0]}}],"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":0,"byte_end":647,"line_start":1,"line_end":23,"column_start":1,"column_end":2}},"imports":[{"kind":"Use","ref_id":{"krate":2,"index":9297},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":14,"byte_end":19,"line_start":1,"line_end":1,"column_start":15,"column_end":20},"alias_span":null,"name":"Debug","value":"","parent":{"krate":0,"index":0}},{"kind":"Use","ref_id":{"krate":2,"index":9300},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":14,"byte_end":19,"line_start":1,"line_end":1,"column_start":15,"column_end":20},"alias_span":null,"name":"Debug","value":"","parent":{"krate":0,"index":0}}],"defs":[{"kind":"Mod","id":{"krate":0,"index":0},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":0,"byte_end":647,"line_start":1,"line_end":23,"column_start":1,"column_end":2},"name":"","qualname":"::","value":"crates/test_util/src/lib.rs","parent":null,"children":[{"krate":0,"index":1},{"krate":0,"index":2},{"krate":0,"index":3},{"krate":0,"index":4},{"krate":0,"index":6}],"decl_id":null,"docs":"","sig":null,"attributes":[]},{"kind":"Local","id":{"krate":0,"index":1073741830},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":130,"byte_end":138,"line_start":6,"line_end":6,"column_start":46,"column_end":54},"name":"expected","qualname":"::assert_slice_eq::expected","value":"&[T]","parent":null,"children":[],"decl_id":null,"docs":"","sig":null,"attributes":[]},{"kind":"Local","id":{"krate":0,"index":536870918},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":146,"byte_end":152,"line_start":6,"line_end":6,"column_start":62,"column_end":68},"name":"actual","qualname":"::assert_slice_eq::actual","value":"&[T]","parent":null,"children":[],"decl_id":null,"docs":"","sig":null,"attributes":[]},{"kind":"Type","id":{"krate":0,"index":7},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":108,"byte_end":109,"line_start":6,"line_end":6,"column_start":24,"column_end":25},"name":"T","qualname":"::assert_slice_eq::T$HirId { owner: DefId(0:6 ~ test_util[d147]::assert_slice_eq), local_id: 0 }","value":"","parent":null,"children":[],"decl_id":null,"docs":"","sig":null,"attributes":[]},{"kind":"Function","id":{"krate":0,"index":6},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":92,"byte_end":107,"line_start":6,"line_end":6,"column_start":8,"column_end":23},"name":"assert_slice_eq","qualname":"::assert_slice_eq","value":"pub fn assert_slice_eq(&[T], &[T])","parent":null,"children":[],"decl_id":null,"docs":" # Panics","sig":null,"attributes":[{"value":"/ # Panics","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":22,"byte_end":34,"line_start":3,"line_end":3,"column_start":1,"column_end":13}},{"value":"/","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":35,"byte_end":38,"line_start":4,"line_end":4,"column_start":1,"column_end":4}},{"value":"/ Will panic when the slices are different.","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":39,"byte_end":84,"line_start":5,"line_end":5,"column_start":1,"column_end":46}}]},{"kind":"Local","id":{"krate":0,"index":3992977414},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":342,"byte_end":343,"line_start":13,"line_end":13,"column_start":10,"column_end":11},"name":"i","qualname":"i$HirId { owner: DefId(0:6 ~ test_util[d147]::assert_slice_eq), local_id: 119 }","value":"usize","parent":null,"children":[],"decl_id":null,"docs":"","sig":null,"attributes":[]},{"kind":"Local","id":{"krate":0,"index":503316486},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":345,"byte_end":353,"line_start":13,"line_end":13,"column_start":13,"column_end":21},"name":"expected","qualname":"expected$HirId { owner: DefId(0:6 ~ test_util[d147]::assert_slice_eq), local_id: 120 }","value":"&T","parent":null,"children":[],"decl_id":null,"docs":"","sig":null,"attributes":[]},{"kind":"Local","id":{"krate":0,"index":553648134},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":400,"byte_end":406,"line_start":14,"line_end":14,"column_start":13,"column_end":19},"name":"actual","qualname":"actual$HirId { owner: DefId(0:6 ~ test_util[d147]::assert_slice_eq), local_id: 132 }","value":"&T","parent":null,"children":[],"decl_id":null,"docs":"","sig":null,"attributes":[]}],"impls":[],"refs":[{"kind":"Mod","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":4,"byte_end":7,"line_start":1,"line_end":1,"column_start":5,"column_end":8},"ref_id":{"krate":1,"index":0}},{"kind":"Mod","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":9,"byte_end":12,"line_start":1,"line_end":1,"column_start":10,"column_end":13},"ref_id":{"krate":5,"index":4294}},{"kind":"Type","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":111,"byte_end":120,"line_start":6,"line_end":6,"column_start":27,"column_end":36},"ref_id":{"krate":2,"index":2640}},{"kind":"Type","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":123,"byte_end":128,"line_start":6,"line_end":6,"column_start":39,"column_end":44},"ref_id":{"krate":2,"index":9297}},{"kind":"Type","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":142,"byte_end":143,"line_start":6,"line_end":6,"column_start":58,"column_end":59},"ref_id":{"krate":0,"index":7}},{"kind":"Type","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":156,"byte_end":157,"line_start":6,"line_end":6,"column_start":72,"column_end":73},"ref_id":{"krate":0,"index":7}},{"kind":"Function","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":192,"byte_end":195,"line_start":8,"line_end":8,"column_start":18,"column_end":21},"ref_id":{"krate":2,"index":11160}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":183,"byte_end":191,"line_start":8,"line_end":8,"column_start":9,"column_end":17},"ref_id":{"krate":0,"index":1073741830}},{"kind":"Function","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":208,"byte_end":211,"line_start":8,"line_end":8,"column_start":34,"column_end":37},"ref_id":{"krate":2,"index":11160}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":201,"byte_end":207,"line_start":8,"line_end":8,"column_start":27,"column_end":33},"ref_id":{"krate":0,"index":536870918}},{"kind":"Function","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":297,"byte_end":300,"line_start":10,"line_end":10,"column_start":18,"column_end":21},"ref_id":{"krate":2,"index":11160}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":288,"byte_end":296,"line_start":10,"line_end":10,"column_start":9,"column_end":17},"ref_id":{"krate":0,"index":1073741830}},{"kind":"Function","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":319,"byte_end":322,"line_start":11,"line_end":11,"column_start":16,"column_end":19},"ref_id":{"krate":2,"index":11160}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":312,"byte_end":318,"line_start":11,"line_end":11,"column_start":9,"column_end":15},"ref_id":{"krate":0,"index":536870918}},{"kind":"Function","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":367,"byte_end":371,"line_start":13,"line_end":13,"column_start":35,"column_end":39},"ref_id":{"krate":2,"index":11184}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":358,"byte_end":366,"line_start":13,"line_end":13,"column_start":26,"column_end":34},"ref_id":{"krate":0,"index":1073741830}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":341,"byte_end":354,"line_start":13,"line_end":13,"column_start":9,"column_end":22},"ref_id":{"krate":2,"index":43192}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":410,"byte_end":416,"line_start":14,"line_end":14,"column_start":23,"column_end":29},"ref_id":{"krate":0,"index":536870918}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":417,"byte_end":418,"line_start":14,"line_end":14,"column_start":30,"column_end":31},"ref_id":{"krate":0,"index":3992977414}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":451,"byte_end":459,"line_start":16,"line_end":16,"column_start":14,"column_end":22},"ref_id":{"krate":0,"index":503316486}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":464,"byte_end":470,"line_start":16,"line_end":16,"column_start":27,"column_end":33},"ref_id":{"krate":0,"index":553648134}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":584,"byte_end":585,"line_start":18,"line_end":18,"column_start":13,"column_end":14},"ref_id":{"krate":0,"index":3992977414}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":599,"byte_end":607,"line_start":19,"line_end":19,"column_start":13,"column_end":21},"ref_id":{"krate":0,"index":503316486}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":621,"byte_end":627,"line_start":20,"line_end":20,"column_start":13,"column_end":19},"ref_id":{"krate":0,"index":553648134}}],"macro_refs":[],"relations":[]} -------------------------------------------------------------------------------- /core/crates/math/src/vector4.rs: -------------------------------------------------------------------------------- 1 | use bytemuck::{Pod, Zeroable}; 2 | use std::default::Default; 3 | use std::ops::{ 4 | Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, 5 | }; 6 | 7 | #[macro_export] 8 | macro_rules! vector4 { 9 | ($x: expr, $y: expr, $z: expr, $w: expr) => { 10 | Vector4 { 11 | x: $x, 12 | y: $y, 13 | z: $z, 14 | w: $w, 15 | } 16 | }; 17 | } 18 | 19 | /// A 2-dimensional vector. 20 | #[repr(C)] 21 | #[derive(PartialEq, Copy, Clone, Debug, Pod, Zeroable)] 22 | pub struct Vector4 { 23 | /// The x component of the vector. 24 | pub x: f32, 25 | /// The y component of the vector. 26 | pub y: f32, 27 | /// The z component of the vector. 28 | pub z: f32, 29 | /// The w component of the vector. 30 | pub w: f32, 31 | } 32 | 33 | impl Vector4 { 34 | /// Construct a new vector using the provided components. 35 | #[must_use] 36 | pub fn new(x: f32, y: f32, z: f32, w: f32) -> Vector4 { 37 | Vector4 { x, y, z, w } 38 | } 39 | 40 | /// Construct a new vector where all components are 0. 41 | #[must_use] 42 | pub fn zero() -> Vector4 { 43 | Vector4::new(0.0, 0.0, 0.0, 0.0) 44 | } 45 | 46 | /// Check if the vector is zero. 47 | #[must_use] 48 | pub fn is_zero(self) -> bool { 49 | self == Vector4::zero() 50 | } 51 | 52 | #[must_use] 53 | pub fn up() -> Vector4 { 54 | Vector4::new(0.0, 1.0, 0.0, 0.0) 55 | } 56 | 57 | #[must_use] 58 | pub fn is_up(self) -> bool { 59 | self == Vector4::up() 60 | } 61 | 62 | #[must_use] 63 | pub fn down() -> Vector4 { 64 | Vector4::new(0.0, -1.0, 0.0, 0.0) 65 | } 66 | 67 | #[must_use] 68 | pub fn is_down(self) -> bool { 69 | self == Vector4::down() 70 | } 71 | 72 | #[must_use] 73 | pub fn left() -> Vector4 { 74 | Vector4::new(-1.0, 0.0, 0.0, 0.0) 75 | } 76 | 77 | #[must_use] 78 | pub fn is_left(self) -> bool { 79 | self == Vector4::left() 80 | } 81 | 82 | #[must_use] 83 | pub fn right() -> Vector4 { 84 | Vector4::new(1.0, 0.0, 0.0, 0.0) 85 | } 86 | 87 | #[must_use] 88 | pub fn is_right(self) -> bool { 89 | self == Vector4::right() 90 | } 91 | 92 | #[must_use] 93 | pub fn forward() -> Vector4 { 94 | Vector4::new(0.0, 0.0, 1.0, 0.0) 95 | } 96 | 97 | #[must_use] 98 | pub fn is_forward(self) -> bool { 99 | self == Vector4::forward() 100 | } 101 | 102 | #[must_use] 103 | pub fn backward() -> Vector4 { 104 | Vector4::new(0.0, 0.0, -1.0, 0.0) 105 | } 106 | 107 | #[must_use] 108 | pub fn is_backward(self) -> bool { 109 | self == Vector4::backward() 110 | } 111 | 112 | /// Calculate the sum of the x and y components of the vector. 113 | #[must_use] 114 | pub fn sum(self) -> f32 { 115 | self.x + self.y + self.z + self.w 116 | } 117 | 118 | /// Calculate the product of the x and y components of the vector. 119 | #[must_use] 120 | pub fn product(self) -> f32 { 121 | self.x * self.y * self.z * self.w 122 | } 123 | 124 | /// Get the dot product of two vectors. 125 | #[must_use] 126 | pub fn dot(lhs: Vector4, rhs: Vector4) -> f32 { 127 | (lhs.x * rhs.x) + (lhs.y * rhs.y) + (lhs.z * rhs.z) + (lhs.w * rhs.w) 128 | } 129 | 130 | /// Get the magnitude, or length, of the vector. 131 | #[must_use] 132 | pub fn magnitude(self) -> f32 { 133 | (self.x.powi(2) + self.y.powi(2) + self.z.powi(2) + self.w.powi(2)).sqrt() 134 | } 135 | 136 | /// Return a vector with a magnitude of 1. 137 | #[must_use] 138 | pub fn normalized(self) -> Vector4 { 139 | let mag = self.magnitude(); 140 | Vector4::new(self.x / mag, self.y / mag, self.z / mag, self.w / mag) 141 | } 142 | 143 | /// Get the smallest component of the vector. 144 | #[must_use] 145 | pub fn min(self) -> f32 { 146 | f32::min(f32::min(f32::min(self.x, self.y), self.z), self.w) 147 | } 148 | 149 | /// Get the largest component of the vector. 150 | #[must_use] 151 | pub fn max(self) -> f32 { 152 | f32::max(f32::max(f32::max(self.x, self.y), self.z), self.w) 153 | } 154 | } 155 | 156 | // -------------------------------------------------- 157 | // Vector operations 158 | // -------------------------------------------------- 159 | 160 | impl Default for Vector4 { 161 | /// Get the zero vector. 162 | #[must_use] 163 | fn default() -> Vector4 { 164 | Vector4::zero() 165 | } 166 | } 167 | 168 | impl Neg for Vector4 { 169 | type Output = Vector4; 170 | 171 | /// Flip the sign on all the components in the vector. 172 | #[must_use] 173 | fn neg(self) -> Vector4 { 174 | Vector4::new(-self.x, -self.y, -self.z, -self.w) 175 | } 176 | } 177 | 178 | impl Add for Vector4 { 179 | type Output = Vector4; 180 | 181 | /// Add two vectors together. The result will have each component be the sum 182 | /// of the original components. 183 | #[must_use] 184 | fn add(self, rhs: Vector4) -> Vector4 { 185 | Vector4::new( 186 | self.x + rhs.x, 187 | self.y + rhs.y, 188 | self.z + rhs.z, 189 | self.w + rhs.w, 190 | ) 191 | } 192 | } 193 | 194 | impl AddAssign for Vector4 { 195 | /// Add the components of another vector. 196 | fn add_assign(&mut self, rhs: Vector4) { 197 | self.x += rhs.x; 198 | self.y += rhs.y; 199 | self.z += rhs.z; 200 | self.w += self.w; 201 | } 202 | } 203 | 204 | impl Sub for Vector4 { 205 | type Output = Vector4; 206 | 207 | /// Subtract two vectors together. The result will have each component be the difference 208 | /// of the original components. 209 | #[must_use] 210 | fn sub(self, rhs: Vector4) -> Vector4 { 211 | Vector4::new( 212 | self.x - rhs.x, 213 | self.y - rhs.y, 214 | self.z - rhs.z, 215 | self.w - rhs.w, 216 | ) 217 | } 218 | } 219 | 220 | impl SubAssign for Vector4 { 221 | /// Subtract the components of another vector. 222 | fn sub_assign(&mut self, rhs: Vector4) { 223 | self.x -= rhs.x; 224 | self.y -= rhs.y; 225 | self.z -= rhs.z; 226 | self.w -= rhs.w; 227 | } 228 | } 229 | 230 | impl Mul for Vector4 { 231 | type Output = Vector4; 232 | 233 | /// Multiply two vectors together. The result will have each component be the multiplication 234 | /// of the original two components. 235 | #[must_use] 236 | fn mul(self, rhs: Vector4) -> Vector4 { 237 | Vector4::new( 238 | self.x * rhs.x, 239 | self.y * rhs.y, 240 | self.z * rhs.z, 241 | self.w * rhs.w, 242 | ) 243 | } 244 | } 245 | 246 | impl MulAssign for Vector4 { 247 | /// Multiply by the components of another vector. 248 | fn mul_assign(&mut self, rhs: Vector4) { 249 | self.x *= rhs.x; 250 | self.y *= rhs.y; 251 | self.z *= rhs.z; 252 | self.w *= rhs.w; 253 | } 254 | } 255 | 256 | impl Div for Vector4 { 257 | type Output = Vector4; 258 | 259 | /// Divide two vectors together. The result will have each component be the division 260 | /// of the original components. 261 | #[must_use] 262 | fn div(self, rhs: Vector4) -> Vector4 { 263 | Vector4::new( 264 | self.x / rhs.x, 265 | self.y / rhs.y, 266 | self.z / rhs.z, 267 | self.w / rhs.w, 268 | ) 269 | } 270 | } 271 | 272 | impl DivAssign for Vector4 { 273 | /// Divide by the components of another vector. 274 | fn div_assign(&mut self, rhs: Vector4) { 275 | self.x /= rhs.x; 276 | self.y /= rhs.y; 277 | self.z /= rhs.z; 278 | self.w /= rhs.w; 279 | } 280 | } 281 | 282 | impl Rem for Vector4 { 283 | type Output = Vector4; 284 | 285 | /// Get the elementwise remainder of two vectors. The result will have each component be 286 | /// the remainder of the original two components. 287 | #[must_use] 288 | fn rem(self, rhs: Vector4) -> Vector4 { 289 | Vector4::new( 290 | self.x % rhs.x, 291 | self.y % rhs.y, 292 | self.z % rhs.z, 293 | self.w % rhs.w, 294 | ) 295 | } 296 | } 297 | 298 | impl RemAssign for Vector4 { 299 | /// Get the elementwise remainder using the components of another vector. 300 | fn rem_assign(&mut self, rhs: Vector4) { 301 | self.x %= rhs.x; 302 | self.y %= rhs.y; 303 | self.z %= rhs.z; 304 | self.w %= rhs.w; 305 | } 306 | } 307 | 308 | // -------------------------------------------------- 309 | // Scalar operations 310 | // -------------------------------------------------- 311 | 312 | impl Add for Vector4 { 313 | type Output = Vector4; 314 | 315 | /// Add a scalar to each component of the vector. 316 | #[must_use] 317 | fn add(self, rhs: f32) -> Vector4 { 318 | Vector4::new(self.x + rhs, self.y + rhs, self.z + rhs, self.w + rhs) 319 | } 320 | } 321 | 322 | impl AddAssign for Vector4 { 323 | /// Add a scalar to each component of the vector. 324 | fn add_assign(&mut self, rhs: f32) { 325 | self.x += rhs; 326 | self.y += rhs; 327 | self.z += rhs; 328 | self.w += rhs; 329 | } 330 | } 331 | 332 | impl Sub for Vector4 { 333 | type Output = Vector4; 334 | 335 | /// Subtract a scalar from each component of the vector. 336 | #[must_use] 337 | fn sub(self, rhs: f32) -> Vector4 { 338 | Vector4::new(self.x - rhs, self.y - rhs, self.z - rhs, self.w - rhs) 339 | } 340 | } 341 | 342 | impl SubAssign for Vector4 { 343 | /// Subtract a scalar from each component of the vector. 344 | fn sub_assign(&mut self, rhs: f32) { 345 | self.x -= rhs; 346 | self.y -= rhs; 347 | self.z -= rhs; 348 | self.w -= rhs; 349 | } 350 | } 351 | 352 | impl Mul for Vector4 { 353 | type Output = Vector4; 354 | 355 | /// Multiply each component of the vector by a scalar. 356 | #[must_use] 357 | fn mul(self, rhs: f32) -> Vector4 { 358 | Vector4::new(self.x * rhs, self.y * rhs, self.z * rhs, self.w * rhs) 359 | } 360 | } 361 | 362 | impl MulAssign for Vector4 { 363 | /// Multiply each component of the vector by a scalar. 364 | fn mul_assign(&mut self, rhs: f32) { 365 | self.x *= rhs; 366 | self.y *= rhs; 367 | self.z *= rhs; 368 | self.w *= rhs; 369 | } 370 | } 371 | 372 | impl Div for Vector4 { 373 | type Output = Vector4; 374 | 375 | /// Divide each component of the vector by a scalar. 376 | #[must_use] 377 | fn div(self, rhs: f32) -> Vector4 { 378 | Vector4::new(self.x / rhs, self.y / rhs, self.z / rhs, self.w / rhs) 379 | } 380 | } 381 | 382 | impl DivAssign for Vector4 { 383 | /// Divide each component of the vector by a scalar. 384 | fn div_assign(&mut self, rhs: f32) { 385 | self.x /= rhs; 386 | self.y /= rhs; 387 | self.z /= rhs; 388 | self.w /= rhs; 389 | } 390 | } 391 | 392 | impl Rem for Vector4 { 393 | type Output = Vector4; 394 | 395 | /// Get the remainder of each component after dividing by a scalar. 396 | fn rem(self, rhs: f32) -> Vector4 { 397 | Vector4::new(self.x % rhs, self.y % rhs, self.z % rhs, self.w % rhs) 398 | } 399 | } 400 | 401 | impl RemAssign for Vector4 { 402 | /// Get the remainder after a component-wise division of a scalar. 403 | fn rem_assign(&mut self, rhs: f32) { 404 | self.x %= rhs; 405 | self.y %= rhs; 406 | self.z %= rhs; 407 | self.w %= rhs; 408 | } 409 | } 410 | 411 | // -------------------------------------------------- 412 | // Transform into Vector4 413 | // -------------------------------------------------- 414 | 415 | impl From<[f32; 4]> for Vector4 { 416 | fn from(lhs: [f32; 4]) -> Vector4 { 417 | Vector4::new(lhs[0], lhs[1], lhs[2], lhs[3]) 418 | } 419 | } 420 | 421 | impl From<(f32, f32, f32, f32)> for Vector4 { 422 | fn from(lhs: (f32, f32, f32, f32)) -> Vector4 { 423 | let (x, y, z, w) = lhs; 424 | Vector4::new(x, y, z, w) 425 | } 426 | } 427 | 428 | // -------------------------------------------------- 429 | // Transform from Vector4 430 | // -------------------------------------------------- 431 | 432 | impl From for [f32; 4] { 433 | fn from(lhs: Vector4) -> [f32; 4] { 434 | [lhs.x, lhs.y, lhs.z, lhs.w] 435 | } 436 | } 437 | 438 | impl From for (f32, f32, f32, f32) { 439 | fn from(lhs: Vector4) -> (f32, f32, f32, f32) { 440 | (lhs.x, lhs.y, lhs.z, lhs.w) 441 | } 442 | } 443 | -------------------------------------------------------------------------------- /core/crates/test_util/target/rls/debug/deps/save-analysis/test_util-cf59899279081ef4.json: -------------------------------------------------------------------------------- 1 | {"config":{"output_file":null,"full_docs":false,"pub_only":false,"reachable_only":false,"distro_crate":false,"signatures":false,"borrow_data":false},"version":"0.19.1","compilation":{"directory":"/Users/harrisonturton/Documents/dev/wasm-ui/core","program":"/Users/harrisonturton/.rustup/toolchains/nightly-x86_64-apple-darwin/bin/rls","arguments":[],"output":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/target/rls/debug/deps/libtest_util-cf59899279081ef4.rmeta"},"prelude":{"crate_id":{"name":"test_util","disambiguator":[18014383057476978293,0]},"crate_root":"crates/test_util/src","external_crates":[{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":1,"id":{"name":"std","disambiguator":[8121778101813075280,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":2,"id":{"name":"core","disambiguator":[17620493357780722015,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":3,"id":{"name":"compiler_builtins","disambiguator":[18161881043014032950,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":4,"id":{"name":"rustc_std_workspace_core","disambiguator":[16830480400437399750,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":5,"id":{"name":"alloc","disambiguator":[7722068402736000666,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":6,"id":{"name":"libc","disambiguator":[1004281680224563355,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":7,"id":{"name":"unwind","disambiguator":[18155916040418436021,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":8,"id":{"name":"cfg_if","disambiguator":[9117696045549818189,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":9,"id":{"name":"hashbrown","disambiguator":[334768894470426301,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":10,"id":{"name":"rustc_std_workspace_alloc","disambiguator":[7725474617005895420,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":11,"id":{"name":"rustc_demangle","disambiguator":[17357724652728987651,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":12,"id":{"name":"std_detect","disambiguator":[9382970206070903742,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":13,"id":{"name":"addr2line","disambiguator":[2993262292404423595,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":14,"id":{"name":"gimli","disambiguator":[10596867704632839566,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":15,"id":{"name":"object","disambiguator":[16130277864861404901,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":16,"id":{"name":"memchr","disambiguator":[790493182071655123,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":17,"id":{"name":"panic_unwind","disambiguator":[4301512845898432394,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":18,"id":{"name":"test","disambiguator":[818474990801564077,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":19,"id":{"name":"getopts","disambiguator":[6596277897514226620,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":20,"id":{"name":"rustc_std_workspace_std","disambiguator":[10321667898045059494,0]}},{"file_name":"/Users/harrisonturton/Documents/dev/wasm-ui/core/crates/test_util/src/lib.rs","num":21,"id":{"name":"unicode_width","disambiguator":[3548452647446517558,0]}}],"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":0,"byte_end":647,"line_start":1,"line_end":23,"column_start":1,"column_end":2}},"imports":[{"kind":"Use","ref_id":{"krate":2,"index":9297},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":14,"byte_end":19,"line_start":1,"line_end":1,"column_start":15,"column_end":20},"alias_span":null,"name":"Debug","value":"","parent":{"krate":0,"index":0}},{"kind":"Use","ref_id":{"krate":2,"index":9300},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":14,"byte_end":19,"line_start":1,"line_end":1,"column_start":15,"column_end":20},"alias_span":null,"name":"Debug","value":"","parent":{"krate":0,"index":0}}],"defs":[{"kind":"Mod","id":{"krate":0,"index":0},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":0,"byte_end":647,"line_start":1,"line_end":23,"column_start":1,"column_end":2},"name":"","qualname":"::","value":"crates/test_util/src/lib.rs","parent":null,"children":[{"krate":0,"index":1},{"krate":0,"index":2},{"krate":0,"index":3},{"krate":0,"index":4},{"krate":0,"index":6},{"krate":0,"index":8}],"decl_id":null,"docs":"","sig":null,"attributes":[]},{"kind":"Local","id":{"krate":0,"index":1073741830},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":130,"byte_end":138,"line_start":6,"line_end":6,"column_start":46,"column_end":54},"name":"expected","qualname":"::assert_slice_eq::expected","value":"&[T]","parent":null,"children":[],"decl_id":null,"docs":"","sig":null,"attributes":[]},{"kind":"Local","id":{"krate":0,"index":536870918},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":146,"byte_end":152,"line_start":6,"line_end":6,"column_start":62,"column_end":68},"name":"actual","qualname":"::assert_slice_eq::actual","value":"&[T]","parent":null,"children":[],"decl_id":null,"docs":"","sig":null,"attributes":[]},{"kind":"Type","id":{"krate":0,"index":7},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":108,"byte_end":109,"line_start":6,"line_end":6,"column_start":24,"column_end":25},"name":"T","qualname":"::assert_slice_eq::T$HirId { owner: DefId(0:6 ~ test_util[f9ff]::assert_slice_eq), local_id: 0 }","value":"","parent":null,"children":[],"decl_id":null,"docs":"","sig":null,"attributes":[]},{"kind":"Function","id":{"krate":0,"index":6},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":92,"byte_end":107,"line_start":6,"line_end":6,"column_start":8,"column_end":23},"name":"assert_slice_eq","qualname":"::assert_slice_eq","value":"pub fn assert_slice_eq(&[T], &[T])","parent":null,"children":[],"decl_id":null,"docs":" # Panics","sig":null,"attributes":[{"value":"/ # Panics","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":22,"byte_end":34,"line_start":3,"line_end":3,"column_start":1,"column_end":13}},{"value":"/","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":35,"byte_end":38,"line_start":4,"line_end":4,"column_start":1,"column_end":4}},{"value":"/ Will panic when the slices are different.","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":39,"byte_end":84,"line_start":5,"line_end":5,"column_start":1,"column_end":46}}]},{"kind":"Local","id":{"krate":0,"index":3992977414},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":342,"byte_end":343,"line_start":13,"line_end":13,"column_start":10,"column_end":11},"name":"i","qualname":"i$HirId { owner: DefId(0:6 ~ test_util[f9ff]::assert_slice_eq), local_id: 119 }","value":"usize","parent":null,"children":[],"decl_id":null,"docs":"","sig":null,"attributes":[]},{"kind":"Local","id":{"krate":0,"index":503316486},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":345,"byte_end":353,"line_start":13,"line_end":13,"column_start":13,"column_end":21},"name":"expected","qualname":"expected$HirId { owner: DefId(0:6 ~ test_util[f9ff]::assert_slice_eq), local_id: 120 }","value":"&T","parent":null,"children":[],"decl_id":null,"docs":"","sig":null,"attributes":[]},{"kind":"Local","id":{"krate":0,"index":553648134},"span":{"file_name":"crates/test_util/src/lib.rs","byte_start":400,"byte_end":406,"line_start":14,"line_end":14,"column_start":13,"column_end":19},"name":"actual","qualname":"actual$HirId { owner: DefId(0:6 ~ test_util[f9ff]::assert_slice_eq), local_id: 132 }","value":"&T","parent":null,"children":[],"decl_id":null,"docs":"","sig":null,"attributes":[]}],"impls":[],"refs":[{"kind":"Mod","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":4,"byte_end":7,"line_start":1,"line_end":1,"column_start":5,"column_end":8},"ref_id":{"krate":1,"index":0}},{"kind":"Mod","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":9,"byte_end":12,"line_start":1,"line_end":1,"column_start":10,"column_end":13},"ref_id":{"krate":5,"index":4294}},{"kind":"Type","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":111,"byte_end":120,"line_start":6,"line_end":6,"column_start":27,"column_end":36},"ref_id":{"krate":2,"index":2640}},{"kind":"Type","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":123,"byte_end":128,"line_start":6,"line_end":6,"column_start":39,"column_end":44},"ref_id":{"krate":2,"index":9297}},{"kind":"Type","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":142,"byte_end":143,"line_start":6,"line_end":6,"column_start":58,"column_end":59},"ref_id":{"krate":0,"index":7}},{"kind":"Type","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":156,"byte_end":157,"line_start":6,"line_end":6,"column_start":72,"column_end":73},"ref_id":{"krate":0,"index":7}},{"kind":"Function","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":192,"byte_end":195,"line_start":8,"line_end":8,"column_start":18,"column_end":21},"ref_id":{"krate":2,"index":11160}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":183,"byte_end":191,"line_start":8,"line_end":8,"column_start":9,"column_end":17},"ref_id":{"krate":0,"index":1073741830}},{"kind":"Function","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":208,"byte_end":211,"line_start":8,"line_end":8,"column_start":34,"column_end":37},"ref_id":{"krate":2,"index":11160}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":201,"byte_end":207,"line_start":8,"line_end":8,"column_start":27,"column_end":33},"ref_id":{"krate":0,"index":536870918}},{"kind":"Function","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":297,"byte_end":300,"line_start":10,"line_end":10,"column_start":18,"column_end":21},"ref_id":{"krate":2,"index":11160}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":288,"byte_end":296,"line_start":10,"line_end":10,"column_start":9,"column_end":17},"ref_id":{"krate":0,"index":1073741830}},{"kind":"Function","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":319,"byte_end":322,"line_start":11,"line_end":11,"column_start":16,"column_end":19},"ref_id":{"krate":2,"index":11160}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":312,"byte_end":318,"line_start":11,"line_end":11,"column_start":9,"column_end":15},"ref_id":{"krate":0,"index":536870918}},{"kind":"Function","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":367,"byte_end":371,"line_start":13,"line_end":13,"column_start":35,"column_end":39},"ref_id":{"krate":2,"index":11184}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":358,"byte_end":366,"line_start":13,"line_end":13,"column_start":26,"column_end":34},"ref_id":{"krate":0,"index":1073741830}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":341,"byte_end":354,"line_start":13,"line_end":13,"column_start":9,"column_end":22},"ref_id":{"krate":2,"index":43192}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":410,"byte_end":416,"line_start":14,"line_end":14,"column_start":23,"column_end":29},"ref_id":{"krate":0,"index":536870918}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":417,"byte_end":418,"line_start":14,"line_end":14,"column_start":30,"column_end":31},"ref_id":{"krate":0,"index":3992977414}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":451,"byte_end":459,"line_start":16,"line_end":16,"column_start":14,"column_end":22},"ref_id":{"krate":0,"index":503316486}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":464,"byte_end":470,"line_start":16,"line_end":16,"column_start":27,"column_end":33},"ref_id":{"krate":0,"index":553648134}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":584,"byte_end":585,"line_start":18,"line_end":18,"column_start":13,"column_end":14},"ref_id":{"krate":0,"index":3992977414}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":599,"byte_end":607,"line_start":19,"line_end":19,"column_start":13,"column_end":21},"ref_id":{"krate":0,"index":503316486}},{"kind":"Variable","span":{"file_name":"crates/test_util/src/lib.rs","byte_start":621,"byte_end":627,"line_start":20,"line_end":20,"column_start":13,"column_end":19},"ref_id":{"krate":0,"index":553648134}}],"macro_refs":[],"relations":[]} -------------------------------------------------------------------------------- /core/src/app.rs: -------------------------------------------------------------------------------- 1 | use layout::{ 2 | Alignment, Axis, Borders, Color, Container, CrossAxisAlignment, EdgeInsets, Flex, Flexible, 3 | Layout, MainAxisAlignment, MainAxisSize, Positioned, Stack, 4 | }; 5 | use math::Vector2; 6 | use platform::AppDriver; 7 | 8 | pub struct App { 9 | position: Vector2, 10 | } 11 | 12 | impl AppDriver for App { 13 | fn tick(&mut self, time: f32) -> Box { 14 | self.sidebar(time) 15 | } 16 | } 17 | 18 | impl App { 19 | pub fn new() -> App { 20 | let position = Vector2::zero(); 21 | App { position } 22 | } 23 | 24 | #[allow(dead_code)] 25 | pub fn sidebar(&self, time: f32) -> Box { 26 | let speed = 0.003; 27 | let size_multiplier = 0.5 + (0.5 * (time * speed).sin()); 28 | let size = 200.0 * size_multiplier + 1.0; // Extra 1 to accomodate border padding 29 | 30 | let border_color = Color::rgba(70.0, 70.0, 70.0, 255.0); 31 | let mut files: Vec> = vec![]; 32 | let mut files2: Vec> = vec![]; 33 | for _ in 0..10 { 34 | files.push(Box::new(Container { 35 | height: Some(20.0), 36 | width: None, 37 | color: Color::rgba(40.0, 40.0, 40.0, 255.0), 38 | margin: EdgeInsets::bottom(5.0), 39 | ..Default::default() 40 | })); 41 | files2.push(Box::new(Container { 42 | height: Some(25.0), 43 | width: None, 44 | color: Color::rgba(40.0, 40.0, 40.0, 255.0), 45 | margin: EdgeInsets::bottom(5.0), 46 | ..Default::default() 47 | })); 48 | } 49 | let widgets = Container { 50 | borders: Borders::bottom(Color::rgba(15.0, 100.0, 225.0, 255.0), 10.0), 51 | child: Some(Box::new(Flex { 52 | axis: Axis::Horizontal, 53 | main_axis_size: MainAxisSize::Max, 54 | main_axis_alignment: MainAxisAlignment::Start, 55 | cross_axis_alignment: CrossAxisAlignment::Stretch, 56 | children: vec![ 57 | Box::new(Container { 58 | width: Some(50.0), 59 | height: Some(f32::INFINITY), 60 | color: Color::rgba(30.0, 30.0, 30.0, 255.0), 61 | padding: EdgeInsets { 62 | top: 6.0, 63 | bottom: 0.0, 64 | right: 6.0, 65 | left: 6.0, 66 | }, 67 | child: Some(Box::new(Flex { 68 | axis: Axis::Vertical, 69 | main_axis_size: MainAxisSize::Max, 70 | main_axis_alignment: MainAxisAlignment::Start, 71 | cross_axis_alignment: CrossAxisAlignment::Center, 72 | children: vec![ 73 | Box::new(Container { 74 | height: Some(40.0), 75 | width: Some(40.0), 76 | color: Color::rgba(45.0, 45.0, 45.0, 255.0), 77 | margin: EdgeInsets::bottom(5.0), 78 | ..Default::default() 79 | }), 80 | Box::new(Container { 81 | height: Some(40.0), 82 | width: Some(40.0), 83 | color: Color::rgba(45.0, 45.0, 45.0, 255.0), 84 | margin: EdgeInsets::bottom(5.0), 85 | ..Default::default() 86 | }), 87 | Box::new(Container { 88 | height: Some(40.0), 89 | width: Some(40.0), 90 | color: Color::rgba(45.0, 45.0, 45.0, 255.0), 91 | margin: EdgeInsets::bottom(5.0), 92 | ..Default::default() 93 | }), 94 | Box::new(Flexible { 95 | flex_factor: 1.0, 96 | child: Box::new(layout::Spacer {}), 97 | }), 98 | Box::new(Container { 99 | height: Some(40.0), 100 | width: Some(40.0), 101 | color: Color::rgba(45.0, 45.0, 45.0, 255.0), 102 | margin: EdgeInsets::bottom(5.0), 103 | ..Default::default() 104 | }), 105 | ], 106 | })), 107 | ..Default::default() 108 | }), 109 | Box::new(Container { 110 | borders: Borders::right(border_color, 1.0), 111 | height: Some(f32::INFINITY), 112 | width: Some(size), 113 | color: Color::rgba(35.0, 35.0, 35.0, 255.0), 114 | child: Some(Box::new(Flex { 115 | axis: Axis::Vertical, 116 | main_axis_size: MainAxisSize::Max, 117 | main_axis_alignment: MainAxisAlignment::Start, 118 | cross_axis_alignment: CrossAxisAlignment::Stretch, 119 | children: files, 120 | })), 121 | ..Default::default() 122 | }), 123 | Box::new(Flexible { 124 | flex_factor: 1.0, 125 | child: Box::new(Container { 126 | color: Color::rgba(22.0, 22.0, 22.0, 255.0), 127 | alignment: Alignment::center(), 128 | child: Some(Box::new(Flex { 129 | axis: Axis::Vertical, 130 | main_axis_size: MainAxisSize::Min, 131 | main_axis_alignment: MainAxisAlignment::Start, 132 | cross_axis_alignment: CrossAxisAlignment::Center, 133 | children: vec![ 134 | Box::new(Container { 135 | width: Some(150.0), 136 | height: Some(15.0), 137 | margin: EdgeInsets::bottom(5.0), 138 | color: Color::rgba(45.0, 45.0, 45.0, 255.0), 139 | ..Default::default() 140 | }), 141 | Box::new(Container { 142 | width: Some(100.0), 143 | height: Some(15.0), 144 | margin: EdgeInsets::bottom(5.0), 145 | color: Color::rgba(35.0, 35.0, 35.0, 255.0), 146 | ..Default::default() 147 | }), 148 | Box::new(Container { 149 | width: Some(150.0), 150 | height: Some(15.0), 151 | margin: EdgeInsets::bottom(5.0), 152 | color: Color::rgba(45.0, 45.0, 45.0, 255.0), 153 | ..Default::default() 154 | }), 155 | ], 156 | })), 157 | ..Default::default() 158 | }), 159 | }), 160 | Box::new(Container { 161 | width: Some(175.0), 162 | height: Some(f32::INFINITY), 163 | color: Color::rgba(35.0, 35.0, 35.0, 255.0), 164 | borders: Borders::left(border_color, 1.0), 165 | child: Some(Box::new(Flex { 166 | axis: Axis::Vertical, 167 | main_axis_size: MainAxisSize::Max, 168 | main_axis_alignment: MainAxisAlignment::Start, 169 | cross_axis_alignment: CrossAxisAlignment::Stretch, 170 | children: files2, 171 | })), 172 | ..Default::default() 173 | }), 174 | ], 175 | })), 176 | ..Container::default() 177 | }; 178 | Box::new(widgets) 179 | } 180 | 181 | // The green box should be positioned at (200, 200). If not, then we are not 182 | // correctly calculating the cumulative relative offset of a widget from 183 | // it's ancestors. 184 | #[allow(dead_code)] 185 | fn render_nested_positioned(&self, _time: f32) -> Box { 186 | Box::new(Positioned { 187 | position: (0.0, 0.0).into(), 188 | child: Box::new(Positioned { 189 | position: (100.0, 100.0).into(), 190 | child: Box::new(Positioned { 191 | position: (200.0, 200.0).into(), 192 | child: Box::new(Container { 193 | color: Color::green(), 194 | width: Some(100.0), 195 | height: Some(100.0), 196 | ..Default::default() 197 | }), 198 | }), 199 | }), 200 | }) 201 | } 202 | 203 | #[allow(dead_code)] 204 | fn render_sidebar(&self, _: f32) -> Box { 205 | Box::new(Positioned { 206 | position: Vector2::zero(), 207 | child: Box::new(Container { 208 | color: Color::rgba(0.0, 0.0, 0.0, 50.0), 209 | width: Some(150.0), 210 | height: Some(f32::INFINITY), 211 | ..Default::default() 212 | }), 213 | }) 214 | } 215 | 216 | // 5 boxes should be placed directly next to eachother in a row. This tests 217 | // for bugs in how the layout algorithm positions widgets relative to their 218 | // parent, specifically when there are multiple independently-positioned 219 | // widgets that are laid out in a row. 220 | #[allow(dead_code)] 221 | fn render_boxes(&self, _: f32) -> Box { 222 | Box::new(Stack { 223 | children: vec![ 224 | Positioned { 225 | position: (0.0, 0.0).into(), 226 | child: Box::new(Container { 227 | width: Some(100.0), 228 | height: Some(100.0), 229 | color: Color::green(), 230 | ..Default::default() 231 | }), 232 | }, 233 | Positioned { 234 | position: (100.0, 0.0).into(), 235 | child: Box::new(Container { 236 | width: Some(100.0), 237 | height: Some(100.0), 238 | color: Color::red(), 239 | ..Default::default() 240 | }), 241 | }, 242 | Positioned { 243 | position: (200.0, 0.0).into(), 244 | child: Box::new(Container { 245 | width: Some(100.0), 246 | height: Some(100.0), 247 | color: Color::blue(), 248 | ..Default::default() 249 | }), 250 | }, 251 | Positioned { 252 | position: (300.0, 0.0).into(), 253 | child: Box::new(Container { 254 | width: Some(100.0), 255 | height: Some(100.0), 256 | color: Color::red(), 257 | ..Default::default() 258 | }), 259 | }, 260 | Positioned { 261 | position: (400.0, 0.0).into(), 262 | child: Box::new(Container { 263 | width: Some(100.0), 264 | height: Some(100.0), 265 | color: Color::green(), 266 | ..Default::default() 267 | }), 268 | }, 269 | ], 270 | }) 271 | } 272 | 273 | #[allow(dead_code)] 274 | fn render_moving_box(&mut self, time: f32) -> Box { 275 | let speed = 0.005; 276 | let radius = 100.0; 277 | let offset = Vector2::new(100.0, 100.0); 278 | self.position.x = 100.0 + radius * (time * speed).sin(); 279 | self.position.y = 100.0 + radius * (time * speed).cos(); 280 | self.position += offset; 281 | 282 | Box::new(Positioned { 283 | position: self.position, 284 | child: Box::new(Container { 285 | color: Color::rgba(0.0, 0.0, 0.0, 50.0), 286 | width: Some(100.0), 287 | height: Some(100.0), 288 | ..Default::default() 289 | }), 290 | }) 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /core/crates/math/src/vector2.rs: -------------------------------------------------------------------------------- 1 | use crate::Vector3; 2 | use bytemuck::{Pod, Zeroable}; 3 | use std::default::Default; 4 | use std::ops::{ 5 | Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, 6 | }; 7 | 8 | #[macro_export] 9 | macro_rules! vector2 { 10 | ($x: expr, $y: expr) => { 11 | Vector2 { x: $x, y: $y } 12 | }; 13 | } 14 | 15 | /// A 2-dimensional vector. 16 | #[repr(C)] 17 | #[derive(PartialOrd, Copy, Clone, Debug, Pod, Zeroable)] 18 | pub struct Vector2 { 19 | /// The x component of the vector. 20 | pub x: f32, 21 | /// The y component of the vector. 22 | pub y: f32, 23 | } 24 | 25 | impl Vector2 { 26 | /// Construct a new vector using the provided components. 27 | #[must_use] 28 | pub fn new(x: f32, y: f32) -> Vector2 { 29 | Vector2 { x, y } 30 | } 31 | 32 | /// Construct a new vector where all components are 0. 33 | #[must_use] 34 | pub fn zero() -> Vector2 { 35 | Vector2::new(0.0, 0.0) 36 | } 37 | 38 | /// Check if the vector is zero. 39 | #[must_use] 40 | pub fn is_zero(self) -> bool { 41 | self == Vector2::zero() 42 | } 43 | 44 | #[must_use] 45 | pub fn up() -> Vector2 { 46 | Vector2::new(0.0, 1.0) 47 | } 48 | 49 | #[must_use] 50 | pub fn is_up(self) -> bool { 51 | self == Vector2::up() 52 | } 53 | 54 | #[must_use] 55 | pub fn down() -> Vector2 { 56 | Vector2::new(0.0, -1.0) 57 | } 58 | 59 | #[must_use] 60 | pub fn is_down(self) -> bool { 61 | self == Vector2::down() 62 | } 63 | 64 | #[must_use] 65 | pub fn left() -> Vector2 { 66 | Vector2::new(-1.0, 0.0) 67 | } 68 | 69 | #[must_use] 70 | pub fn is_left(self) -> bool { 71 | self == Vector2::left() 72 | } 73 | 74 | #[must_use] 75 | pub fn right() -> Vector2 { 76 | Vector2::new(1.0, 0.0) 77 | } 78 | 79 | #[must_use] 80 | pub fn is_right(self) -> bool { 81 | self == Vector2::right() 82 | } 83 | 84 | /// Calculate the sum of the x and y components of the vector. 85 | #[must_use] 86 | pub fn sum(self) -> f32 { 87 | self.x + self.y 88 | } 89 | 90 | /// Calculate the product of the x and y components of the vector. 91 | #[must_use] 92 | pub fn product(self) -> f32 { 93 | self.x * self.y 94 | } 95 | 96 | /// Get the dot product of two vectors. 97 | #[must_use] 98 | pub fn dot(lhs: Vector2, rhs: Vector2) -> f32 { 99 | (lhs.x * rhs.x) + (lhs.y * rhs.y) 100 | } 101 | 102 | /// Get the magnitude, or length, of the vector. 103 | #[must_use] 104 | pub fn magnitude(self) -> f32 { 105 | (self.x.powi(2) + self.y.powi(2)).sqrt() 106 | } 107 | 108 | /// Return a vector with a magnitude of 1. 109 | #[must_use] 110 | pub fn normalized(self) -> Vector2 { 111 | let mag = self.magnitude(); 112 | Vector2::new(self.x / mag, self.y / mag) 113 | } 114 | 115 | /// Get the smallest component of the vector. 116 | #[must_use] 117 | pub fn min(self) -> f32 { 118 | if self.x < self.y { 119 | self.x 120 | } else { 121 | self.y 122 | } 123 | } 124 | 125 | /// Get the largest component of the vector. 126 | #[must_use] 127 | pub fn max(self) -> f32 { 128 | if self.x > self.y { 129 | self.x 130 | } else { 131 | self.y 132 | } 133 | } 134 | 135 | // Clamp the x and y components between the bounds of another Vector 136 | #[must_use] 137 | pub fn clamp(self, bounds: Vector2) -> Vector2 { 138 | Vector2::new( 139 | safe_clamp(self.x, bounds.x, bounds.y), 140 | safe_clamp(self.y, bounds.x, bounds.y), 141 | ) 142 | } 143 | 144 | #[must_use] 145 | pub fn clamp_between(self, min: Vector2, max: Vector2) -> Vector2 { 146 | Vector2::new( 147 | safe_clamp(self.x, min.x, max.x), 148 | safe_clamp(self.y, min.y, max.y), 149 | ) 150 | } 151 | } 152 | 153 | fn safe_clamp(val: f32, min: f32, max: f32) -> f32 { 154 | if min > max { 155 | log::debug!("{:?}.clamp({:?}, {:?})", val, min, max); 156 | 0.0 157 | } else { 158 | val.clamp(min, max) 159 | } 160 | } 161 | 162 | // -------------------------------------------------- 163 | // Vector operations 164 | // -------------------------------------------------- 165 | 166 | impl Default for Vector2 { 167 | /// Get the zero vector. 168 | fn default() -> Vector2 { 169 | Vector2::zero() 170 | } 171 | } 172 | 173 | impl PartialEq for Vector2 { 174 | fn eq(&self, rhs: &Vector2) -> bool { 175 | let eq_x = (self.x - rhs.x).abs() <= f32::EPSILON; 176 | let eq_y = (self.y - rhs.y).abs() <= f32::EPSILON; 177 | eq_x && eq_y 178 | } 179 | } 180 | 181 | impl Eq for Vector2 {} 182 | 183 | impl Neg for Vector2 { 184 | type Output = Vector2; 185 | 186 | /// Flip the sign on all the components in the vector. 187 | fn neg(self) -> Vector2 { 188 | Vector2::new(-self.x, -self.y) 189 | } 190 | } 191 | 192 | impl Add for Vector2 { 193 | type Output = Vector2; 194 | 195 | /// Add two vectors together. The result will have each component be the sum 196 | /// of the original two components. 197 | fn add(self, rhs: Vector2) -> Vector2 { 198 | Vector2::new(self.x + rhs.x, self.y + rhs.y) 199 | } 200 | } 201 | 202 | impl AddAssign for Vector2 { 203 | /// Add the components of another vector. 204 | fn add_assign(&mut self, rhs: Vector2) { 205 | self.x += rhs.x; 206 | self.y += rhs.y; 207 | } 208 | } 209 | 210 | impl Sub for Vector2 { 211 | type Output = Vector2; 212 | 213 | /// Subtract two vectors together. The result will have each component be the difference 214 | /// of the original two components. 215 | fn sub(self, rhs: Vector2) -> Vector2 { 216 | Vector2::new(self.x - rhs.x, self.y - rhs.y) 217 | } 218 | } 219 | 220 | impl SubAssign for Vector2 { 221 | /// Subtract the components of another vector. 222 | fn sub_assign(&mut self, rhs: Vector2) { 223 | self.x -= rhs.x; 224 | self.y -= rhs.y; 225 | } 226 | } 227 | 228 | impl Mul for Vector2 { 229 | type Output = Vector2; 230 | 231 | /// Multiply two vectors together. The result will have each component be the multiplication 232 | /// of the original two components. 233 | fn mul(self, rhs: Vector2) -> Vector2 { 234 | Vector2::new(self.x * rhs.x, self.y * rhs.y) 235 | } 236 | } 237 | 238 | impl MulAssign for Vector2 { 239 | /// Multiply by the components of another vector. 240 | fn mul_assign(&mut self, rhs: Vector2) { 241 | self.x *= rhs.x; 242 | self.y *= rhs.y; 243 | } 244 | } 245 | 246 | impl Div for Vector2 { 247 | type Output = Vector2; 248 | 249 | /// Divide two vectors together. The result will have each component be the division 250 | /// of the original two components. 251 | fn div(self, rhs: Vector2) -> Vector2 { 252 | Vector2::new(self.x / rhs.x, self.y / rhs.y) 253 | } 254 | } 255 | 256 | impl DivAssign for Vector2 { 257 | /// Divide by the components of another vector. 258 | fn div_assign(&mut self, rhs: Vector2) { 259 | self.x /= rhs.x; 260 | self.y /= rhs.y; 261 | } 262 | } 263 | 264 | impl Rem for Vector2 { 265 | type Output = Vector2; 266 | 267 | /// Get the elementwise remainder of two vectors. The result will have each component be 268 | /// the remainder of the original two components. 269 | fn rem(self, rhs: Vector2) -> Vector2 { 270 | Vector2::new(self.x % rhs.x, self.y % rhs.y) 271 | } 272 | } 273 | 274 | impl RemAssign for Vector2 { 275 | /// Get the elementwise remainder using the components of another vector. 276 | fn rem_assign(&mut self, rhs: Vector2) { 277 | self.x %= rhs.x; 278 | self.y %= rhs.y; 279 | } 280 | } 281 | 282 | // -------------------------------------------------- 283 | // Scalar operations 284 | // -------------------------------------------------- 285 | 286 | impl Add for Vector2 { 287 | type Output = Vector2; 288 | 289 | /// Add a scalar to each component of the vector. 290 | fn add(self, rhs: f32) -> Vector2 { 291 | Vector2::new(self.x + rhs, self.y + rhs) 292 | } 293 | } 294 | 295 | impl AddAssign for Vector2 { 296 | /// Add a scalar to each component of the vector. 297 | fn add_assign(&mut self, rhs: f32) { 298 | self.x += rhs; 299 | self.y += rhs; 300 | } 301 | } 302 | 303 | impl Sub for Vector2 { 304 | type Output = Vector2; 305 | 306 | /// Subtract a scalar from each component of the vector. 307 | fn sub(self, rhs: f32) -> Vector2 { 308 | Vector2::new(self.x - rhs, self.y - rhs) 309 | } 310 | } 311 | 312 | impl SubAssign for Vector2 { 313 | /// Subtract a scalar from each component of the vector. 314 | fn sub_assign(&mut self, rhs: f32) { 315 | self.x -= rhs; 316 | self.y -= rhs; 317 | } 318 | } 319 | 320 | impl Mul for Vector2 { 321 | type Output = Vector2; 322 | 323 | /// Multiply each component of the vector by a scalar. 324 | fn mul(self, rhs: f32) -> Vector2 { 325 | Vector2::new(self.x * rhs, self.y * rhs) 326 | } 327 | } 328 | 329 | impl MulAssign for Vector2 { 330 | /// Multiply each component of the vector by a scalar. 331 | fn mul_assign(&mut self, rhs: f32) { 332 | self.x *= rhs; 333 | self.y *= rhs; 334 | } 335 | } 336 | 337 | impl Div for Vector2 { 338 | type Output = Vector2; 339 | 340 | /// Divide each component of the vector by a scalar. 341 | fn div(self, rhs: f32) -> Vector2 { 342 | Vector2::new(self.x / rhs, self.y / rhs) 343 | } 344 | } 345 | 346 | impl DivAssign for Vector2 { 347 | /// Divide each component of the vector by a scalar. 348 | fn div_assign(&mut self, rhs: f32) { 349 | self.x /= rhs; 350 | self.y /= rhs; 351 | } 352 | } 353 | 354 | impl Rem for Vector2 { 355 | type Output = Vector2; 356 | 357 | /// Get the remainder of each component after dividing by a scalar. 358 | fn rem(self, rhs: f32) -> Vector2 { 359 | Vector2::new(self.x % rhs, self.y % rhs) 360 | } 361 | } 362 | 363 | impl RemAssign for Vector2 { 364 | /// Get the remainder after a component-wise division of a scalar. 365 | fn rem_assign(&mut self, rhs: f32) { 366 | self.x %= rhs; 367 | self.y %= rhs; 368 | } 369 | } 370 | 371 | // -------------------------------------------------- 372 | // Transform into Vector2 373 | // -------------------------------------------------- 374 | 375 | impl From<[f32; 2]> for Vector2 { 376 | fn from(lhs: [f32; 2]) -> Vector2 { 377 | Vector2::new(lhs[0], lhs[1]) 378 | } 379 | } 380 | 381 | impl From<(f32, f32)> for Vector2 { 382 | fn from(lhs: (f32, f32)) -> Vector2 { 383 | let (x, y) = lhs; 384 | Vector2::new(x, y) 385 | } 386 | } 387 | 388 | // -------------------------------------------------- 389 | // Transform from Vector2 390 | // -------------------------------------------------- 391 | 392 | impl From for Vector3 { 393 | fn from(lhs: Vector2) -> Vector3 { 394 | Vector3::new(lhs.x, lhs.y, 0.0) 395 | } 396 | } 397 | 398 | impl From for [f32; 2] { 399 | fn from(lhs: Vector2) -> [f32; 2] { 400 | [lhs.x, lhs.y] 401 | } 402 | } 403 | 404 | impl From for (f32, f32) { 405 | fn from(lhs: Vector2) -> (f32, f32) { 406 | (lhs.x, lhs.y) 407 | } 408 | } 409 | 410 | #[cfg(test)] 411 | mod tests { 412 | use super::*; 413 | 414 | #[test] 415 | fn macro_constructor_is_equal_to_new_constructor() { 416 | let actual = vector2!(199.0, -512.0); 417 | let expected = Vector2::new(199.0, -512.0); 418 | assert_eq!(expected, actual); 419 | } 420 | 421 | #[test] 422 | fn up_constructor_is_up() { 423 | let actual = Vector2::up(); 424 | let expected = Vector2::new(0.0, 1.0); 425 | assert_eq!(expected, actual); 426 | } 427 | 428 | #[test] 429 | fn down_constructor_is_down() { 430 | let actual = Vector2::down(); 431 | let expected = Vector2::new(0.0, -1.0); 432 | assert_eq!(expected, actual); 433 | } 434 | 435 | #[test] 436 | fn left_constructor_is_left() { 437 | let actual = Vector2::left(); 438 | let expected = Vector2::new(-1.0, 0.0); 439 | assert_eq!(expected, actual); 440 | } 441 | 442 | #[test] 443 | fn right_constructor_is_right() { 444 | let actual = Vector2::right(); 445 | let expected = Vector2::new(1.0, 0.0); 446 | assert_eq!(expected, actual); 447 | } 448 | 449 | #[test] 450 | fn sum_gives_expected_result() { 451 | let vec = vector2!(-50.0, 100.0); 452 | 453 | let actual = vec.sum(); 454 | let expected = 50.0; 455 | assert_eq!(expected, actual); 456 | } 457 | 458 | #[test] 459 | fn product_gives_expected_result() { 460 | let vec = vector2!(-50.0, 100.0); 461 | 462 | let actual = vec.product(); 463 | let expected = -5000.0; 464 | assert_eq!(expected, actual); 465 | } 466 | 467 | #[test] 468 | fn dot_product_is_zero_for_equal_vectors() { 469 | let a = vector2!(0.0, 1.0); 470 | let b = vector2!(0.0, 1.0); 471 | 472 | let actual = Vector2::dot(a, b); 473 | let expected = 1.0; 474 | assert_eq!(expected, actual); 475 | } 476 | 477 | #[test] 478 | fn dot_product_gives_expected_result() { 479 | let a = vector2!(50.0, -100.0); 480 | let b = vector2!(-20.0, 100.0); 481 | 482 | let actual = Vector2::dot(a, b); 483 | let expected = -11000.0; 484 | assert_eq!(expected, actual); 485 | } 486 | 487 | #[test] 488 | fn magnitude_for_unit_vectors_are_zero() { 489 | let up = Vector2::up(); 490 | let up_magnitude = up.magnitude(); 491 | assert_eq!(1.0, up_magnitude); 492 | 493 | let down = Vector2::down(); 494 | let down_magnitude = down.magnitude(); 495 | assert_eq!(1.0, down_magnitude); 496 | 497 | let left = Vector2::left(); 498 | let left_magnitude = left.magnitude(); 499 | assert_eq!(1.0, left_magnitude); 500 | 501 | let right = Vector2::right(); 502 | let right_magnitude = right.magnitude(); 503 | assert_eq!(1.0, right_magnitude); 504 | } 505 | 506 | #[test] 507 | fn normalize_gives_correct_result_for_non_unit_vector() { 508 | let vec = vector2!(0.0, 100.0); 509 | let actual = vec.normalized(); 510 | let expected = vector2!(0.0, 1.0); 511 | assert_eq!(expected, actual); 512 | } 513 | 514 | #[test] 515 | fn normalize_gives_correct_result_for_unit_vector() { 516 | let vec = Vector2::up(); 517 | let actual = vec.normalized(); 518 | let expected = Vector2::up(); 519 | assert_eq!(expected, actual); 520 | } 521 | 522 | #[test] 523 | fn min_gives_expected_result_when_x_is_less_than_y() { 524 | let vec = vector2!(-100.0, 50.0); 525 | assert_eq!(-100.0, vec.min()); 526 | } 527 | 528 | #[test] 529 | fn min_gives_expected_result_when_y_is_less_than_x() { 530 | let vec = vector2!(50.0, -100.0); 531 | assert_eq!(-100.0, vec.min()); 532 | } 533 | 534 | #[test] 535 | fn max_gives_expected_result_when_x_is_greater_than_y() { 536 | let vec = vector2!(50.0, -100.0); 537 | assert_eq!(50.0, vec.max()); 538 | } 539 | 540 | #[test] 541 | fn max_gives_expected_result_when_y_is_greater_than_x() { 542 | let vec = vector2!(-100.0, 50.0); 543 | assert_eq!(50.0, vec.max()); 544 | } 545 | 546 | #[test] 547 | fn default_constructor_is_zero() { 548 | let actual = Default::default(); 549 | let expected = Vector2::zero(); 550 | assert_eq!(expected, actual); 551 | } 552 | 553 | #[test] 554 | fn negate_gives_expected_result() { 555 | let vec = vector2!(-100.0, 50.0); 556 | 557 | let actual = -vec; 558 | let expected = vector2!(100.0, -50.0); 559 | assert_eq!(expected, actual); 560 | } 561 | 562 | #[test] 563 | fn adding_vector_to_vector_gives_expected_result() { 564 | let a = vector2!(-100.0, 50.0); 565 | let b = vector2!(100.0, -50.0); 566 | 567 | let actual = a + b; 568 | let expected = Vector2::zero(); 569 | assert_eq!(expected, actual); 570 | } 571 | 572 | #[test] 573 | fn add_assign_vector_to_vector_gives_expected_result() { 574 | let mut actual = vector2!(-100.0, 50.0); 575 | actual += vector2!(100.0, -50.0); 576 | 577 | let expected = Vector2::zero(); 578 | assert_eq!(expected, actual); 579 | } 580 | 581 | #[test] 582 | fn subtracting_vector_from_vector_gives_expected_result() { 583 | let a = vector2!(-100.0, 50.0); 584 | let b = vector2!(100.0, -50.0); 585 | 586 | let actual = a - b; 587 | let expected = vector2!(-200.0, 100.0); 588 | assert_eq!(expected, actual); 589 | } 590 | 591 | #[test] 592 | fn subtract_assign_vector_to_vector_gives_expected_result() { 593 | let mut actual = vector2!(-100.0, 50.0); 594 | actual -= vector2!(100.0, -50.0); 595 | 596 | let expected = vector2!(-200.0, 100.0); 597 | assert_eq!(expected, actual); 598 | } 599 | 600 | #[test] 601 | fn multiplying_vector_by_vector_gives_expected_result() { 602 | let a = vector2!(-100.0, 50.0); 603 | let b = vector2!(100.0, -50.0); 604 | 605 | let actual = a * b; 606 | let expected = vector2!(-10000.0, -2500.0); 607 | assert_eq!(expected, actual); 608 | } 609 | 610 | #[test] 611 | fn multiply_assign_vector_to_vector_gives_expected_result() { 612 | let mut actual = vector2!(-100.0, 50.0); 613 | actual *= vector2!(100.0, -50.0); 614 | 615 | let expected = vector2!(-10000.0, -2500.0); 616 | assert_eq!(expected, actual); 617 | } 618 | 619 | #[test] 620 | fn dividing_vector_by_vector_gives_expected_result() { 621 | let a = vector2!(-100.0, 50.0); 622 | let b = vector2!(100.0, -50.0); 623 | 624 | let actual = a / b; 625 | let expected = vector2!(-1.0, -1.0); 626 | assert_eq!(expected, actual); 627 | } 628 | 629 | #[test] 630 | fn div_assign_vector_to_vector_gives_expected_result() { 631 | let mut actual = vector2!(-100.0, 50.0); 632 | actual /= vector2!(100.0, -50.0); 633 | 634 | let expected = vector2!(-1.0, -1.0); 635 | assert_eq!(expected, actual); 636 | } 637 | 638 | #[test] 639 | fn remainder_vector_by_vector_gives_expected_result() { 640 | let a = vector2!(-100.0, 50.0); 641 | let b = vector2!(100.0, -50.0); 642 | 643 | let actual = a % b; 644 | let expected = Vector2::zero(); 645 | assert_eq!(expected, actual); 646 | } 647 | 648 | #[test] 649 | fn remainder_assign_vector_to_vector_gives_expected_result() { 650 | let mut actual = vector2!(-100.0, 50.0); 651 | actual %= vector2!(100.0, -50.0); 652 | 653 | let expected = Vector2::zero(); 654 | assert_eq!(expected, actual); 655 | } 656 | 657 | #[test] 658 | fn add_scalar_to_vector_gives_expected_result() { 659 | let vec = vector2!(-100.0, 50.0); 660 | let scalar = 100.0; 661 | 662 | let actual = vec + scalar; 663 | let expected = vector2!(0.0, 150.0); 664 | assert_eq!(expected, actual); 665 | } 666 | 667 | #[test] 668 | fn add_assign_scalar_to_vector_gives_expected_result() { 669 | let mut actual = vector2!(-100.0, 50.0); 670 | actual += 100.0; 671 | 672 | let expected = vector2!(0.0, 150.0); 673 | assert_eq!(expected, actual); 674 | } 675 | 676 | #[test] 677 | fn subtract_scalar_from_vector_gives_expected_result() { 678 | let vec = vector2!(-100.0, 50.0); 679 | let scalar = 100.0; 680 | 681 | let actual = vec - scalar; 682 | let expected = vector2!(-200.0, -50.0); 683 | assert_eq!(expected, actual); 684 | } 685 | 686 | #[test] 687 | fn subtract_assign_scalar_to_vector_gives_expected_result() { 688 | let mut actual = vector2!(-100.0, 50.0); 689 | actual -= 100.0; 690 | 691 | let expected = vector2!(-200.0, -50.0); 692 | assert_eq!(expected, actual); 693 | } 694 | 695 | #[test] 696 | fn multiply_vector_by_scalar_gives_expected_result() { 697 | let vec = vector2!(-100.0, 50.0); 698 | let scalar = 100.0; 699 | 700 | let actual = vec * scalar; 701 | let expected = vector2!(-10000.0, 5000.0); 702 | assert_eq!(expected, actual); 703 | } 704 | 705 | #[test] 706 | fn multiply_assign_scalar_to_vector_gives_expected_result() { 707 | let mut actual = vector2!(-100.0, 50.0); 708 | actual *= 100.0; 709 | 710 | let expected = vector2!(-10000.0, 5000.0); 711 | assert_eq!(expected, actual); 712 | } 713 | 714 | #[test] 715 | fn divide_vector_by_scalar_gives_expected_result() { 716 | let vec = vector2!(-100.0, 50.0); 717 | let scalar = 100.0; 718 | 719 | let actual = vec / scalar; 720 | let expected = vector2!(-1.0, 0.5); 721 | assert_eq!(expected, actual); 722 | } 723 | 724 | #[test] 725 | fn divide_assign_scalar_to_vector_gives_expected_result() { 726 | let mut actual = vector2!(-100.0, 50.0); 727 | actual /= 100.0; 728 | 729 | let expected = vector2!(-1.0, 0.5); 730 | assert_eq!(expected, actual); 731 | } 732 | 733 | #[test] 734 | fn remainder_vector_by_scalar_gives_expected_result() { 735 | let vec = vector2!(-100.0, 50.0); 736 | let scalar = 100.0; 737 | 738 | let actual = vec % scalar; 739 | let expected = vector2!(0.0, 50.0); 740 | assert_eq!(expected, actual); 741 | } 742 | 743 | #[test] 744 | fn remainder_assign_scalar_to_vector_gives_expected_result() { 745 | let mut actual = vector2!(-100.0, 50.0); 746 | actual %= 100.0; 747 | 748 | let expected = vector2!(0.0, 50.0); 749 | assert_eq!(expected, actual); 750 | } 751 | } 752 | --------------------------------------------------------------------------------