Repository: mitchmindtree/elmesque Branch: master Commit: aef4ec6b478b Files: 14 Total size: 83.4 KB Directory structure: gitextract_r6tt53c7/ ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── ELM_LICENSE.md ├── README.md ├── assets/ │ └── NotoSans/ │ └── LICENSE-2.0.txt ├── examples/ │ └── graphics.rs └── src/ ├── color.rs ├── element.rs ├── form.rs ├── lib.rs ├── text.rs ├── transform_2d.rs └── utils.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # RUST STUFF # Compiled files *.o *.so *.rlib *.dll # Executables *.exe # Generated by Cargo /target/ Cargo.lock # MAC STUFF .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear on external disk .Spotlight-V100 .Trashes ================================================ FILE: .travis.yml ================================================ sudo: false language: rust os: - linux env: global: - secure: W/pxVmgtzNXIQNPOm9lsIjSr2nEHGVD8uOGV0be4kdz0bUXCjFDe1j45VVDnXPoJZDrnv7TO0etn3yT7hpuiZGAT40Ovn7LVq7gqtTAoP2U7vbURN55g0MU9dSIAOUdfclAMZez9HgOHWC0P3Tg6bNkNrW5B5wwpmaFVyYwiQkE= - secure: qlflwsinhvNorlh6l4Hl3tQDytF/LTzlUmw3hA4yj7pwEFUP4BORTvNIlJa+DCft4P4aEU0pgCsC8eb+MQ+q1WOQr2e+EfE+KT/FS9pT6RvqyYUs4QaEznbJkHxMjzkU2N5jf6RGssIEx3ieXD1y+LETxk+KIBFY8DN+wmMYjas= addons: apt: packages: - libxxf86vm-dev - libosmesa6-dev script: - cargo build --verbose - cargo test --verbose - cargo doc --verbose after_success: | [ $TRAVIS_BRANCH = master ] && [ $TRAVIS_PULL_REQUEST = false ] && cargo doc && echo "" > target/doc/index.html && sudo pip install ghp-import && ghp-import -n target/doc && git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages ================================================ FILE: Cargo.toml ================================================ [package] name = "elmesque" version = "0.12.0" authors = ["mitchmindtree "] description = "An attempt at porting Elm's incredibly useful, purely functional std graphics modules." readme = "README.md" keywords = ["elm", "graphics", "2d", "ui", "shape"] license = "MIT" repository = "https://github.com/mitchmindtree/elmesque.git" homepage = "https://github.com/mitchmindtree/elmesque" [dependencies] num = "0.1.27" piston2d-graphics = "0.13.0" rand = "0.3.12" rustc-serialize = "0.3.16" vecmath = "0.2.0" [dev-dependencies] find_folder = "0.3.0" piston = "0.16.0" piston_window = "0.33.0" ================================================ FILE: ELM_LICENSE.md ================================================ Copyright (c) 2013-2015, Evan Czaplicki All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Evan Czaplicki nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: README.md ================================================ # elmesque [![Build Status](https://travis-ci.org/mitchmindtree/elmesque.svg?branch=master)](https://travis-ci.org/mitchmindtree/elmesque) This crate is an attempt at porting Elm's incredibly useful, purely functional std graphics modules. Its useful for all kinds of 2D freeform graphics and UI design. See [the docs](http://mitchmindtree.github.io/elmesque) or checkout [the example](https://github.com/mitchmindtree/elmesque/blob/master/examples/graphics.rs). Visit [elm-lang.org](http://elm-lang.org/) to learn more about Elm. All credit and thanks goes to Evan Czaplicki for all algorithms included within. Ported to Rust by Mitchell Nordine. Usage ----- Add elmesque to your cargo dependencies like so. ```toml [dependencies] elmesque = "*" ``` ================================================ FILE: assets/NotoSans/LICENSE-2.0.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: examples/graphics.rs ================================================ extern crate elmesque; extern crate find_folder; extern crate graphics; extern crate num; extern crate piston; extern crate piston_window; use elmesque::{Form, Renderer}; use piston::input::UpdateEvent; use piston::window::WindowSettings; use piston_window::{PistonWindow, Glyphs}; fn main() { // Construct the window. let window: PistonWindow = WindowSettings::new("Elmesque", [1180, 580]) .exit_on_esc(true) .samples(4) .vsync(true) .build() .unwrap(); // Construct the GlyphCache. let mut glyph_cache = { let assets = find_folder::Search::ParentsThenKids(3, 3).for_folder("assets").unwrap(); let font_path = assets.join("NotoSans/NotoSans-Regular.ttf"); Glyphs::new(&font_path, window.factory.borrow().clone()).unwrap() }; // We'll use this to animate our graphics. let mut secs = 0.0; // Poll events from the window. for event in window { event.draw_2d(|context, g| { let view_dim = context.get_view_size(); let (w, h) = (view_dim[0], view_dim[1]); // Construct the elmesque Renderer with our graphics backend and glyph cache. let mut renderer = Renderer::new(context, g).character_cache(&mut glyph_cache); // Construct some freeform graphics aka a `Form`. let form = elmesque_demo_form(secs); // Convert the form to an `Element` for rendering. let a = elmesque::form::collage(w as i32, h as i32, vec![form]) //.crop((secs / 2.0).sin() * (w / 2.0), (secs / 3.0).sin() * (h / 2.0), 400.0, 400.0) .clear(elmesque::color::black()); a.draw(&mut renderer); }); event.update(|args| secs += args.dt); } } /// Demo of grouping multiple forms into a new single form, transformable at any stage. pub fn elmesque_demo_form(secs: f64) -> Form { use elmesque::color::{blue, dark_blue, light_blue, dark_purple, white}; use elmesque::form::{circle, group, ngon, oval, point_path, rect, solid, text, traced}; use elmesque::text::Text; use elmesque::utils::{degrees}; use num::Float; // Time to get creative! group(vec![ rect(60.0, 40.0).filled(blue()) .shift(secs.sin() * 50.0, secs.cos() * 50.0) .alpha(((secs * 200.0).cos() * 0.5 + 0.5) as f32) .rotate(-secs), rect(100.0, 10.0).filled(dark_blue()) .shift((secs * 5.0).sin() * 200.0, (secs * 5.0).cos() * 200.0) .alpha(((secs * 2.0).cos() * 0.5 + 0.5) as f32) .rotate(-(secs * 5.0)), rect(10.0, 300.0).filled(blue()) .alpha(((secs * 3.0).sin() * 0.25 + 0.75) as f32) .rotate(-(secs * 1.5)), rect(5.0, (secs * 0.1).sin() * 600.0 + 300.0).filled(light_blue()) .alpha(((secs).cos() * 0.25 + 0.75) as f32) .rotate(secs * 0.75), rect(3.0, 2000.0).filled(dark_blue()) .alpha(((secs * 100.0).cos() * 0.5 + 0.25) as f32) .rotate(-(secs * 0.5)), oval(3.0, 2000.0 * (secs * 60.0).sin()).filled(light_blue()) .alpha(((secs * 100.0).cos() * 0.5 + 0.25) as f32) .rotate(-(secs * 0.6)), rect(10.0, 750.0).filled(blue()) .alpha(((secs * 2.0).cos() * 0.5 + 0.25) as f32) .rotate(-(secs * 1.85)), circle((secs * 0.5).sin() * 1500.0).outlined(solid(dark_purple())) .alpha(((secs * 0.2).sin() * 0.25 + 0.35) as f32) .rotate(-(secs * 0.5)), ngon(12, (secs * 0.1).cos() * 100.0 + 300.0).filled(blue()) .alpha((0.25 * secs.cos()) as f32) .rotate(secs * 0.5), ngon(9, (secs * 0.1).cos() * 200.0 + 250.0).outlined(solid(dark_blue())) .alpha(((0.33 * secs).sin() + 0.15) as f32) .rotate(secs * 0.2), rect(300.0, 20.0).filled(light_blue()) .shift((secs * 1.5).cos() * 250.0, (secs * 1.5).sin() * 250.0) .alpha(((secs * 4.5).cos() * 0.25 + 0.35) as f32) .rotate(secs * 1.5 + degrees(90.0)), traced( solid(light_blue()), point_path(vec![(-500.0, 100.0), (0.0, 250.0 * secs.sin()), (500.0, 100.0)]) ).alpha(((secs * 0.2).sin() * 0.25 + 0.35) as f32), traced( solid(blue()), point_path(vec![(-500.0, 0.0), (0.0, 0.0), (500.0, 0.0)]) ).alpha(((secs * 4.5).cos() * 0.25 + 0.35) as f32), traced( solid(dark_blue()), point_path(vec![(-500.0, -100.0), (0.0, -250.0 * secs.sin()), (500.0, -100.0)]) ).alpha(((secs * 0.15).cos() * 0.25 + 0.35) as f32), text(Text::from_string("elmesque".to_string()).color(white())), ]).rotate(degrees(secs.sin() * 360.0)) .scale((secs * 0.05).cos() * 0.2 + 0.9) } ================================================ FILE: src/color.rs ================================================ //! //! A library providing simple `Color` and `Gradient` types along with useful transformations and //! presets. //! //! //! Inspiration taken from [elm-lang's color module] //! (https://github.com/elm-lang/core/blob/62b22218c42fb8ccc996c86bea450a14991ab815/src/Color.elm) //! //! //! Module for working with colors. Includes [RGB](https://en.wikipedia.org/wiki/RGB_color_model) //! and [HSL](http://en.wikipedia.org/wiki/HSL_and_HSV) creation, gradients and built-in names. //! use rustc_serialize::hex::ToHex; use std::ascii::AsciiExt; use std::f32::consts::PI; use utils::{clampf32, degrees, fmod, min, max, turns}; /// Color supporting RGB and HSL variants. #[derive(PartialEq, Copy, Clone, Debug, RustcEncodable, RustcDecodable)] pub enum Color { /// Red, Green, Blue, Alpha - All values' scales represented between 0.0 and 1.0. Rgba(f32, f32, f32, f32), /// Hue, Saturation, Lightness, Alpha - all valuess scales represented between 0.0 and 1.0. Hsla(f32, f32, f32, f32), } /// Regional spelling alias. pub type Colour = Color; /// Create RGB colors with an alpha component for transparency. /// The alpha component is specified with numbers between 0 and 1. #[inline] pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Color { Color::Rgba(r, g, b, a) } /// Create RGB colors from numbers between 0.0 and 1.0. #[inline] pub fn rgb(r: f32, g: f32, b: f32) -> Color { Color::Rgba(r, g, b, 1.0) } /// Create RGB colors from numbers between 0 and 255 inclusive. /// The alpha component is specified with numbers between 0 and 1. #[inline] pub fn rgba_bytes(r: u8, g: u8, b: u8, a: f32) -> Color { Color::Rgba(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, a) } /// Create RGB colors from numbers between 0 and 255 inclusive. #[inline] pub fn rgb_bytes(r: u8, g: u8, b: u8) -> Color { rgba_bytes(r, g, b, 1.0) } /// Create [HSL colors](http://en.wikipedia.org/wiki/HSL_and_HSV) with an alpha component for /// transparency. #[inline] pub fn hsla(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Color { Color::Hsla(hue - turns((hue / (2.0 * PI)).floor()), saturation, lightness, alpha) } /// Create [HSL colors](http://en.wikipedia.org/wiki/HSL_and_HSV). This gives you access to colors /// more like a color wheel, where all hues are arranged in a circle that you specify with radians. /// /// red = hsl(degrees(0.0) , 1.0 , 0.5) /// green = hsl(degrees(120.0) , 1.0 , 0.5) /// blue = hsl(degrees(240.0) , 1.0 , 0.5) /// pastel_red = hsl(degrees(0.0) , 0.7 , 0.7) /// /// To cycle through all colors, just cycle through degrees. The saturation level is how vibrant /// the color is, like a dial between grey and bright colors. The lightness level is a dial between /// white and black. #[inline] pub fn hsl(hue: f32, saturation: f32, lightness: f32) -> Color { hsla(hue, saturation, lightness, 1.0) } /// Produce a gray based on the input. 0.0 is white, 1.0 is black. pub fn grayscale(p: f32) -> Color { Color::Hsla(0.0, 0.0, 1.0-p, 1.0) } /// Produce a gray based on the input. 0.0 is white, 1.0 is black. pub fn greyscale(p: f32) -> Color { Color::Hsla(0.0, 0.0, 1.0-p, 1.0) } /// Construct a random color. pub fn random() -> Color { rgb(::rand::random(), ::rand::random(), ::rand::random()) } impl Color { /// Produce a complementary color. The two colors will accent each other. This is the same as /// rotating the hue by 180 degrees. pub fn complement(self) -> Color { match self { Color::Hsla(h, s, l, a) => hsla(h + degrees(180.0), s, l, a), Color::Rgba(r, g, b, a) => { let (h, s, l) = rgb_to_hsl(r, g, b); hsla(h + degrees(180.0), s, l, a) }, } } /// Calculate and return the luminance of the Color. pub fn luminance(&self) -> f32 { match *self { Color::Rgba(r, g, b, _) => (r + g + b) / 3.0, Color::Hsla(_, _, l, _) => l, } } /// Return either black or white, depending which contrasts the Color the most. This will be /// useful for determining a readable color for text on any given background Color. pub fn plain_contrast(self) -> Color { if self.luminance() > 0.5 { black() } else { white() } } /// Extract the components of a color in the HSL format. pub fn to_hsl(self) -> Hsla { match self { Color::Hsla(h, s, l, a) => Hsla(h, s, l, a), Color::Rgba(r, g, b, a) => { let (h, s, l) = rgb_to_hsl(r, g, b); Hsla(h, s, l, a) }, } } /// Extract the components of a color in the RGB format. pub fn to_rgb(self) -> Rgba { match self { Color::Rgba(r, g, b, a) => Rgba(r, g, b, a), Color::Hsla(h, s, l, a) => { let (r, g, b) = hsl_to_rgb(h, s, l); Rgba(r, g, b, a) }, } } /// Extract the components of a color in the RGB format within a fixed-size array. pub fn to_fsa(self) -> [f32; 4] { let Rgba(r, g, b, a) = self.to_rgb(); [r, g, b, a] } /// Same as `to_fsa`, except r, g, b and a are represented in byte form. pub fn to_byte_fsa(self) -> [u8; 4] { let Rgba(r, g, b, a) = self.to_rgb(); [f32_to_byte(r), f32_to_byte(g), f32_to_byte(b), f32_to_byte(a)] } /// Return the hex representation of this color in the format #RRGGBBAA /// e.g. `Color(1.0, 0.0, 5.0, 1.0) == "#FF0080FF"` pub fn to_hex(self) -> String { let vals = self.to_byte_fsa(); let hex = vals.to_hex().to_ascii_uppercase(); format!("#{}", &hex) } /// Return the same color but with the given luminance. pub fn with_luminance(self, l: f32) -> Color { let Hsla(h, s, _, a) = self.to_hsl(); Color::Hsla(h, s, l, a) } /// Return the same color but with the alpha multiplied by the given alpha. pub fn alpha(self, alpha: f32) -> Color { match self { Color::Rgba(r, g, b, a) => Color::Rgba(r, g, b, a * alpha), Color::Hsla(h, s, l, a) => Color::Hsla(h, s, l, a * alpha), } } /// Return the same color but with the given alpha. pub fn with_alpha(self, a: f32) -> Color { match self { Color::Rgba(r, g, b, _) => Color::Rgba(r, g, b, a), Color::Hsla(h, s, l, _) => Color::Hsla(h, s, l, a), } } /// Return a highlighted version of the current Color. pub fn highlighted(self) -> Color { let luminance = self.luminance(); let Rgba(r, g, b, a) = self.to_rgb(); let (r, g, b) = { if luminance > 0.8 { (r - 0.2, g - 0.2, b - 0.2) } else if luminance < 0.2 { (r + 0.2, g + 0.2, b + 0.2) } else { (clampf32((1.0 - r) * 0.5 * r + r), clampf32((1.0 - g) * 0.1 * g + g), clampf32((1.0 - b) * 0.1 * b + b)) } }; let a = clampf32((1.0 - a) * 0.5 + a); rgba(r, g, b, a) } /// Return a clicked version of the current Color. pub fn clicked(&self) -> Color { let luminance = self.luminance(); let Rgba(r, g, b, a) = self.to_rgb(); let (r, g, b) = { if luminance > 0.8 { (r , g - 0.2, b - 0.2) } else if luminance < 0.2 { (r + 0.4, g + 0.2, b + 0.2) } else { (clampf32((1.0 - r) * 0.75 + r), clampf32((1.0 - g) * 0.25 + g), clampf32((1.0 - b) * 0.25 + b)) } }; let a = clampf32((1.0 - a) * 0.75 + a); rgba(r, g, b, a) } /// Return the Color's invert. pub fn invert(self) -> Color { let Rgba(r, g, b, a) = self.to_rgb(); rgba((r - 1.0).abs(), (g - 1.0).abs(), (b - 1.0).abs(), a) } /// Return the red value. pub fn red(&self) -> f32 { let Rgba(r, _, _, _) = self.to_rgb(); r } /// Return the green value. pub fn green(&self) -> f32 { let Rgba(_, g, _, _) = self.to_rgb(); g } /// Return the blue value. pub fn blue(&self) -> f32 { let Rgba(_, _, b, _) = self.to_rgb(); b } /// Set the red value. pub fn set_red(&mut self, r: f32) { let Rgba(_, g, b, a) = self.to_rgb(); *self = rgba(r, g, b, a); } /// Set the green value. pub fn set_green(&mut self, g: f32) { let Rgba(r, _, b, a) = self.to_rgb(); *self = rgba(r, g, b, a); } /// Set the blue value. pub fn set_blue(&mut self, b: f32) { let Rgba(r, g, _, a) = self.to_rgb(); *self = rgba(r, g, b, a); } } /// The parts of HSL along with an alpha for transparency. #[derive(Copy, Clone, Debug)] pub struct Hsla(pub f32, pub f32, pub f32, pub f32); /// The parts of RGB along with an alpha for transparency. #[derive(Copy, Clone, Debug)] pub struct Rgba(pub f32, pub f32, pub f32, pub f32); /// Convert an f32 color to a byte. #[inline] pub fn f32_to_byte(c: f32) -> u8 { (c * 255.0) as u8 } /// Pure function for converting rgb to hsl. pub fn rgb_to_hsl(r: f32, g: f32, b: f32) -> (f32, f32, f32) { let c_max = max(max(r, g), b); let c_min = min(min(r, g), b); let c = c_max - c_min; let hue = if c == 0.0 { // If there's no difference in the channels we have grayscale, so the hue is undefined. 0.0 } else { degrees(60.0) * if c_max == r { fmod(((g - b) / c), 6) } else if c_max == g { ((b - r) / c) + 2.0 } else { ((r - g) / c) + 4.0 } }; let lightness = (c_max + c_min) / 2.0; let saturation = if lightness == 0.0 { 0.0 } else { c / (1.0 - (2.0 * lightness - 1.0).abs()) }; (hue, saturation, lightness) } /// Pure function for converting hsl to rgb. pub fn hsl_to_rgb(hue: f32, saturation: f32, lightness: f32) -> (f32, f32, f32) { let chroma = (1.0 - (2.0 * lightness - 1.0).abs()) * saturation; let hue = hue / degrees(60.0); let x = chroma * (1.0 - (fmod(hue, 2) - 1.0).abs()); let (r, g, b) = match hue { hue if hue < 0.0 => (0.0, 0.0, 0.0), hue if hue < 1.0 => (chroma, x, 0.0), hue if hue < 2.0 => (x, chroma, 0.0), hue if hue < 3.0 => (0.0, chroma, x), hue if hue < 4.0 => (0.0, x, chroma), hue if hue < 5.0 => (x, 0.0, chroma), hue if hue < 6.0 => (chroma, 0.0, x), _ => (0.0, 0.0, 0.0), }; let m = lightness - chroma / 2.0; (r + m, g + m, b + m) } /// Linear or Radial Gradient. #[derive(Clone, Debug)] pub enum Gradient { /// Takes a start and end point and then a series of color stops that indicate how to /// interpolate between the start and end points. Linear((f64, f64), (f64, f64), Vec<(f64, Color)>), /// First takes a start point and inner radius. Then takes an end point and outer radius. /// It then takes a series of color stops that indicate how to interpolate between the /// inner and outer circles. Radial((f64, f64), f64, (f64, f64), f64, Vec<(f64, Color)>), } /// Create a linear gradient. pub fn linear(start: (f64, f64), end: (f64, f64), colors: Vec<(f64, Color)>) -> Gradient { Gradient::Linear(start, end, colors) } /// Create a radial gradient. pub fn radial(start: (f64, f64), start_r: f64, end: (f64, f64), end_r: f64, colors: Vec<(f64, Color)>) -> Gradient { Gradient::Radial(start, start_r, end, end_r, colors) } /// Built-in colors. /// /// These colors come from the /// [Tango palette](http://tango.freedesktop.org/Tango_Icon_Theme_Guidelines) which provides /// aesthetically reasonable defaults for colors. Each color also comes with a light and dark /// version. /// Scarlet Red - Light - #EF2929 pub fn light_red() -> Color { rgb_bytes(239 , 41 , 41 ) } /// Scarlet Red - Regular - #CC0000 pub fn red() -> Color { rgb_bytes(204 , 0 , 0 ) } /// Scarlet Red - Dark - #A30000 pub fn dark_red() -> Color { rgb_bytes(164 , 0 , 0 ) } /// Orange - Light - #FCAF3E pub fn light_orange() -> Color { rgb_bytes(252 , 175 , 62 ) } /// Orange - Regular - #F57900 pub fn orange() -> Color { rgb_bytes(245 , 121 , 0 ) } /// Orange - Dark - #CE5C00 pub fn dark_orange() -> Color { rgb_bytes(206 , 92 , 0 ) } /// Butter - Light - #FCE94F pub fn light_yellow() -> Color { rgb_bytes(255 , 233 , 79 ) } /// Butter - Regular - #EDD400 pub fn yellow() -> Color { rgb_bytes(237 , 212 , 0 ) } /// Butter - Dark - #C4A000 pub fn dark_yellow() -> Color { rgb_bytes(196 , 160 , 0 ) } /// Chameleon - Light - #8AE234 pub fn light_green() -> Color { rgb_bytes(138 , 226 , 52 ) } /// Chameleon - Regular - #73D216 pub fn green() -> Color { rgb_bytes(115 , 210 , 22 ) } /// Chameleon - Dark - #4E9A06 pub fn dark_green() -> Color { rgb_bytes(78 , 154 , 6 ) } /// Sky Blue - Light - #729FCF pub fn light_blue() -> Color { rgb_bytes(114 , 159 , 207) } /// Sky Blue - Regular - #3465A4 pub fn blue() -> Color { rgb_bytes(52 , 101 , 164) } /// Sky Blue - Dark - #204A87 pub fn dark_blue() -> Color { rgb_bytes(32 , 74 , 135) } /// Plum - Light - #AD7FA8 pub fn light_purple() -> Color { rgb_bytes(173 , 127 , 168) } /// Plum - Regular - #75507B pub fn purple() -> Color { rgb_bytes(117 , 80 , 123) } /// Plum - Dark - #5C3566 pub fn dark_purple() -> Color { rgb_bytes(92 , 53 , 102) } /// Chocolate - Light - #E9B96E pub fn light_brown() -> Color { rgb_bytes(233 , 185 , 110) } /// Chocolate - Regular - #C17D11 pub fn brown() -> Color { rgb_bytes(193 , 125 , 17 ) } /// Chocolate - Dark - #8F5902 pub fn dark_brown() -> Color { rgb_bytes(143 , 89 , 2 ) } /// Straight Black. pub fn black() -> Color { rgb_bytes(0 , 0 , 0 ) } /// Straight White. pub fn white() -> Color { rgb_bytes(255 , 255 , 255) } /// Alluminium - Light pub fn light_gray() -> Color { rgb_bytes(238 , 238 , 236) } /// Alluminium - Regular pub fn gray() -> Color { rgb_bytes(211 , 215 , 207) } /// Alluminium - Dark pub fn dark_gray() -> Color { rgb_bytes(186 , 189 , 182) } /// Aluminium - Light - #EEEEEC pub fn light_grey() -> Color { rgb_bytes(238 , 238 , 236) } /// Aluminium - Regular - #D3D7CF pub fn grey() -> Color { rgb_bytes(211 , 215 , 207) } /// Aluminium - Dark - #BABDB6 pub fn dark_grey() -> Color { rgb_bytes(186 , 189 , 182) } /// Charcoal - Light - #888A85 pub fn light_charcoal() -> Color { rgb_bytes(136 , 138 , 133) } /// Charcoal - Regular - #555753 pub fn charcoal() -> Color { rgb_bytes(85 , 87 , 83 ) } /// Charcoal - Dark - #2E3436 pub fn dark_charcoal() -> Color { rgb_bytes(46 , 52 , 54 ) } /// Types that can be colored. pub trait Colorable: Sized { /// Set the color of the widget. fn color(self, color: Color) -> Self; /// Set the color of the widget from rgba values. fn rgba(self, r: f32, g: f32, b: f32, a: f32) -> Self { self.color(rgba(r, g, b, a)) } /// Set the color of the widget from rgb values. fn rgb(self, r: f32, g: f32, b: f32) -> Self { self.color(rgb(r, g, b)) } /// Set the color of the widget from hsla values. fn hsla(self, h: f32, s: f32, l: f32, a: f32) -> Self { self.color(hsla(h, s, l, a)) } /// Set the color of the widget from hsl values. fn hsl(self, h: f32, s: f32, l: f32) -> Self { self.color(hsl(h, s, l)) } } ================================================ FILE: src/element.rs ================================================ //! //! Ported from [elm-lang's `Graphics.Element` module] //! (https://github.com/elm-lang/core/blob/1.1.1/src/Graphics/Element.elm) //! //! //! Graphical elements that snap together to build complex widgets and layouts. //! //! Each element is a rectangle with a known width and height, making them easy to combine and //! position. //! //! //! # Images //! //! image, fitted_image, cropped_image, tiled_image //! //! //! # Styling //! //! width, height, size, color, opacity //! //! //! # Inspection //! //! width_of, height_of, size_of //! //! //! # Layout //! //! flow, up, down, left, right, inward, outward //! //! ## Layout Aliases //! //! There are some convenience functions for working with `flow` in specific cases: //! //! layers, above, below, beside //! //! //! # Positioning //! empty, spacer, container //! //! ## Specific Positions //! //! To create a `Position` you can use any of the built-in positions which cover nine common //! positions: //! //! middle, mid_top, mid_bottom, mid_left, mid_right, top_left, top_right, bottom_left, //! bottom_right //! //! If you need more precision, you can create custom positions: //! //! absolute, relative, middle_at, mid_top_at, mid_bottom_at, mid_left_at, mid_right_at, //! top_left_at, top_right_at, bottom_left_at, bottom_right_at //! use color::Color; use form::{self, Form}; use graphics::character::CharacterCache; use graphics::{Context, Graphics, Transformed}; use self::Three::{P, Z, N}; use std::path::PathBuf; use transform_2d; /// An Element's Properties. #[derive(Clone, Debug)] pub struct Properties { pub width: i32, pub height: i32, pub opacity: f32, pub crop: Option<(f64, f64, f64, f64)>, pub color: Option, } /// Graphical elements that snap together to build complex widgets and layouts. /// /// Each element is a rectangle with a known width and height, making them easy to combine and /// position. #[derive(Clone, Debug)] pub struct Element { pub props: Properties, pub element: Prim, } impl Element { /// Create an `Element` with a given width. #[inline] pub fn width(self, new_width: i32) -> Element { let Element { props, element } = self; let new_props = match element { Prim::Image(_, w, h, _) | Prim::Collage(w, h, _) => { Properties { height: (h as f32 / w as f32 * new_width as f32).round() as i32, ..props } }, _ => props, }; Element { props: new_props, element: element } } /// Create an `Element` with a given height. #[inline] pub fn height(self, new_height: i32) -> Element { let Element { props, element } = self; let new_props = match element { Prim::Image(_, w, h, _) | Prim::Collage(w, h, _) => { Properties { width: (w as f32 / h as f32 * new_height as f32).round() as i32, ..props } }, _ => props, }; Element { props: new_props, element: element } } /// Create an `Element` with a given size. #[inline] pub fn size(self, new_w: i32, new_h: i32) -> Element { self.height(new_h).width(new_w) } /// Create an `Element` with a given opacity. #[inline] pub fn opacity(mut self, opacity: f32) -> Element { self.props.opacity = opacity; self } /// Create an `Element with a given background color. #[inline] pub fn color(mut self, color: Color) -> Element { self.props.color = Some(color); self } /// Crops an `Element` with the given rectangle. #[inline] pub fn crop(self, x: f64, y: f64, w: f64, h: f64) -> Element { let Element { props, element } = self; let new_props = Properties { crop: Some((x, y, w, h)), ..props }; Element { props: new_props, element: element } } /// Put an element in a container. This lets you position the element really easily, and there are /// tons of ways to set the `Position`. #[inline] pub fn container(self, w: i32, h: i32, pos: Position) -> Element { new_element(w, h, Prim::Container(pos, Box::new(self))) } /// Put an element in a cleared wrapper. The color provided will be the color that clears the /// screen before rendering the contained element. #[inline] pub fn clear(self, color: Color) -> Element { new_element(self.get_width(), self.get_height(), Prim::Cleared(color, Box::new(self))) } /// Stack elements vertically. To put `a` above `b` you would say: `a.above(b)` #[inline] pub fn above(self, other: Element) -> Element { new_element(::std::cmp::max(self.get_width(), other.get_width()), self.get_height() + other.get_height(), Prim::Flow(down(), vec![self, other])) } /// Stack elements vertically. To put `a` below `b` you would say: `a.below(b)` #[inline] pub fn below(self, other: Element) -> Element { other.above(self) } /// Put elements beside each other horizontally. To put `b` to the right of `a` you would say: /// `a.beside(b)` #[inline] pub fn beside(self, other: Element) -> Element { new_element(self.get_width() + other.get_width(), ::std::cmp::max(self.get_height(), other.get_height()), Prim::Flow(right(), vec![self, other])) } /// Return the width of the Element. pub fn get_width(&self) -> i32 { self.props.width } /// Return the height of the Element. pub fn get_height(&self) -> i32 { self.props.height } /// Return the size of the Element's bounding rectangle. pub fn get_size(&self) -> (i32, i32) { (self.props.width, self.props.height) } /// Draw the form with some given graphics backend. #[inline] pub fn draw<'a, C, G>(&self, renderer: &mut Renderer<'a, C, G>) where C: CharacterCache, G: Graphics, { let Renderer { context, ref mut backend, ref mut maybe_character_cache, } = *renderer; let view_size = context.get_view_size(); let context = context.trans(view_size[0] / 2.0, view_size[1] / 2.0).scale(1.0, -1.0); draw_element(self, 1.0, *backend, maybe_character_cache, context); } /// Return whether or not a point is over the element. pub fn is_over(&self, x: i32, y: i32) -> bool { unimplemented!(); } } /// Return the size of the Element. pub fn size_of(e: &Element) -> (i32, i32) { (e.props.width, e.props.height) } /// Construct a new Element from width, height and some Prim. /// Iterates the global GUID counter by one and returns that as the Element id. #[inline] pub fn new_element(w: i32, h: i32, element: Prim) -> Element { Element { props: Properties { width: w, height: h, opacity: 1.0, color: None, crop: None, }, element: element, } } /// Create an empty box. this is useful for getting your spacing right and making borders. pub fn spacer(w: i32, h: i32) -> Element { new_element(w, h, Prim::Spacer) } /// An Element that takes up no space. Good for things that appear conditionally. pub fn empty() -> Element { spacer(0, 0) } /// The various kinds of Elements. #[derive(Clone, Debug)] pub enum Prim { Image(ImageStyle, i32, i32, PathBuf), Container(Position, Box), Flow(Direction, Vec), Collage(i32, i32, Vec
), Cleared(Color, Box), Spacer, } /// Styling for the Image Element. #[derive(Copy, Clone, Debug)] pub enum ImageStyle { Plain, Fitted, Cropped(i32, i32), Tiled, } /// Create an image given a width, height and texture. pub fn image(w: i32, h: i32, path: PathBuf) -> Element { new_element(w, h, Prim::Image(ImageStyle::Plain, w, h, path)) } /// Create a fitted image given a width, height and texture. This will crop the picture to best /// fill the given dimensions. pub fn fitted_image(w: i32, h: i32, path: PathBuf) -> Element { new_element(w, h, Prim::Image(ImageStyle::Fitted, w, h, path)) } /// Create a cropped image. Take a rectangle out of the picture starting at the given top left /// coordinate. pub fn cropped_image(x: i32, y: i32, w: i32, h: i32, path: PathBuf) -> Element { new_element(w, h, Prim::Image(ImageStyle::Cropped(x, y), w, h, path)) } /// Create a tiled image given a width, height and texture. pub fn tiled_image(w: i32, h: i32, path: PathBuf) -> Element { new_element(w, h, Prim::Image(ImageStyle::Tiled, w, h, path)) } #[derive(Copy, Clone, Debug)] pub enum Three { P, Z, N } #[derive(Copy, Clone, Debug)] pub enum Pos { Absolute(i32), Relative(f32) } /// An element's Position. #[derive(Copy, Clone, Debug)] pub struct Position { horizontal: Three, vertical: Three, x: Pos, y: Pos, } /// The direction for a flow of `Element`s. #[derive(Copy, Clone, Debug)] pub enum Direction { Up, Down, Left, Right, In, Out } /// Have a list of elements flow in a particular direction. The `Direction` starts from the first /// element in the list. The result is an `Element`. pub fn flow(dir: Direction, elements: Vec) -> Element { if elements.is_empty() { return empty() } let max_w = elements.iter().map(|e| e.get_width()).max().unwrap(); let max_h = elements.iter().map(|e| e.get_height()).max().unwrap(); let sum_w = elements.iter().fold(0, |total, e| total + e.get_width()); let sum_h = elements.iter().fold(0, |total, e| total + e.get_height()); let new_flow = |w: i32, h: i32| new_element(w, h, Prim::Flow(dir, elements)); match dir { Direction::Up | Direction::Down => new_flow(max_w, sum_h), Direction::Left | Direction::Right => new_flow(sum_w, max_h), Direction::In | Direction::Out => new_flow(max_w, max_h), } } /// Layer elements on top of each other, starting from the bottom. pub fn layers(elements: Vec) -> Element { let max_w = elements.iter().map(|e| e.get_width()).max().unwrap_or(0); let max_h = elements.iter().map(|e| e.get_height()).max().unwrap_or(0); new_element(max_w, max_h, Prim::Flow(outward(), elements)) } /// Repetitive things. pub fn absolute(i: i32) -> Pos { Pos::Absolute(i) } pub fn relative(f: f32) -> Pos { Pos::Relative(f) } #[inline] fn p(h: Three, v: Three, x: Pos, y: Pos) -> Position { Position { horizontal: h, vertical: v, x: x, y: y } } pub fn middle() -> Position { p(Z, Z, relative(0.5), relative(0.5)) } pub fn top_left() -> Position { p(N, P, absolute(0), absolute(0)) } pub fn top_right() -> Position { p(P, P, absolute(0), absolute(0)) } pub fn bottom_left() -> Position { p(N, N, absolute(0), absolute(0)) } pub fn bottom_right() -> Position { p(P, N, absolute(0), absolute(0)) } pub fn mid_left() -> Position { p(N, Z, absolute(0), relative(0.5)) } pub fn mid_right() -> Position { p(P, Z, absolute(0), relative(0.5)) } pub fn mid_top() -> Position { p(Z, P, relative(0.5), absolute(0)) } pub fn mid_bottom() -> Position { p(Z, N, relative(0.5), absolute(0)) } pub fn middle_at(x: Pos, y: Pos) -> Position { p(Z, Z, x, y) } pub fn top_left_at(x: Pos, y: Pos) -> Position { p(N, P, x, y) } pub fn top_right_at(x: Pos, y: Pos) -> Position { p(P, P, x, y) } pub fn bottom_left_at(x: Pos, y: Pos) -> Position { p(N, N, x, y) } pub fn bottom_right_at(x: Pos, y: Pos) -> Position { p(P, N, x, y) } pub fn mid_left_at(x: Pos, y: Pos) -> Position { p(N, Z, x, y) } pub fn mid_right_at(x: Pos, y: Pos) -> Position { p(P, Z, x, y) } pub fn mid_top_at(x: Pos, y: Pos) -> Position { p(Z, P, x, y) } pub fn mid_bottom_at(x: Pos, y: Pos) -> Position { p(Z, N, x, y) } pub fn up() -> Direction { Direction::Up } pub fn down() -> Direction { Direction::Down } pub fn left() -> Direction { Direction::Left } pub fn right() -> Direction { Direction::Right } pub fn inward() -> Direction { Direction::In } pub fn outward() -> Direction { Direction::Out } /// /// CUSTOM NON-ELM FUNCTIONS /// /// Used for rendering elmesque `Element`s. pub struct Renderer<'a, C: 'a, G: 'a> { context: Context, backend: &'a mut G, maybe_character_cache: Option<&'a mut C>, } impl<'a, C, G> Renderer<'a, C, G> { /// Construct a renderer, used for rendering elmesque `Element`s. pub fn new(context: Context, backend: &'a mut G) -> Renderer<'a, C, G> { Renderer { context: context, backend: backend, maybe_character_cache: None, } } /// Builder method for constructing a Renderer with a GlyphCache for drawing text. pub fn character_cache(self, character_cache: &'a mut C) -> Renderer<'a, C, G> { Renderer { maybe_character_cache: Some(character_cache), ..self } } } /// Draw an Element. pub fn draw_element<'a, C: CharacterCache, G: Graphics>( element: &Element, opacity: f32, backend: &mut G, maybe_character_cache: &mut Option<&mut C>, context: Context, ) { let Element { ref props, ref element } = *element; // Crop the Element if some crop was given. // We'll use the `DrawState::scissor` method for this. // // Because `DrawState`'s `scissor` `Rect` uses bottom-left origin coords, we'll have to convert // from our centered-origin coordinate system. // // We'll also need to stretch our coords to match the correct viewport.draw_size. let context = match props.crop { Some((x, y, w, h)) => { use utils::{clamp, map_range}; let Context { draw_state, .. } = context; // Our view_dim is our virtual window size which is consistent no matter the display. let view_dim = context.get_view_size(); // Our draw_dim is the actual window size in pixels. Our target crop area must be // represented in this size. let draw_dim = match context.viewport { Some(viewport) => [viewport.draw_size[0] as f64, viewport.draw_size[1] as f64], None => view_dim, }; // Calculate the distance to the edges of the window from the center. let left = -view_dim[0] / 2.0; let right = view_dim[0] / 2.0; let bottom = -view_dim[1] / 2.0; let top = view_dim[1] / 2.0; // We start with the x and y in the center of our crop area, however we need it to be // at the top left of the crop area. let left_x = x - w as f64 / 2.0; let top_y = y - h as f64 / 2.0; // Map the position at the top left of the crop area in view_dim to our draw_dim. let x = map_range(left_x, left, right, 0, draw_dim[0] as i32); let y = map_range(top_y, bottom, top, 0, draw_dim[1] as i32); // Convert the w and h from our view_dim to the draw_dim. let w_scale = draw_dim[0] / view_dim[0]; let h_scale = draw_dim[1] / view_dim[1]; let w = w * w_scale; let h = h * h_scale; // If we ended up with negative coords for the crop area, we'll use 0 instead as we // can't represent the negative coords with `u16` (the target DrawState dimension type). // We'll hold onto the lost negative values (x_neg and y_neg) so that we can compensate // with the width and height. let x_neg = if x < 0 { x } else { 0 }; let y_neg = if y < 0 { y } else { 0 }; let mut x = ::std::cmp::max(0, x) as u16; let mut y = ::std::cmp::max(0, y) as u16; let mut w = ::std::cmp::max(0, (w as i32 + x_neg)) as u16; let mut h = ::std::cmp::max(0, (h as i32 + y_neg)) as u16; // If there was already some scissor set, we must check for the intersection. if let Some(rect) = draw_state.scissor { if x + w < rect.x || rect.x + rect.w < x || y + h < rect.y || rect.y + rect.h < y { // If there is no intersection, we have no scissor. w = 0; h = 0; } else { // If there is some intersection, calculate the overlapping rect. let (a_l, a_r, a_b, a_t) = (x, x+w, y, y+h); let (b_l, b_r, b_b, b_t) = (rect.x, rect.x+rect.w, rect.y, rect.y+rect.h); let l = if a_l > b_l { a_l } else { b_l }; let r = if a_r < b_r { a_r } else { b_r }; let b = if a_b > b_b { a_b } else { b_b }; let t = if a_t < b_t { a_t } else { b_t }; x = l; y = b; w = r - l; h = t - b; } } Context { draw_state: draw_state.scissor(x, y, w, h), ..context } }, None => context, }; match *element { Prim::Image(style, w, h, ref path) => { let Properties { width, height, opacity, color, .. } = *props; match style { ImageStyle::Plain => { // let image = graphics::Image { // color: None, // rectangle: None, // source_rectangle: Some([src_x, src_y, w, h]), // }; // let image = Image::new(); // let texture: &Texture = ::std::ops::Deref::deref(&texture); // image.draw(texture, draw_state, matrix, backend); unimplemented!(); }, ImageStyle::Fitted => { unimplemented!(); }, ImageStyle::Cropped(x, y) => { unimplemented!(); }, ImageStyle::Tiled => { unimplemented!(); }, } }, Prim::Container(position, ref element) => { let Position { horizontal, vertical, x, y } = position; let context = match (x, y) { (Pos::Relative(x), Pos::Relative(y)) => context.trans(x as f64, y as f64), (Pos::Absolute(x), Pos::Relative(y)) => Context { transform: transform_2d::matrix(1.0, 0.0, 0.0, 1.0, x as f64, 0.0).0, ..context }.trans(0.0, y as f64), (Pos::Relative(x), Pos::Absolute(y)) => Context { transform: transform_2d::matrix(1.0, 0.0, 0.0, 1.0, 0.0, y as f64).0, ..context }.trans(x as f64, 0.0), (Pos::Absolute(x), Pos::Absolute(y)) => Context { transform: transform_2d::matrix(1.0, 0.0, 0.0, 1.0, x as f64, y as f64).0, ..context }, }; let new_opacity = opacity * props.opacity; draw_element(element, new_opacity, backend, maybe_character_cache, context); } Prim::Flow(direction, ref elements) => { let mut context = context; match direction { Direction::Up | Direction::Down => { let multi = if let Direction::Up = direction { 1.0 } else { -1.0 }; let mut half_prev_height = 0.0; for element in elements.iter() { let half_height = element.get_height() as f64 / 2.0; let new_opacity = opacity * props.opacity; draw_element(element, new_opacity, backend, maybe_character_cache, context); let y_trans = half_height + half_prev_height; context = context.trans(0.0, y_trans * multi); half_prev_height = half_height; } }, Direction::Left | Direction::Right => { let multi = if let Direction::Right = direction { 1.0 } else { -1.0 }; let mut half_prev_width = 0.0; for element in elements.iter() { let half_width = element.get_width() as f64 / 2.0; let new_opacity = opacity * props.opacity; draw_element(element, new_opacity, backend, maybe_character_cache, context); let x_trans = half_width + half_prev_width; context = context.trans(x_trans * multi, 0.0); half_prev_width = half_width; } }, Direction::Out => { for element in elements.iter() { let new_opacity = opacity * props.opacity; draw_element(element, new_opacity, backend, maybe_character_cache, context); } } Direction::In => { for element in elements.iter().rev() { let new_opacity = opacity * props.opacity; draw_element(element, new_opacity, backend, maybe_character_cache, context); } } } }, Prim::Collage(w, h, ref forms) => { for form in forms.iter() { let new_opacity = opacity * props.opacity; form::draw_form(form, new_opacity, backend, maybe_character_cache, context); } }, Prim::Cleared(color, ref element) => { backend.clear_color(color.to_fsa()); draw_element(element, opacity, backend, maybe_character_cache, context); }, Prim::Spacer => {}, } } ================================================ FILE: src/form.rs ================================================ //! //! Ported from [elm-lang's `Graphics.Collage` module] //! (https://github.com/elm-lang/core/blob/62b22218c42fb8ccc996c86bea450a14991ab815/src/Graphics/Collage.elm). //! //! //! This module is for freeform graphics. You can shift, rotate, scale etc. All sorts of forms //! including lines, shapes, images and elements. //! //! Collages use the same coordinate system you might see in an algebra or physics problem. The //! origin (0, 0) is at the center of the collage, not the top left corner as in some other //! graphics libraries. Furthermore, the y-axis points up, so shifting a form 10 units in the //! y-axis will move it up screen. //! //! # Creating Forms //! to_form, filled, textured, gradient, outlined, traced, text, outlined_text //! //! # Transforming Forms //! shift, shift_x, shift_y, scale, rotate, alpha //! //! # Grouping Forms //! Grouping forms makes it easier to write modular graphics code. You can create a form that is a //! composite of many subforms. From there it is easy to transform it as a single unit. //! group, group_transform //! //! # Shapes //! rect, oval, square, circle, ngon, polygon //! //! # Paths //! segment, path //! //! # Line Styles //! solid, dashed, dotted, LineStyle, LineCap, LineJoin //! use color::{Color, Gradient}; use element::{self, Element, new_element}; use graphics::{self, Context, Graphics, Transformed}; use graphics::character::CharacterCache; use std::f64::consts::PI; use std::path::PathBuf; use text::Text; use transform_2d::{self, Transform2D}; /// A general, freeform 2D graphics structure. #[derive(Clone, Debug)] pub struct Form { pub theta: f64, pub scale: f64, pub x: f64, pub y: f64, pub alpha: f32, pub form: BasicForm, } #[derive(Clone, Debug)] pub enum FillStyle { Solid(Color), Texture(PathBuf), Grad(Gradient), } #[derive(Copy, Clone, Debug)] pub enum LineCap { Flat, Round, Padded, } #[derive(Copy, Clone, Debug)] pub enum LineJoin { Smooth, Sharp(f64), Clipped, } #[derive(Clone, Debug)] pub struct LineStyle { pub color: Color, pub width: f64, pub cap: LineCap, pub join: LineJoin, pub dashing: Vec, pub dash_offset: i64, } impl LineStyle { /// The default LineStyle. pub fn default() -> LineStyle { LineStyle { color: ::color::black(), width: 1.0, cap: LineCap::Flat, join: LineJoin::Sharp(10.0), dashing: Vec::new(), dash_offset: 0, } } /// The LineStyle with some given width. #[inline] pub fn width(self, w: f64) -> LineStyle { LineStyle { width: w, ..self } } } /// Create a solid line style with a given color. pub fn solid(color: Color) -> LineStyle { LineStyle { color: color, ..LineStyle::default() } } /// Create a dashed line style with a given color. Dashing equals `[8, 4]`. pub fn dashed(color: Color) -> LineStyle { LineStyle { color: color, dashing: vec![8, 4], ..LineStyle::default() } } /// Create a dotted line style with a given color. Dashing equals `[3, 3]`. pub fn dotted(color: Color) -> LineStyle { LineStyle { color: color, dashing: vec![3, 3], ..LineStyle::default() } } /// The basic variants a Form can consist of. #[derive(Clone, Debug)] pub enum BasicForm { PointPath(LineStyle, PointPath), Shape(ShapeStyle, Shape), OutlinedText(LineStyle, Text), Text(Text), Image(i32, i32, (i32, i32), PathBuf), Element(Element), Group(Transform2D, Vec), } /// Whether a shape is outlined or filled. #[derive(Clone, Debug)] pub enum ShapeStyle { Line(LineStyle), Fill(FillStyle), } impl Form { fn new(basic_form: BasicForm) -> Form { Form { theta: 0.0, scale: 1.0, x: 0.0, y: 0.0, alpha: 1.0, form: basic_form, } } /// Move a form by the given amount. this is a relative translation so `shift(10.0, 10.0, form) /// would move `form` ten pixels up and ten pixels to the right. #[inline] pub fn shift(self, x: f64, y: f64) -> Form { Form { x: self.x + x, y: self.y + y, ..self } } /// Move a shape in the x direction. This is relative so `shift_x(10.0, form)` moves `form` 10 /// pixels to the right. #[inline] pub fn shift_x(self, x: f64) -> Form { self.shift(x, 0.0) } /// Move a shape in the y direction. This is relative so `shift_y(10.0, form)` moves `form /// upwards by 10 pixels. #[inline] pub fn shift_y(self, y: f64) -> Form { self.shift(0.0, y) } /// Scale a form by a given factor. Scaling by 2 doubles both dimensions and quadruples the /// area. #[inline] pub fn scale(self, scale: f64) -> Form { Form { scale: self.scale * scale, ..self } } /// Rotate a form by a given angle. Rotate takes radians and turns things counterclockwise. /// So to turn `form` 30 degrees to the left you would say `rotate(degrees(30), form)`. #[inline] pub fn rotate(self, theta: f64) -> Form { Form { theta: self.theta + theta, ..self } } /// Set the alpha of a Form. The default is 1 and 0 is totally transparent. #[inline] pub fn alpha(self, alpha: f32) -> Form { Form { alpha: alpha, ..self } } } /// Turn any `Element` into a `Form`. This lets you use text, gifs, and video in your collage. This /// means you can move, rotate, and scale an `Element` however you want. pub fn to_form(element: Element) -> Form { Form::new(BasicForm::Element(element)) } /// Flatten many forms into a single `Form`. This lets you move and rotate them as a single unit, /// making it possible to build small, modular components. pub fn group(forms: Vec) -> Form { Form::new(BasicForm::Group(transform_2d::identity(), forms)) } /// Flatten many forms into a single `Form` and then apply a matrix transformation. pub fn group_transform(matrix: Transform2D, forms: Vec) -> Form { Form::new(BasicForm::Group(matrix, forms)) } /// Trace a path with a given line style. pub fn traced(style: LineStyle, path: PointPath) -> Form { Form::new(BasicForm::PointPath(style, path)) } /// Create a line with a given line style. pub fn line(style: LineStyle, x1: f64, y1: f64, x2: f64, y2: f64) -> Form { traced(style, segment((x1, y1), (x2, y2))) } /// Create a sprite from a sprite sheet. It cuts out a rectangle at a given position. pub fn sprite(w: i32, h: i32, pos: (i32, i32), path: PathBuf) -> Form { Form::new(BasicForm::Image(w, h, pos, path)) } /// A collage is a collection of 2D forms. There are no strict positioning relationships between /// forms, so you are free to do all kinds of 2D graphics. pub fn collage(w: i32, h: i32, forms: Vec) -> Element { new_element(w, h, element::Prim::Collage(w, h, forms)) } /// A path described by a sequence of points. #[derive(Clone, Debug)] pub struct PointPath(pub Vec<(f64, f64)>); /// Create a PointPath that follows a sequence of points. pub fn point_path(points: Vec<(f64, f64)>) -> PointPath { PointPath(points) } /// Create a PointPath along a given line segment. pub fn segment(a: (f64, f64), b: (f64, f64)) -> PointPath { PointPath(vec![a, b]) } /// A shape described by its edges. #[derive(Clone, Debug)] pub struct Shape(pub Vec<(f64, f64)>); impl Shape { #[inline] fn fill(self, style: FillStyle) -> Form { Form::new(BasicForm::Shape(ShapeStyle::Fill(style), self)) } /// Create a filled-in shape. #[inline] pub fn filled(self, color: Color) -> Form { self.fill(FillStyle::Solid(color)) } /// Create a textured shape. /// The texture is described by some path and is tiled to fill the entire shape. #[inline] pub fn textured(self, path: PathBuf) -> Form { self.fill(FillStyle::Texture(path)) } /// Fill a shape with a gradient. #[inline] pub fn gradient(self, grad: Gradient) -> Form { self.fill(FillStyle::Grad(grad)) } /// Outline a shape with a given line style. #[inline] pub fn outlined(self, style: LineStyle) -> Form { Form::new(BasicForm::Shape(ShapeStyle::Line(style), self)) } } /// Create an arbitrary polygon by specifying its corners in order. `polygon` will automatically /// close all shapes, so the given list of points does not need to start and end with the same /// position. pub fn polygon(points: Vec<(f64, f64)>) -> Shape { Shape(points) } /// A rectangle with a given width and height. pub fn rect(w: f64, h: f64) -> Shape { let hw = w / 2.0; let hh = h / 2.0; Shape(vec![ (0.0-hw, 0.0-hh), (0.0-hw, hh), (hw, hh), (hw, 0.0-hh) ]) } /// A square with a given edge length. pub fn square(n: f64) -> Shape { rect(n, n) } /// An oval with a given width and height. pub fn oval(w: f64, h: f64) -> Shape { let n: usize = 50; let t = 2.0 * PI / n as f64; let hw = w / 2.0; let hh = h / 2.0; let f = |i: f64| (hw * (t*i).cos(), hh * (t*i).sin()); let points = (0..n-1).map(|i| f(i as f64)).collect(); Shape(points) } /// A circle with a given radius. pub fn circle(r: f64) -> Shape { let d = 2.0 * r; oval(d, d) } /// A regular polygon with N sides. The first argument specifies the number of sides and the second /// is the radius. So to create a pentagon with radius 30, you would say `ngon(5, 30.0)` pub fn ngon(n: usize, r: f64) -> Shape { let t = 2.0 * PI / n as f64; let f = |i: f64| (r * (t*i).cos(), r * (t*i).sin()); let points = (0..n).map(|i| f(i as f64)).collect(); Shape(points) } /// Create some text. Details like size and color are part of the `Text` value itself, so you can /// mix colors and sizes and fonts easily. pub fn text(t: Text) -> Form { Form::new(BasicForm::Text(t)) } /// /// CUSTOM NON-ELM FUNCTIONS. /// /// Normally Elm renders to html and javascript, however the aim of elmesque is to render to GL. /// /// This function draws a form with some given transform using the generic [Piston graphics] /// (https://github.com/PistonDevelopers/graphics) backend. pub fn draw_form<'a, C: CharacterCache, G: Graphics>( form: &Form, alpha: f32, backend: &mut G, maybe_character_cache: &mut Option<&mut C>, context: Context, ) { let Form { theta, scale, x, y, alpha, ref form } = *form; let context = context.trans(x, y).scale(scale, scale).rot_rad(theta); match *form { BasicForm::PointPath(ref line_style, PointPath(ref points)) => { // NOTE: join, dashing and dash_offset are not yet handled properly. let LineStyle { color, width, cap, join, ref dashing, dash_offset } = *line_style; let color = convert_color(color, alpha); let mut draw_line = |(x1, y1), (x2, y2)| { if dashing.is_empty() { let line = match cap { LineCap::Flat => graphics::Line::new(color, width / 2.0), LineCap::Round => graphics::Line::new_round(color, width / 2.0), LineCap::Padded => unimplemented!(), }; line.draw([x1, y1, x2, y2], &context.draw_state, context.transform, backend); } else { unimplemented!(); } }; for window in points.windows(2) { let (a, b) = (window[0], window[1]); draw_line(a, b); } }, BasicForm::Shape(ref shape_style, Shape(ref points)) => { match *shape_style { ShapeStyle::Line(ref line_style) => { // NOTE: join, dashing and dash_offset are not yet handled properly. let LineStyle { color, width, cap, join, ref dashing, dash_offset } = *line_style; let color = convert_color(color, alpha); let mut draw_line = |(x1, y1), (x2, y2)| { let line = match cap { LineCap::Flat => graphics::Line::new(color, width / 2.0), LineCap::Round => graphics::Line::new_round(color, width / 2.0), LineCap::Padded => unimplemented!(), }; line.draw([x1, y1, x2, y2], &context.draw_state, context.transform, backend); }; for window in points.windows(2) { let (a, b) = (window[0], window[1]); draw_line(a, b); } if points.len() > 2 { draw_line(points[points.len()-1], points[0]) } }, ShapeStyle::Fill(ref fill_style) => match *fill_style { FillStyle::Solid(color) => { let color = convert_color(color, alpha); let polygon = graphics::Polygon::new(color); let points: Vec<_> = points.iter().map(|&(x, y)| [x, y]).collect(); polygon.draw(&points[..], &context.draw_state, context.transform, backend); }, FillStyle::Texture(ref path) => { unimplemented!(); }, FillStyle::Grad(ref gradient) => { unimplemented!(); }, }, } }, BasicForm::OutlinedText(ref line_style, ref text) => { unimplemented!(); }, BasicForm::Text(ref text) => { let context = context.scale(1.0, -1.0); if let Some(ref mut character_cache) = *maybe_character_cache { use text::Style as TextStyle; use text::Position as TextPosition; use text::TextUnit; let (total_width, max_height) = text.sequence.iter().fold((0.0, 0.0), |(w, h), unit| { let TextUnit { ref string, ref style } = *unit; let TextStyle { ref typeface, height, color, bold, italic, line, monospace } = *style; let height = height.unwrap_or(16.0); let new_total_width = w + character_cache.width(height as u32, &string); let new_max_height = if height > h { height } else { h }; (new_total_width, new_max_height) }); let x_offset = match text.position { TextPosition::Center => -(total_width / 2.0).floor(), TextPosition::ToLeft => -total_width.floor(), TextPosition::ToRight => 0.0 }; let y_offset = (max_height / 3.0).floor(); // TODO: FIX THIS (3.0) let context = context.trans(x_offset, y_offset); for unit in text.sequence.iter() { let TextUnit { ref string, ref style } = *unit; let TextStyle { ref typeface, height, color, bold, italic, line, monospace } = *style; let height = height.unwrap_or(16.0).floor(); let color = convert_color(color, alpha); graphics::text::Text::new_color(color, height as u32) .round() .draw(&string[..], *character_cache, &context.draw_state, context.transform, backend); } } }, BasicForm::Image(src_x, src_y, (w, h), ref path) => { // let image = graphics::Image { // color: None, // rectangle: None, // source_rectangle: Some([src_x, src_y, w, h]), // }; // let texture: &Texture = ::std::ops::Deref::deref(&texture); // image.draw(texture, draw_state, matrix, backend); unimplemented!(); }, BasicForm::Group(ref group_transform, ref forms) => { let Transform2D(matrix) = Transform2D(context.transform.clone()) .multiply(group_transform.clone()); let context = Context { transform: matrix, ..context }; for form in forms.iter() { draw_form(form, alpha, backend, maybe_character_cache, context); } }, BasicForm::Element(ref element) => element::draw_element(element, alpha, backend, maybe_character_cache, context), } } /// Convert an elmesque color to a piston-graphics color. fn convert_color(color: Color, alpha: f32) -> [f32; 4] { use color::hsl_to_rgb; let ((r, g, b), a) = match color { Color::Hsla(h, s, l, a) => (hsl_to_rgb(h, s, l), a), Color::Rgba(r, g, b, a) => ((r, g, b), a), }; [r, g, b, a * alpha] } ================================================ FILE: src/lib.rs ================================================ //! //! This crate is an attempt at porting Elm's incredibly useful std graphics modules. //! //! Visit [elm-lang.org](http://elm-lang.org/). //! //! //! All credit goes to Evan Czaplicki for all algorithms included within. //! //! Ported to Rust by Mitchell Nordine. //! extern crate graphics; extern crate num; extern crate rand; extern crate rustc_serialize; extern crate vecmath; pub use color as colour; pub use element::{Element, Renderer}; pub use form::{Form}; pub mod color; pub mod element; pub mod form; pub mod text; pub mod transform_2d; pub mod utils; ================================================ FILE: src/text.rs ================================================ use color::{black, Color}; use std::path::PathBuf; /// Drawable Text. #[derive(Clone, Debug)] pub struct Text { pub sequence: Vec, pub position: Position, } #[derive(Clone, Debug)] pub struct TextUnit { pub string: String, pub style: Style, } /// Styles for lines on text. This allows you to add an underline, an overline, or strike out text. #[derive(Copy, Clone, Debug)] pub enum Line { Under, Over, Through, } /// Text position relative to center point #[derive(Copy, Clone, Debug)] pub enum Position { Center, ToLeft, ToRight } /// Represents all the ways you can style `Text`. If the `type_face` list is empty or the `height` /// is `None`, the users will fall back on their default settings. The following `Style` is black, /// 16 pixel tall, underlined, and Times New Roman (assuming that typeface is available on the /// user's computer): /// /// Style { /// type_face: Some("Times New Roman"), /// height: Some(16), /// color: black(), /// bold: false, /// italic: false, /// line: Some(Line::Under), /// } /// #[derive(Clone, Debug)] pub struct Style { pub typeface: Option, pub height: Option, pub color: Color, pub bold: bool, pub italic: bool, pub line: Option, pub monospace: bool, } impl Style { pub fn default() -> Style { Style { typeface: None, height: None, color: black(), bold: false, italic: false, line: None, monospace: false, } } } impl Text { /// Convert a string into text which can be styled and displayed. pub fn from_string(string: String) -> Text { Text { sequence: vec![TextUnit { string: string, style: Style::default(), }], position: Position::Center } } /// Text with nothing in it. pub fn empty() -> Text { Text::from_string("".to_string()) } /// Put two chunks of text together. #[inline] pub fn append(mut self, other: Text) -> Text { self.sequence.extend(other.sequence.into_iter()); self } /// Put many chunks of text together. pub fn concat(texts: Vec) -> Text { let position = texts.get(0).map(|t| t.position).unwrap_or(Position::Center); Text { sequence: texts.into_iter() .flat_map(|Text { sequence, position }| sequence.into_iter()) .collect(), position: position } } /// Put many chunks of text together with a separator. pub fn join(separator: Text, texts: Vec) -> Text { texts.into_iter().fold(Text::empty(), |texts, text| { texts.append(text).append(separator.clone()) }) } /// Set the style of some text. For example, if you design a `Style` called `foorter_style` that is /// specifically for the bottom of your page, you could apply it to text like this: /// /// style(footer_style, from_string("the old prince / 2007")) /// #[inline] pub fn style(self, style: Style) -> Text { let string = String::from_utf8(self.sequence.into_iter().flat_map(|unit| { unit.string.into_bytes().into_iter() }).collect()).unwrap(); Text { sequence: vec![TextUnit { string: string, style: style }], ..self } } /// Provide a path of a typeface to be used for some text. #[inline] pub fn typeface(mut self, path: PathBuf) -> Text { for unit in self.sequence.iter_mut() { unit.style.typeface = Some(path.clone()); } self } /// Switch to a monospace typeface. Good for code snippets. /// /// monospace(from_string("(0..3).fold(0, |a, b| a + b)")) /// #[inline] pub fn monospace(mut self) -> Text { for unit in self.sequence.iter_mut() { unit.style.monospace = true; } self } /// Set the height of some text in pixels. #[inline] pub fn height(mut self, h: f64) -> Text { for unit in self.sequence.iter_mut() { unit.style.height = Some(h); } self } /// Set the color of some text. #[inline] pub fn color(mut self, color: Color) -> Text { for unit in self.sequence.iter_mut() { unit.style.color = color; } self } /// Make the text bold. #[inline] pub fn bold(mut self) -> Text { for unit in self.sequence.iter_mut() { unit.style.bold = true; } self } /// Make the text italic. #[inline] pub fn italic(mut self) -> Text { for unit in self.sequence.iter_mut() { unit.style.italic = true; } self } /// Put lines on text. #[inline] pub fn line(mut self, line: Line) -> Text { for unit in self.sequence.iter_mut() { unit.style.line = Some(line); } self } /// Change the text position relative to it's center point #[inline] pub fn position(mut self, position: Position) -> Text { self.position = position; self } } ================================================ FILE: src/transform_2d.rs ================================================ //! //! Ported from [elm-lang's Transform2D module] //! (https://github.com/elm-lang/core/blob/62b22218c42fb8ccc996c86bea450a14991ab815/src/Transform2D.elm) //! //! //! A library for performing 2D matrix transformations. It is used primarily with the //! `group_transform` function from the `form` module and allows you to do things like rotation, //! scaling, translation, shearing and reflection. //! //! Note that all the matrices in this library are 3*3 matrices of homogeneous coordinates, used //! for affine transformations. Since the bottom row is always `0 0 1` in these matrices, it is //! omitted in the diagrams below. //! use vecmath::{mat2x3_id, Matrix2x3, row_mat2x3_mul}; pub type Matrix2d = Matrix2x3; /// Represents a 2D transform. #[derive(Clone, Debug)] pub struct Transform2D(pub Matrix2d); impl Transform2D { /// Multiply two transforms together. /// /// ma mb mx na nb nx /// mc md my . nc nd ny /// 0 0 1 0 0 1 /// #[inline] pub fn multiply(self, other: Transform2D) -> Transform2D { let (Transform2D(m), Transform2D(n)) = (self, other); Transform2D(row_mat2x3_mul(m, n)) } } /// Create an identity transform. Transforming by the identity does not change anything, but it can /// come in handy as a default or base case. /// /// 1 0 0 /// 0 1 0 /// #[inline] pub fn identity() -> Transform2D { Transform2D(mat2x3_id()) } /// Creates a transformation matrix. This lets you create transforms such as scales, shears, /// reflections and translations. /// /// a b x /// c d y /// #[inline] pub fn matrix(a: f64, b: f64, c: f64, d: f64, x: f64, y: f64) -> Transform2D { Transform2D([ [a, b, x], [c, d, y] ]) } /// Create a [rotation matrix](http://en.wikipedia.org/wiki/Rotation_matrix). Given an angle t, it /// creates a counterclockwise rotation matrix. /// /// cos t -sin t 0 /// sin t cos t 0 /// #[inline] pub fn rotation(t: f64) -> Transform2D { Transform2D([ [t.cos(), -t.sin(), 0.0], [t.sin(), t.cos(), 0.0] ]) } /// Creates a transformation matrix for translation. /// /// 1 0 x /// 0 1 y /// #[inline] pub fn translation(x: f64, y: f64) -> Transform2D { matrix(1.0, 0.0, 0.0, 1.0, x, y) } /// Creates a transformation matrix for scaling by all directions. /// /// s 0 0 /// 0 s 0 /// #[inline] pub fn scale(s: f64) -> Transform2D { matrix(s, 0.0, 0.0, s, 0.0, 0.0) } /// Creates a transformation for horizontal scaling. #[inline] pub fn scale_x(s: f64) -> Transform2D { matrix(s, 0.0, 0.0, 1.0, 0.0, 0.0) } /// Creates a transformation for vertical scaling. #[inline] pub fn scale_y(s: f64) -> Transform2D { matrix(1.0, 0.0, 0.0, s, 0.0, 0.0) } ================================================ FILE: src/utils.rs ================================================ use num::{Float, NumCast}; use num::PrimInt as Int; use num::traits::cast; use std::f32::consts::PI; /// Clamp a f32 between 0f32 and 1f32. pub fn clampf32(f: f32) -> f32 { if f < 0f32 { 0f32 } else if f > 1f32 { 1f32 } else { f } } /// Convert degrees to radians. pub fn degrees(d: F) -> F { d * cast(PI / 180.0).unwrap() } /// Convert turns to radians. pub fn turns(t: F) -> F { let f: F = cast(2.0 * PI).unwrap(); f * t } /// The modulo function. #[inline] pub fn modulo(a: I, b: I) -> I { match a % b { r if (r > I::zero() && b < I::zero()) || (r < I::zero() && b > I::zero()) => r + b, r => r, } } /// Modulo float. pub fn fmod(f: f32, n: i32) -> f32 { let i = f.floor() as i32; modulo(i, n) as f32 + f - i as f32 } /// Return the min between to floats. pub fn min(a: f32, b: f32) -> f32 { if a <= b { a } else { b } } /// Return the max between to floats. pub fn max(a: f32, b: f32) -> f32 { if a >= b { a } else { b } } /// Clamp a value to a range. #[inline] pub fn clamp(val: T, min: T, max: T) -> T { if val < min { min } else { if val > max { max } else { val } } } /// Map a value from a given range to a new given range. pub fn map_range (val: X, in_min: X, in_max: X, out_min: Y, out_max: Y) -> Y { let val_f: f64 = NumCast::from(val).unwrap(); let in_min_f: f64 = NumCast::from(in_min).unwrap(); let in_max_f: f64 = NumCast::from(in_max).unwrap(); let out_min_f: f64 = NumCast::from(out_min).unwrap(); let out_max_f: f64 = NumCast::from(out_max).unwrap(); NumCast::from( (val_f - in_min_f) / (in_max_f - in_min_f) * (out_max_f - out_min_f) + out_min_f ).unwrap() }