├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples └── test.rs └── src ├── animations └── mod.rs ├── draw.rs ├── id.rs ├── layout.rs ├── lib.rs ├── matrix.rs └── widgets ├── circular_progress_bar.rs ├── image.rs ├── image9.rs ├── image9_button.rs ├── image_button.rs ├── label.rs ├── mod.rs └── progress_bar.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: rust 4 | rust: 5 | - stable 6 | - beta 7 | - nightly 8 | 9 | after_success: 10 | - | 11 | [ $TRAVIS_BRANCH = master ] && 12 | [ $TRAVIS_PULL_REQUEST = false ] && 13 | cargo publish --no-verify --token ${CRATESIO_TOKEN} 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "immi" 3 | version = "1.0.4" 4 | authors = ["Pierre Krieger "] 5 | documentation = "https://docs.rs/crate/immi/1" 6 | repository = "https://github.com/tomaka/immi" 7 | readme = "README.md" 8 | license = "MIT OR Apache-2.0" 9 | description = "Immediate mode user interface toolkit." 10 | keywords = ["ui", "gamedev", "user", "interface"] 11 | categories = ["gui"] 12 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 immi Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Immi 2 | 3 | Immediate mode user interface library. 4 | 5 | * Immi doesn't draw for you, it only tells you what you need to draw. This is done through a 6 | trait named `Draw` which you must implement on whatever type you want. 7 | * Immi is an immediate mode library, which means that the state of the UI is managed by you and 8 | not by the library. *You* choose what must be displayed in what situation. 9 | 10 | ## [Documentation](https://docs.rs/crate/immi/1) 11 | 12 | - Crates.io: https://docs.rs/crate/immi/1 13 | - Master branch: http://tomaka.github.io/immi/immi/index.html 14 | 15 | The documentation contains the basics of immi. 16 | 17 | ## License 18 | 19 | Licensed under either of 20 | 21 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 22 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 23 | 24 | at your option. 25 | 26 | ### Contribution 27 | 28 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in 29 | the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 30 | additional terms or conditions. 31 | -------------------------------------------------------------------------------- /examples/test.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 immi Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | extern crate immi; 9 | 10 | fn main() { 11 | } 12 | -------------------------------------------------------------------------------- /src/animations/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 immi Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | //! Contains everything related to the animations that are supported by this library. 9 | 10 | use std::time::Duration; 11 | use std::time::SystemTime; 12 | 13 | /// Describes how an animation should be interpolated. 14 | pub trait Interpolation { 15 | /// Takes a number representing the number of animation cycles that have elapsed, and returns 16 | /// the progress of the interpolation. 17 | /// 18 | /// Note that we're using `f64` instead of `f32` like in the rest of the library, because it 19 | /// is common to start an animation at `UNIX_EPOCH` which is far away enough to cause precision 20 | /// issues. 21 | fn from_progress(&self, anim_progress: f64) -> f64; 22 | 23 | /// Takes an instance representing the current point in time, an instant representing the 24 | /// point in time when the animation has started or will start, the duration, and returns a 25 | /// value between 0.0 and 1.0 representing the progress of the animation. 26 | /// 27 | /// Implementations typically return `0.0` when `now < start` and `1.0` when 28 | /// `now > start + duration_ns`. 29 | fn calculate(&self, now: SystemTime, start: SystemTime, duration: Duration) -> f64 { 30 | let now_minus_start_ms = { 31 | let (dur, neg) = match now.duration_since(start) { 32 | Ok(d) => (d, false), 33 | Err(err) => (err.duration(), true) 34 | }; 35 | 36 | let val = dur.as_secs() as f64 * 1000000.0 + dur.subsec_nanos() as f64 / 1000.0; 37 | if neg { -val } else { val } 38 | }; 39 | 40 | let duration_ms = duration.as_secs() as f64 * 1000000.0 + 41 | duration.subsec_nanos() as f64 / 1000.0; 42 | 43 | let anim_progress = now_minus_start_ms / duration_ms; 44 | self.from_progress(anim_progress) 45 | } 46 | 47 | /// Reverses an interpolation. The element will start at its final position and go towards 48 | /// the start. 49 | #[inline] 50 | fn reverse(self) -> Reversed where Self: Sized { 51 | Reversed::new(self) 52 | } 53 | 54 | /// Repeats an interpolation forever. 55 | #[inline] 56 | fn repeat(self) -> Repeated where Self: Sized { 57 | Repeated::new(self) 58 | } 59 | 60 | /// Repeats an interpolation forever. Every other cycle is reversed. 61 | #[inline] 62 | fn alternate_repeat(self) -> AlternateRepeated where Self: Sized { 63 | AlternateRepeated::new(self) 64 | } 65 | } 66 | 67 | /// A linear animation. The animation progresses at a constant rate. 68 | #[derive(Copy, Clone, Default, Debug)] 69 | pub struct Linear; 70 | 71 | impl Interpolation for Linear { 72 | #[inline] 73 | fn from_progress(&self, anim_progress: f64) -> f64 { 74 | if anim_progress >= 1.0 { 75 | 1.0 76 | } else if anim_progress <= 0.0 { 77 | 0.0 78 | } else { 79 | anim_progress 80 | } 81 | } 82 | } 83 | 84 | /// An ease-out animation. The animation progresses quickly and then slows down before reaching its 85 | /// final position. 86 | #[derive(Copy, Clone, Debug)] 87 | pub struct EaseOut { 88 | /// The formula is `1.0 - exp(-linear_progress * factor)`. 89 | /// 90 | /// The higher the factor, the quicker the element will reach its destination. 91 | pub factor: f64, 92 | } 93 | 94 | impl EaseOut { 95 | /// Builds a `EaseOut` object. 96 | #[inline] 97 | pub fn new(factor: f64) -> EaseOut { 98 | EaseOut { 99 | factor: factor, 100 | } 101 | } 102 | } 103 | 104 | impl Default for EaseOut { 105 | #[inline] 106 | fn default() -> EaseOut { 107 | EaseOut { factor: 10.0 } 108 | } 109 | } 110 | 111 | impl Interpolation for EaseOut { 112 | #[inline] 113 | fn from_progress(&self, anim_progress: f64) -> f64 { 114 | 1.0 - (-anim_progress * self.factor).exp() 115 | } 116 | } 117 | 118 | /// Wraps around an interpolation and reverses it. The element will start at its final position 119 | /// and go towards the start. 120 | #[derive(Copy, Clone, Debug)] 121 | pub struct Reversed { 122 | inner: I 123 | } 124 | 125 | impl Reversed where I: Interpolation { 126 | /// Builds a `Reversed` object. 127 | #[inline] 128 | pub fn new(inner: I) -> Reversed { 129 | Reversed { 130 | inner: inner, 131 | } 132 | } 133 | } 134 | 135 | impl Interpolation for Reversed where I: Interpolation { 136 | #[inline] 137 | fn from_progress(&self, anim_progress: f64) -> f64 { 138 | self.inner.from_progress(1.0 - anim_progress) 139 | } 140 | } 141 | 142 | /// Wraps around an interpolation and repeats the interpolation multiple times. 143 | #[derive(Copy, Clone, Debug)] 144 | pub struct Repeated { 145 | inner: I 146 | } 147 | 148 | impl Repeated where I: Interpolation { 149 | /// Builds a `Repeated` object. 150 | #[inline] 151 | pub fn new(inner: I) -> Repeated { 152 | Repeated { 153 | inner: inner, 154 | } 155 | } 156 | } 157 | 158 | impl Interpolation for Repeated where I: Interpolation { 159 | #[inline] 160 | fn from_progress(&self, anim_progress: f64) -> f64 { 161 | let progress = if anim_progress < 0.0 { 1.0 + anim_progress % 1.0 } 162 | else { anim_progress % 1.0 }; 163 | self.inner.from_progress(progress) 164 | } 165 | } 166 | 167 | /// Wraps around an interpolation and repeats the interpolation multiple times. Each uneven cycle 168 | /// the animation is reversed. 169 | #[derive(Copy, Clone, Debug)] 170 | pub struct AlternateRepeated { 171 | inner: I 172 | } 173 | 174 | impl AlternateRepeated where I: Interpolation { 175 | /// Builds a `AlternateRepeated` object. 176 | #[inline] 177 | pub fn new(inner: I) -> AlternateRepeated { 178 | AlternateRepeated { 179 | inner: inner, 180 | } 181 | } 182 | } 183 | 184 | impl Interpolation for AlternateRepeated where I: Interpolation { 185 | #[inline] 186 | fn from_progress(&self, anim_progress: f64) -> f64 { 187 | let progress = 1.0 - ((anim_progress.abs() % 2.0) - 1.0).abs(); 188 | self.inner.from_progress(progress) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/draw.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 immi Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | use Matrix; 9 | 10 | /// Trait for a context that can handle drawing. 11 | pub trait DrawImage { 12 | /// Draws a single triangle that covers the top-left hand corner of the surface, pre-multiplied 13 | /// by the matrix. 14 | /// 15 | /// To do so, draw a triangle whose coordinatges are `[-1.0, 1.0]`, `[-1.0, -1.0]` 16 | /// and `[1.0, 1.0]`, then pre-muliplty these coordinates with the matrix given as parameter. 17 | /// If you use Vulkan or DirectX, you have to perform an additional step. Pre-multiply that 18 | /// result with a matrix that inverts the `y` coordinate (ie. an identity matrix but whose 19 | /// value at the second row of the second column is `-1.0`). 20 | /// 21 | /// The UV coordinates passed as parameter are respectively the texture coordinates at the 22 | /// top-left, bottom-left and top-right corners. `[0.0, 0.0]` is the bottom-left hand corner 23 | /// of the texture, and `[1.0, 1.0]` is the top-right hand corner. If you use OpenGL, you can 24 | /// pass through the values. If you use DirectX or Vulkan, you must do `y = 1.0 - y` somewhere. 25 | fn draw_triangle(&mut self, texture: &I, matrix: &Matrix, 26 | uv_coords: [[f32; 2]; 3]); 27 | 28 | /// Draws an image that covers the whole surface (from `-1.0` to `1.0` both horizontally and 29 | /// vertically), but multiplied by the matrix. 30 | /// 31 | /// This function should not try to preseve the aspect ratio of the image. This is handled by 32 | /// the caller. 33 | #[inline] 34 | fn draw_image(&mut self, name: &I, matrix: &Matrix) { 35 | self.draw_image_uv(name, matrix, [0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]) 36 | } 37 | 38 | /// Draws an image that covers the whole surface (from `-1.0` to `1.0` both horizontally and 39 | /// vertically), but multiplied by the matrix. 40 | /// 41 | /// This function should not try to preseve the aspect ratio of the image. This is handled by 42 | /// the caller. 43 | /// 44 | /// Contrary to `draw_image`, this library allows one to specify UV coordinates of the four 45 | /// borders. Coordinates `[0.0, 0.0]` correspond to the bottom-left hand corner of the 46 | /// image, and `[1.0, 1.0]` correspond to the top-right hand corner. 47 | #[inline] 48 | fn draw_image_uv(&mut self, name: &I, matrix: &Matrix, top_left: [f32; 2], 49 | top_right: [f32; 2], bottom_right: [f32; 2], bottom_left: [f32; 2]) 50 | { 51 | self.draw_triangle(name, matrix, [top_left, bottom_left, top_right]); 52 | 53 | let invert = Matrix::scale(-1.0); 54 | self.draw_triangle(name, &(*matrix * invert), [bottom_right, top_right, bottom_left]); 55 | } 56 | 57 | /// Given an image, this functions returns its width divided by its height. 58 | fn get_image_width_per_height(&mut self, name: &I) -> f32; 59 | } 60 | 61 | pub trait DrawText { 62 | /// Does the same as `draw_image`, but draws a glyph of a text instead. 63 | fn draw_glyph(&mut self, text_style: &T, glyph: char, matrix: &Matrix); 64 | 65 | /// Returns the height of a line of text in EMs. 66 | /// 67 | /// This value is usually somewhere around `1.2`. 68 | fn line_height(&self, text_style: &T) -> f32; 69 | 70 | /// Returns information about a specific glyph. 71 | fn glyph_infos(&self, text_style: &T, glyph: char) -> GlyphInfos; 72 | 73 | /// Returns the kerning between two characters for the given font. 74 | /// 75 | /// The kerning is an offset to add to the position of a specific character when it follows 76 | /// another specific character. For example when you write `To`, thanks to kerning the `o` 77 | /// can slip under the `T`, which looks nicer than if they were simply next to each other. 78 | /// 79 | /// A positive value moves the second character further away from the first one, while a 80 | /// negative values moves the second character next to the first one. The value must be a 81 | /// multiple of 1 em. When in doubt, you can simply return `0.0`. 82 | fn kerning(&self, text_style: &T, first_char: char, second_char: char) -> f32; 83 | } 84 | 85 | /// Information about a single glyph. 86 | /// 87 | /// All the values of this struct must be relative to the size of an EM, so that the library can 88 | /// adjust the values to any size. 89 | #[derive(Debug, Copy, Clone)] 90 | pub struct GlyphInfos { 91 | /// Width of the glyph in pixels, divided by the number of pixels of an EM. 92 | pub width: f32, 93 | 94 | /// Height of the glyph in pixels, divided by the number of pixels of an EM. 95 | /// 96 | /// By definition, this value is supposed to be always 1.0 for the glyph 'M'. In practice this 97 | /// is not always exactly true. 98 | pub height: f32, 99 | 100 | /// Number of pixels from the end of the previous glyph to the start of this one, divided by 101 | /// the number of pixels of an EM. 102 | pub x_offset: f32, 103 | 104 | /// Number of pixels from the base of the line to the top of this one, divided by the number 105 | /// of pixels of an EM. 106 | /// 107 | /// For glyphs that don't go under the line (like 'm' or 'u' for example), this is equal to 108 | /// the height of the glyph. For glyphs that go under the line (like 'p' or 'g'), this is 109 | /// equal to the height of the glyph minus the portion that goes under the line. 110 | pub y_offset: f32, 111 | 112 | /// Number of pixels from the end of the previous glyph to the end of this one, divided by 113 | /// the number of pixels of an EM. 114 | /// 115 | /// Should always be superior to `width + x_offset`. 116 | pub x_advance: f32, 117 | } 118 | -------------------------------------------------------------------------------- /src/id.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 immi Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | /// Represents an identifier of a widget. See the documentation of `DrawContext`. 9 | #[derive(Clone, Debug, PartialEq, Eq)] 10 | pub struct WidgetId(usize); 11 | 12 | impl From for WidgetId { 13 | #[inline] 14 | fn from(id: usize) -> WidgetId { 15 | WidgetId(id) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/layout.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 immi Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | use std::cell::Cell; 9 | use std::cell::RefCell; 10 | use std::cell::RefMut; 11 | use std::rc::Rc; 12 | use std::sync::Arc; 13 | use std::sync::atomic::AtomicBool; 14 | use std::sync::atomic::AtomicUsize; 15 | use std::sync::atomic::Ordering; 16 | use std::time::Duration; 17 | use std::time::SystemTime; 18 | 19 | use Matrix; 20 | use WidgetId; 21 | 22 | use animations::Interpolation; 23 | 24 | /// Start drawing your UI. 25 | /// 26 | /// This function builds a `SharedDrawContext` that can be used to build `DrawContext`s. 27 | pub fn draw() -> SharedDrawContext { 28 | SharedDrawContext { 29 | shared1: Arc::new(Shared1 { 30 | next_widget_id: AtomicUsize::new(1), 31 | cursor_hovered_widget: AtomicBool::new(false), 32 | }) 33 | } 34 | } 35 | 36 | /// A context shared between all draw contexts. 37 | pub struct SharedDrawContext { 38 | shared1: Arc, 39 | } 40 | 41 | impl SharedDrawContext { 42 | /// 43 | /// The cursor coordinates, if any, must be in OpenGL viewport coordinates. In other words, 44 | /// `[-1.0, -1.0]` corresponds to the bottom-left hand corner of the screen, and `[1.0, 1.0]` 45 | /// to the top-right hand corner. 46 | pub fn draw<'b, D: ?Sized + 'b>(&self, width: f32, height: f32, draw: &'b mut D, 47 | cursor: Option<[f32; 2]>, cursor_was_pressed: bool, 48 | cursor_was_released: bool) -> DrawContext<'b, D> 49 | { 50 | DrawContext { 51 | matrix: Matrix::identity(), 52 | width: width, 53 | height: height, 54 | animation: None, 55 | cursor: cursor, 56 | cursor_was_pressed: cursor_was_pressed, 57 | cursor_was_released: cursor_was_released, 58 | shared1: self.shared1.clone(), 59 | shared2: Rc::new(Shared2 { 60 | draw: RefCell::new(draw), 61 | cursor_hovered_widget: Cell::new(false), 62 | }), 63 | } 64 | } 65 | 66 | /// Returns true if one of the elements that has been drawn by one of the draw contexts was 67 | /// under the mouse cursor. 68 | /// 69 | /// This function can be used to determine whether the user is hovering some part of the UI. 70 | /// In other words, if this function returns false, then you know that the user is hovering 71 | /// what is under the UI. 72 | /// 73 | /// When you create the context, this value is initally false. Each widget that you draw can 74 | /// call `set_cursor_hovered_widget` to pass this value to true. 75 | #[inline] 76 | pub fn cursor_hovered_widget(&self) -> bool { 77 | self.shared1.cursor_hovered_widget.load(Ordering::Relaxed) 78 | } 79 | } 80 | 81 | struct Shared1 { 82 | next_widget_id: AtomicUsize, 83 | cursor_hovered_widget: AtomicBool, 84 | } 85 | 86 | /// Contains everything required to draw a widget. 87 | pub struct DrawContext<'b, D: ?Sized + 'b> { 88 | shared1: Arc, 89 | shared2: Rc>, 90 | 91 | matrix: Matrix, 92 | width: f32, 93 | height: f32, 94 | 95 | /// If `Some`, contains the base animation. The first value is the matrix of the start of 96 | /// the animation, and the second value is the percentage of the linear interpolation between 97 | /// `0.0` and `1.0`. 98 | animation: Option<(Matrix, f32)>, 99 | 100 | /// Position of the cursor between `-1.0` and `1.0`, where -1.0 is the left or bottom, and 1.0 101 | /// is the right or top of the window. 102 | /// 103 | /// This is the position of the cursor in the original viewport, not in the *current* viewport. 104 | cursor: Option<[f32; 2]>, 105 | 106 | cursor_was_pressed: bool, 107 | cursor_was_released: bool, 108 | } 109 | 110 | struct Shared2<'a, D: ?Sized + 'a> { 111 | draw: RefCell<&'a mut D>, 112 | 113 | /// True if the cursor is over an element of the UI. 114 | cursor_hovered_widget: Cell, 115 | } 116 | 117 | impl<'b, D: ?Sized + 'b> DrawContext<'b, D> { 118 | /// UNSTABLE. Obtains the underlying `draw` object. 119 | #[inline] 120 | #[doc(hidden)] 121 | pub fn draw(&self) -> RefMut<&'b mut D> { 122 | self.shared2.draw.borrow_mut() 123 | } 124 | 125 | /// Returns a matrix that turns a fullscreen rectangle into a rectangle that covers only the 126 | /// context's area. 127 | #[inline] 128 | pub fn matrix(&self) -> Matrix { 129 | if let Some((matrix, percent)) = self.animation { 130 | // TODO: correct decomposition with https://drafts.csswg.org/css-transforms/#decomposing-a-2d-matrix 131 | 132 | #[inline] 133 | fn lerp(a: f32, b: f32, f: f32) -> f32 { a + (b - a) * f } 134 | 135 | let matrix = matrix.0; 136 | let my_m = self.matrix.0; 137 | 138 | Matrix([ 139 | [lerp(matrix[0][0], my_m[0][0], percent), lerp(matrix[0][1], my_m[0][1], percent)], 140 | [lerp(matrix[1][0], my_m[1][0], percent), lerp(matrix[1][1], my_m[1][1], percent)], 141 | [lerp(matrix[2][0], my_m[2][0], percent), lerp(matrix[2][1], my_m[2][1], percent)] 142 | ]) 143 | 144 | } else { 145 | self.matrix 146 | } 147 | } 148 | 149 | /// Returns true if the cursor went from up to down in the current frame. 150 | /// 151 | /// This is the value that was passed when constructing the context. 152 | #[inline] 153 | pub fn cursor_was_pressed(&self) -> bool { 154 | self.cursor_was_pressed 155 | } 156 | 157 | /// Returns true if the cursor went from down to up in the current frame. 158 | /// 159 | /// This is the value that was passed when constructing the context. 160 | #[inline] 161 | pub fn cursor_was_released(&self) -> bool { 162 | self.cursor_was_released 163 | } 164 | 165 | /// Returns true if one of the elements that has been drawn is under the mouse cursor. 166 | /// 167 | /// When you create the context, this value is initally false. Each widget that you draw can 168 | /// call `set_cursor_hovered_widget` to pass this value to true. 169 | #[inline] 170 | pub fn cursor_hovered_widget(&self) -> bool { 171 | self.shared2.cursor_hovered_widget.get() 172 | } 173 | 174 | /// Signals the context that the cursor is currently hovering it. This can be later retreived 175 | /// with `cursor_hovered_widget()`. 176 | #[inline] 177 | pub fn set_cursor_hovered_widget(&self) { 178 | self.shared1.cursor_hovered_widget.store(true, Ordering::Relaxed); 179 | self.shared2.cursor_hovered_widget.set(true); 180 | } 181 | 182 | /// Reserves a new ID for a widget. Calling this function multiple times always returns 183 | /// a different id. 184 | #[inline] 185 | pub fn reserve_widget_id(&self) -> WidgetId { 186 | self.shared1.next_widget_id.fetch_add(1, Ordering::Relaxed).into() 187 | } 188 | 189 | /// Returns true if the cursor is currently hovering this part of the viewport. 190 | /// 191 | /// This is equivalent to `cursor_hover_coordinates().is_some()`, except more optimized. 192 | #[inline] 193 | pub fn is_cursor_hovering(&self) -> bool { 194 | /// Calculates whether the point is in a rectangle multiplied by a matrix. 195 | fn test(matrix: &Matrix, point: &[f32; 2]) -> bool { 196 | // We start by calculating the positions of the four corners of the shape in viewport 197 | // coordinates, so that they can be compared with the point which is already in 198 | // viewport coordinates. 199 | 200 | let top_left = *matrix * [-1.0, 1.0, 1.0]; 201 | let top_left = [top_left[0] / top_left[2], top_left[1] / top_left[2]]; 202 | 203 | let top_right = *matrix * [1.0, 1.0, 1.0]; 204 | let top_right = [top_right[0] / top_right[2], top_right[1] / top_right[2]]; 205 | 206 | let bot_left = *matrix * [-1.0, -1.0, 1.0]; 207 | let bot_left = [bot_left[0] / bot_left[2], bot_left[1] / bot_left[2]]; 208 | 209 | let bot_right = *matrix * [1.0, -1.0, 1.0]; 210 | let bot_right = [bot_right[0] / bot_right[2], bot_right[1] / bot_right[2]]; 211 | 212 | // The point is within our rectangle if and only if it is on the right side of each 213 | // border of the rectangle (taken in the right order). 214 | // 215 | // To check this, we calculate the dot product of the vector `point - corner` with 216 | // `next_corner - corner`. If the value is positive, then the angle is inferior to 217 | // 90°. If the the value is negative, the angle is superior to 90° and we know that 218 | // the cursor is outside of the rectangle. 219 | 220 | if (point[0] - top_left[0]) * (top_right[0] - top_left[0]) + 221 | (point[1] - top_left[1]) * (top_right[1] - top_left[1]) < 0.0 222 | { 223 | return false; 224 | } 225 | 226 | if (point[0] - top_right[0]) * (bot_right[0] - top_right[0]) + 227 | (point[1] - top_right[1]) * (bot_right[1] - top_right[1]) < 0.0 228 | { 229 | return false; 230 | } 231 | 232 | if (point[0] - bot_right[0]) * (bot_left[0] - bot_right[0]) + 233 | (point[1] - bot_right[1]) * (bot_left[1] - bot_right[1]) < 0.0 234 | { 235 | return false; 236 | } 237 | 238 | if (point[0] - bot_left[0]) * (top_left[0] - bot_left[0]) + 239 | (point[1] - bot_left[1]) * (top_left[1] - bot_left[1]) < 0.0 240 | { 241 | return false; 242 | } 243 | 244 | true 245 | } 246 | 247 | if let Some(cursor) = self.cursor { 248 | test(&self.matrix(), &cursor) 249 | } else { 250 | false 251 | } 252 | } 253 | 254 | /// If the cursor is hovering the context, returns the coordinates of the cursor within the 255 | /// context. 256 | /// 257 | /// The result is in OpenGL-like coordinates. In other words, (-1,-1) is the bottom-left hand 258 | /// corner and (1,1) is the top-right hand corner. 259 | pub fn cursor_hover_coordinates(&self) -> Option<[f32; 2]> { 260 | // we compute the inverse of the matrix 261 | let m = match self.matrix().invert() { 262 | Some(m) => m, 263 | None => return None, 264 | }; 265 | 266 | // and use it to calculate the position of the cursor within the current context 267 | let in_pos = match self.cursor { 268 | Some(p) => p, 269 | None => return None, 270 | }; 271 | 272 | let output_mouse = [ 273 | in_pos[0]*m[0][0] + in_pos[1]*m[1][0] + m[2][0], 274 | in_pos[0]*m[0][1] + in_pos[1]*m[1][1] + m[2][1], 275 | in_pos[0]*m[0][2] + in_pos[1]*m[1][2] + m[2][2], 276 | ]; 277 | 278 | let output_mouse = [output_mouse[0] / output_mouse[2], output_mouse[1] / output_mouse[2]]; 279 | 280 | if output_mouse[0] < -1.0 || output_mouse[0] > 1.0 || output_mouse[0] != output_mouse[0] || 281 | output_mouse[1] < -1.0 || output_mouse[1] > 1.0 || output_mouse[1] != output_mouse[1] 282 | { 283 | return None; 284 | } 285 | 286 | Some(output_mouse) 287 | } 288 | 289 | /// Returns the ratio of the width of the surface divided by its height. 290 | #[inline] 291 | pub fn width_per_height(&self) -> f32 { 292 | self.width / self.height 293 | } 294 | 295 | /// Builds a new draw context containing a subarea of the current context, but with a margin. 296 | /// 297 | /// The margin is expressed in percentage of the surface (between 0.0 and 1.0). 298 | #[inline] 299 | pub fn margin(&self, top: f32, right: f32, bottom: f32, left: f32) -> DrawContext<'b, D> { 300 | DrawContext { 301 | matrix: self.matrix * Matrix::translate(left - right, bottom - top) 302 | * Matrix::scale_wh(1.0 - right - left, 1.0 - top - bottom), 303 | width: self.width * (1.0 - left - right), 304 | height: self.height * (1.0 - top - bottom), 305 | shared1: self.shared1.clone(), 306 | shared2: self.shared2.clone(), 307 | animation: self.animation, 308 | cursor: self.cursor, 309 | cursor_was_pressed: self.cursor_was_pressed, 310 | cursor_was_released: self.cursor_was_released, 311 | } 312 | } 313 | 314 | /// Builds a new draw context containing a subarea of the current context, but with a margin. 315 | /// 316 | /// If the width of the surface is inferior to the height then the margin is expressed as a 317 | /// percentage of the width, and vice versa. 318 | /// 319 | /// This guarantees that the size in pixels of the margin is the same if you pass the same 320 | /// values. 321 | #[inline] 322 | pub fn uniform_margin(&self, top: f32, right: f32, bottom: f32, left: f32) 323 | -> DrawContext<'b, D> 324 | { 325 | let wph = self.width_per_height(); 326 | let wph = if wph < 1.0 { 1.0 } else { wph }; 327 | 328 | let hpw = 1.0 / self.width_per_height(); 329 | let hpw = if hpw < 1.0 { 1.0 } else { hpw }; 330 | 331 | self.margin(top / hpw, right / wph, bottom / hpw, left / wph) 332 | } 333 | 334 | /// Modifies the layout so that the given width per height ratio is respected. The size of the 335 | /// new viewport will always been equal or small to the existing viewport. 336 | /// 337 | /// If the viewport needs to be reduced horizontally, then the horizontal alignment is used. If 338 | /// it needs to be reduced vertically, then the vertical alignment is used. 339 | pub fn enforce_aspect_ratio_downscale(&self, width_per_height: f32, alignment: &Alignment) 340 | -> DrawContext<'b, D> 341 | { 342 | let current_width_per_height = self.width_per_height(); 343 | 344 | if width_per_height > current_width_per_height { 345 | let alignment = alignment.vertical; 346 | self.vertical_rescale(current_width_per_height / width_per_height, &alignment) 347 | 348 | } else { 349 | let alignment = alignment.horizontal; 350 | self.horizontal_rescale(width_per_height / current_width_per_height, &alignment) 351 | } 352 | } 353 | 354 | /// Modifies the layout so that the given width per height ratio is respected. The size of the 355 | /// new viewport will always been equal or greater to the existing viewport. 356 | /// 357 | /// If the viewport needs to be increased horizontally, then the horizontal alignment is used. 358 | /// If it needs to be increased vertically, then the vertical alignment is used. 359 | pub fn enforce_aspect_ratio_upscale(&self, width_per_height: f32, alignment: &Alignment) 360 | -> DrawContext<'b, D> 361 | { 362 | let current_width_per_height = self.width_per_height(); 363 | 364 | if width_per_height > current_width_per_height { 365 | let alignment = alignment.horizontal; 366 | self.horizontal_rescale(width_per_height / current_width_per_height, &alignment) 367 | 368 | } else { 369 | let alignment = alignment.vertical; 370 | self.vertical_rescale(current_width_per_height / width_per_height, &alignment) 371 | } 372 | } 373 | 374 | /// Builds a new draw context containing a subarea of the current context. The width of the new 375 | /// viewport will be the same as the current one, but its new height will be multipled by 376 | /// the value of `scale`. 377 | /// 378 | /// The alignment is used to determine the position of the new viewport inside the old one. 379 | #[inline] 380 | pub fn vertical_rescale(&self, scale: f32, alignment: &VerticalAlignment) 381 | -> DrawContext<'b, D> 382 | { 383 | let y = match alignment { 384 | &VerticalAlignment::Center => 0.0, 385 | &VerticalAlignment::Bottom => scale - 1.0, 386 | &VerticalAlignment::Top => 1.0 - scale, 387 | }; 388 | 389 | DrawContext { 390 | matrix: self.matrix * Matrix::translate(0.0, y) * Matrix::scale_wh(1.0, scale), 391 | width: self.width, 392 | height: self.height * scale, 393 | animation: self.animation, 394 | shared1: self.shared1.clone(), 395 | shared2: self.shared2.clone(), 396 | cursor: self.cursor, 397 | cursor_was_pressed: self.cursor_was_pressed, 398 | cursor_was_released: self.cursor_was_released, 399 | } 400 | } 401 | 402 | /// Builds a new draw context containing a subarea of the current context. The height of the new 403 | /// viewport will be the same as the current one, but its new width will be multipled by 404 | /// the value of `scale`. 405 | /// 406 | /// The alignment is used to determine the position of the new viewport inside the old one. 407 | #[inline] 408 | pub fn horizontal_rescale(&self, scale: f32, alignment: &HorizontalAlignment) 409 | -> DrawContext<'b, D> 410 | { 411 | let x = match alignment { 412 | &HorizontalAlignment::Center => 0.0, 413 | &HorizontalAlignment::Left => scale - 1.0, 414 | &HorizontalAlignment::Right => 1.0 - scale, 415 | }; 416 | 417 | DrawContext { 418 | matrix: self.matrix * Matrix::translate(x, 0.0) * Matrix::scale_wh(scale, 1.0), 419 | width: self.width * scale, 420 | height: self.height, 421 | animation: self.animation, 422 | shared1: self.shared1.clone(), 423 | shared2: self.shared2.clone(), 424 | cursor: self.cursor, 425 | cursor_was_pressed: self.cursor_was_pressed, 426 | cursor_was_released: self.cursor_was_released, 427 | } 428 | } 429 | 430 | /// Splits the viewport in `splits` vertical chunks of equal size. 431 | #[inline] 432 | pub fn vertical_split<'a>(&'a self, splits: usize) -> SplitsIter<'a, 'b, OneGen, D> { 433 | let iter = OneGen { n: splits }; 434 | self.vertical_split_weights(iter) 435 | } 436 | 437 | /// Same as `vertical_split`, but attributes a weight to each chunk. For example a chunk of 438 | /// weight 2 will have twice the size of a chunk of weight 1. 439 | #[inline] 440 | pub fn vertical_split_weights<'a, I>(&'a self, weights: I) -> SplitsIter<'a, 'b, I::IntoIter, D> 441 | where I: IntoIterator, I::IntoIter: ExactSizeIterator + Clone 442 | { 443 | self.split_weights(weights.into_iter(), true) 444 | } 445 | 446 | /// Splits the viewport in `splits` horizontal chunks of equal size. 447 | #[inline] 448 | pub fn horizontal_split<'a>(&'a self, splits: usize) -> SplitsIter<'a, 'b, OneGen, D> { 449 | let iter = OneGen { n: splits }; 450 | self.horizontal_split_weights(iter) 451 | } 452 | 453 | /// Same as `horizontal_split`, but attributes a weight to each chunk. For example a chunk of 454 | /// weight 2 will have twice the size of a chunk of weight 1. 455 | #[inline] 456 | pub fn horizontal_split_weights<'a, I>(&'a self, weights: I) -> SplitsIter<'a, 'b, I::IntoIter, D> 457 | where I: IntoIterator, I::IntoIter: ExactSizeIterator + Clone 458 | { 459 | self.split_weights(weights.into_iter(), false) 460 | } 461 | 462 | /// Internal implementation of the split functions. 463 | #[inline] 464 | fn split_weights<'a, I>(&'a self, weights: I, vertical: bool) -> SplitsIter<'a, 'b, I, D> 465 | where I: ExactSizeIterator + Clone 466 | { 467 | assert!(weights.len() != 0); 468 | 469 | let total_weight = weights.clone().fold(0.0, |a, b| a + b); 470 | let total_weight_inverse = 1.0 / total_weight; 471 | 472 | SplitsIter { 473 | parent: self, 474 | weights: weights, 475 | total_weight_inverse: total_weight_inverse, 476 | current_offset: 0.0, 477 | vertical: vertical, 478 | } 479 | } 480 | 481 | /// Changes the dimensions of the context. 482 | /// 483 | /// The dimensions are a percentage of the current dimensions. For example to divide the width 484 | /// by two, you need to pass `0.5`. 485 | /// 486 | /// The alignment is used to determine the position of the newly-created context relative to 487 | /// the old one. 488 | pub fn rescale(&self, width_percent: f32, height_percent: f32, alignment: &Alignment) 489 | -> DrawContext<'b, D> 490 | { 491 | let x = match alignment.horizontal { 492 | HorizontalAlignment::Center => 0.0, 493 | HorizontalAlignment::Left => width_percent - 1.0, 494 | HorizontalAlignment::Right => 1.0 - width_percent, 495 | }; 496 | 497 | let y = match alignment.vertical { 498 | VerticalAlignment::Center => 0.0, 499 | VerticalAlignment::Bottom => height_percent - 1.0, 500 | VerticalAlignment::Top => 1.0 - height_percent, 501 | }; 502 | 503 | DrawContext { 504 | matrix: self.matrix * Matrix::translate(x, y) 505 | * Matrix::scale_wh(width_percent, height_percent), 506 | width: self.width * width_percent, 507 | height: self.height * height_percent, 508 | animation: self.animation, 509 | shared1: self.shared1.clone(), 510 | shared2: self.shared2.clone(), 511 | cursor: self.cursor, 512 | cursor_was_pressed: self.cursor_was_pressed, 513 | cursor_was_released: self.cursor_was_released, 514 | } 515 | } 516 | 517 | /// Starts an animation. The interpolation, start time and duration are used to calculate 518 | /// at which point of the animation we are. 519 | /// 520 | /// At the moment where you call this function, the element must be at its starting point. 521 | /// After you call this function, you must add further transformations to represent the 522 | /// destination. Don't forget to call `animation_stop` if you want to add further 523 | /// transformations. 524 | /// 525 | /// You can easily reverse this order (ie. the element is at its destination when you call the 526 | /// function and will be moved to its source) by reversing the interpolation with `.reverse()`. 527 | #[inline] 528 | pub fn animation_start(&self, interpolation: I, start_time: SystemTime, duration: Duration) 529 | -> DrawContext<'b, D> 530 | where I: Interpolation 531 | { 532 | let now = SystemTime::now(); 533 | 534 | let interpolation = interpolation.calculate(now, start_time, duration) as f32; 535 | let current_matrix = self.matrix(); 536 | 537 | DrawContext { 538 | matrix: self.matrix, 539 | width: self.width, 540 | height: self.height, 541 | animation: Some((current_matrix, interpolation)), 542 | shared1: self.shared1.clone(), 543 | shared2: self.shared2.clone(), 544 | cursor: self.cursor, 545 | cursor_was_pressed: self.cursor_was_pressed, 546 | cursor_was_released: self.cursor_was_released, 547 | } 548 | } 549 | 550 | /// Stops the animation process. The next commands will always be applied. 551 | #[inline] 552 | pub fn animation_stop(&self) -> DrawContext<'b, D> { 553 | DrawContext { 554 | matrix: self.matrix(), 555 | width: self.width, 556 | height: self.height, 557 | animation: None, 558 | shared1: self.shared1.clone(), 559 | shared2: self.shared2.clone(), 560 | cursor: self.cursor, 561 | cursor_was_pressed: self.cursor_was_pressed, 562 | cursor_was_released: self.cursor_was_released, 563 | } 564 | } 565 | } 566 | 567 | impl<'a, 'b, D: ?Sized + 'b> Clone for DrawContext<'b, D> { 568 | fn clone(&self) -> DrawContext<'b, D> { 569 | DrawContext { 570 | matrix: self.matrix.clone(), 571 | width: self.width.clone(), 572 | height: self.height.clone(), 573 | animation: self.animation.clone(), 574 | shared1: self.shared1.clone(), 575 | shared2: self.shared2.clone(), 576 | cursor: self.cursor.clone(), 577 | cursor_was_pressed: self.cursor_was_pressed, 578 | cursor_was_released: self.cursor_was_released, 579 | } 580 | } 581 | } 582 | 583 | /// Represents the alignment of a viewport. 584 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 585 | pub struct Alignment { 586 | /// The horizontal alignment. 587 | pub horizontal: HorizontalAlignment, 588 | /// The vertical alignment. 589 | pub vertical: VerticalAlignment, 590 | } 591 | 592 | impl Alignment { 593 | /// Shortcut for `(center, center)`. 594 | #[inline] 595 | pub fn center() -> Alignment { 596 | Alignment { 597 | horizontal: HorizontalAlignment::Center, 598 | vertical: VerticalAlignment::Center, 599 | } 600 | } 601 | 602 | /// Shortcut for `(center, bottom)`. 603 | #[inline] 604 | pub fn bottom() -> Alignment { 605 | Alignment { 606 | horizontal: HorizontalAlignment::Center, 607 | vertical: VerticalAlignment::Bottom, 608 | } 609 | } 610 | 611 | /// Shortcut for `(center, top)`. 612 | #[inline] 613 | pub fn top() -> Alignment { 614 | Alignment { 615 | horizontal: HorizontalAlignment::Center, 616 | vertical: VerticalAlignment::Top, 617 | } 618 | } 619 | 620 | /// Shortcut for `(right, center)`. 621 | #[inline] 622 | pub fn right() -> Alignment { 623 | Alignment { 624 | horizontal: HorizontalAlignment::Right, 625 | vertical: VerticalAlignment::Center, 626 | } 627 | } 628 | 629 | /// Shortcut for `(left, center)`. 630 | #[inline] 631 | pub fn left() -> Alignment { 632 | Alignment { 633 | horizontal: HorizontalAlignment::Left, 634 | vertical: VerticalAlignment::Center, 635 | } 636 | } 637 | 638 | /// Shortcut for `(left, top)`. 639 | #[inline] 640 | pub fn top_left() -> Alignment { 641 | Alignment { 642 | horizontal: HorizontalAlignment::Left, 643 | vertical: VerticalAlignment::Top, 644 | } 645 | } 646 | 647 | /// Shortcut for `(right, top)`. 648 | #[inline] 649 | pub fn top_right() -> Alignment { 650 | Alignment { 651 | horizontal: HorizontalAlignment::Right, 652 | vertical: VerticalAlignment::Top, 653 | } 654 | } 655 | 656 | /// Shortcut for `(right, bottom)`. 657 | #[inline] 658 | pub fn bottom_right() -> Alignment { 659 | Alignment { 660 | horizontal: HorizontalAlignment::Right, 661 | vertical: VerticalAlignment::Bottom, 662 | } 663 | } 664 | 665 | /// Shortcut for `(left, bottom)`. 666 | #[inline] 667 | pub fn bottom_left() -> Alignment { 668 | Alignment { 669 | horizontal: HorizontalAlignment::Left, 670 | vertical: VerticalAlignment::Bottom, 671 | } 672 | } 673 | } 674 | 675 | /// Describes a horizontal alignment. 676 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 677 | pub enum HorizontalAlignment { 678 | /// Align in the middle. 679 | Center, 680 | /// Align left. 681 | Left, 682 | /// Align right. 683 | Right, 684 | } 685 | 686 | /// Describes a vertical alignment. 687 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 688 | pub enum VerticalAlignment { 689 | /// Align in the middle. 690 | Center, 691 | /// Align top. 692 | Top, 693 | /// Align bottom. 694 | Bottom, 695 | } 696 | 697 | /// Iterator that splits a context in pieces and returns new contexts. 698 | pub struct SplitsIter<'a, 'b: 'a, I, D: ?Sized + 'b> { 699 | parent: &'a DrawContext<'b, D>, 700 | weights: I, 701 | total_weight_inverse: f32, 702 | current_offset: f32, 703 | vertical: bool, 704 | } 705 | 706 | impl<'a, 'b: 'a, I, D: ?Sized + 'b> Iterator for SplitsIter<'a, 'b, I, D> 707 | where I: Iterator 708 | { 709 | type Item = DrawContext<'b, D>; 710 | 711 | fn next(&mut self) -> Option { 712 | let weight = match self.weights.next() { 713 | Some(w) => w, 714 | None => return None 715 | }; 716 | 717 | let new_width = if !self.vertical { self.parent.width * weight * self.total_weight_inverse } 718 | else { self.parent.width }; 719 | let new_height = if self.vertical { self.parent.height * weight * self.total_weight_inverse } 720 | else { self.parent.height }; 721 | 722 | let scale_matrix = if self.vertical { 723 | Matrix::scale_wh(1.0, weight * self.total_weight_inverse) 724 | } else { 725 | Matrix::scale_wh(weight * self.total_weight_inverse, 1.0) 726 | }; 727 | 728 | let pos_matrix = if self.vertical { 729 | let y = 1.0 - 2.0 * (self.current_offset + weight * 0.5) * self.total_weight_inverse; 730 | Matrix::translate(0.0, y) 731 | } else { 732 | let x = 2.0 * (self.current_offset + weight * 0.5) * self.total_weight_inverse - 1.0; 733 | Matrix::translate(x, 0.0) 734 | }; 735 | 736 | self.current_offset += weight; 737 | 738 | Some(DrawContext { 739 | matrix: self.parent.matrix * pos_matrix * scale_matrix, 740 | width: new_width, 741 | height: new_height, 742 | animation: self.parent.animation, 743 | shared1: self.parent.shared1.clone(), 744 | shared2: self.parent.shared2.clone(), 745 | cursor: self.parent.cursor, 746 | cursor_was_pressed: self.parent.cursor_was_pressed, 747 | cursor_was_released: self.parent.cursor_was_released, 748 | }) 749 | } 750 | 751 | #[inline] 752 | fn size_hint(&self) -> (usize, Option) { 753 | self.weights.size_hint() 754 | } 755 | } 756 | 757 | impl<'a, 'b: 'a, I, D: ?Sized + 'b> ExactSizeIterator for SplitsIter<'a, 'b, I, D> 758 | where I: ExactSizeIterator 759 | { 760 | } 761 | 762 | /// Iterator that generates `1.0` a certain number of times. 763 | // TODO: This is required so that `horizontal_split` and `vertical_split` can express their 764 | // return type. Should be replaced with `-> impl Iterator` eventually. 765 | #[derive(Debug, Clone)] 766 | pub struct OneGen { 767 | n: usize 768 | } 769 | 770 | impl Iterator for OneGen { 771 | type Item = f32; 772 | 773 | #[inline] 774 | fn next(&mut self) -> Option { 775 | if self.n == 0 { return None; } 776 | self.n -= 1; 777 | Some(1.0) 778 | } 779 | 780 | #[inline] 781 | fn size_hint(&self) -> (usize, Option) { 782 | (self.n, Some(self.n)) 783 | } 784 | } 785 | 786 | impl ExactSizeIterator for OneGen { 787 | } 788 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 immi Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | //! # Immediate mode UI and general application architecture 9 | //! 10 | //! The principle of an immediate mode UI is that the position and dimensions of the UI elements 11 | //! are calculated at each frame. The current state of the user interface (for example the content 12 | //! of text boxes, whether a checkbox is checked, which tab is the current tab, etc.) is not stored 13 | //! by the immi library, but in a user-defined structure. 14 | //! 15 | //! There are three steps involved to make your program work with immi: 16 | //! 17 | //! - Create a custom structure that describes the state of your user interface. This state 18 | //! should contain a `immi::UiState` object. 19 | //! - Create a type that implements the `immi::Draw` trait and that handles loading images, 20 | //! fonts, and drawing the user interface on the screen. This is the most complicated part. 21 | //! - Create a function whose purpose is to draw your user interface. Usually you want to 22 | //! create one function for each part of the UI instead, and call all of them in a main function. 23 | //! 24 | //! At each frame, when it is time to draw your UI: 25 | //! 26 | //! - Call `immi::draw`. You will get a `SharedDrawContext`. This object represents a context for 27 | //! drawing the entirety of your UI. 28 | //! - Call `draw()` on your `SharedDrawContext` in order to obtain a `DrawContext`. You will need 29 | //! to pass your implementation of `immi::Draw` (see above), indicate the position of the mouse 30 | //! pointer, the dimensions of the viewport, and whether or not the main mouse button was pressed 31 | //! or released. 32 | //! - Call your custom UI-drawing function (see above), and pass it a reference to the `DrawContext` 33 | //! and a mutable reference to your custom state-holding structure. 34 | //! - The function draws the various elements and updates the UI state. 35 | //! 36 | //! ## Example 37 | //! 38 | //! ```rust 39 | //! // Object that will allow you to draw the UI. 40 | //! struct MyDrawer; 41 | //! impl immi::DrawImage for MyDrawer { 42 | //! fn draw_triangle(&mut self, _: &str, _: &immi::Matrix, _: [[f32; 2]; 3]) {} 43 | //! fn get_image_width_per_height(&mut self, _: &str) -> f32 { 1.0 } 44 | //! } 45 | //! impl immi::DrawText for MyDrawer { 46 | //! fn draw_glyph(&mut self, _: &str, _: char, _: &immi::Matrix) { } 47 | //! fn line_height(&self, _: &str) -> f32 { 1.2 } 48 | //! fn kerning(&self, _: &str, _: char, _: char) -> f32 { 0.0 } 49 | //! fn glyph_infos(&self, _: &str, _: char) -> immi::GlyphInfos { 50 | //! immi::GlyphInfos { width: 1.0, height: 1.0, x_offset: 0.0, 51 | //! y_offset: 1.0, x_advance: 1.0 } 52 | //! } 53 | //! } 54 | //! 55 | //! struct MyUiState { 56 | //! immi_state: immi::UiState, 57 | //! widget1_text: String, 58 | //! checkbox: bool, 59 | //! } 60 | //! 61 | //! fn draw_ui(ctxt: &immi::DrawContext, ui_state: &mut MyUiState) { 62 | //! // ... 63 | //! } 64 | //! 65 | //! let mut my_state = MyUiState { widget1_text: String::new(), checkbox: false, 66 | //! immi_state: Default::default() }; 67 | //! let mut drawer = MyDrawer; 68 | //! 69 | //! loop { 70 | //! let ui_context = immi::draw(); 71 | //! let ui_context = ui_context.draw(1024.0, 768.0, &mut drawer, None, false, false); 72 | //! draw_ui(&ui_context, &mut my_state); 73 | //! # break; 74 | //! } 75 | //! ``` 76 | //! 77 | //! # Drawing 78 | //! 79 | //! Once you have a `DrawContext` you can start drawing your user interface. 80 | //! 81 | //! A `DrawContext` represents an area of the viewport where things you should be drawn. Initially 82 | //! this area contain the whole viewport, but you can call methods on the `DrawContext` to adjust 83 | //! this area. 84 | //! 85 | //! In order to draw widgets, you can use the functions provided by the modules of the `widgets` 86 | //! module of this library. 87 | //! 88 | //! Example: 89 | //! 90 | //! ``` 91 | //! fn draw_ui(ctxt: &immi::DrawContext) 92 | //! where D: immi::DrawImage 93 | //! { 94 | //! // Assuming you immediately called `draw_ui` after creating the `DrawContext`, the `ctxt` 95 | //! // object represents the whole viewport.. 96 | //! 97 | //! // Draws an image on the whole viewport. 98 | //! // The bottom alignment is used if the aspect ratio of the image doesn't match the aspect 99 | //! // ratio of the viewport. 100 | //! // The "background" string will be passed to your implementation of `immi::Draw`, so you 101 | //! // are free to choose what the type of data exactly is. 102 | //! immi::widgets::image::draw(ctxt, "background", &immi::Alignment::bottom()); 103 | //! 104 | //! // We resize the viewport so that it only covers the top half of the screen 105 | //! let ctxt = ctxt.vertical_rescale(0.5, &immi::VerticalAlignment::Bottom); 106 | //! draw_bottom_bar(&ctxt); 107 | //! } 108 | //! 109 | //! fn draw_bottom_bar(ctxt: &immi::DrawContext) 110 | //! where D: immi::DrawImage 111 | //! { 112 | //! // Draws an image on the bottom half of the screen 113 | //! immi::widgets::image::draw(ctxt, "top_background", &immi::Alignment::center()); 114 | //! } 115 | //! ``` 116 | //! 117 | pub use draw::DrawImage; 118 | pub use draw::DrawText; 119 | pub use draw::GlyphInfos; 120 | pub use id::WidgetId; 121 | pub use layout::draw; 122 | pub use layout::Alignment; 123 | pub use layout::DrawContext; 124 | pub use layout::SharedDrawContext; 125 | pub use layout::HorizontalAlignment; 126 | pub use layout::VerticalAlignment; 127 | pub use matrix::Matrix; 128 | 129 | mod draw; 130 | mod id; 131 | mod layout; 132 | mod matrix; 133 | 134 | pub mod animations; 135 | pub mod widgets; 136 | 137 | /// Contains some persistent info about the UI. 138 | #[derive(Debug, Clone, Default, PartialEq, Eq)] 139 | pub struct UiState { 140 | /// Identifier of the widget that is currently active. 141 | /// 142 | /// For example if you maintain the left button of the mouse, the element under will be active. 143 | /// If you then move your mouse somewhere else, the active element doesn't change. 144 | pub active_widget: Option, 145 | } 146 | -------------------------------------------------------------------------------- /src/matrix.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 immi Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | use std::ops; 9 | 10 | /// A 2x3 matrix. The data is stored in column-major. 11 | #[derive(Copy, Clone, Debug, PartialEq)] 12 | pub struct Matrix(pub [[f32; 2]; 3]); 13 | 14 | impl Matrix { 15 | /// Builds an identity matrix, in other words a matrix that has no effect. 16 | #[inline] 17 | pub fn identity() -> Matrix { 18 | Matrix([ 19 | [1.0, 0.0], 20 | [0.0, 1.0], 21 | [0.0, 0.0], 22 | ]) 23 | } 24 | 25 | /// Builds a matrix that will rescale both width and height of a given factor. 26 | #[inline] 27 | pub fn scale(factor: f32) -> Matrix { 28 | Matrix([ 29 | [factor, 0.0 ], 30 | [ 0.0 , factor], 31 | [ 0.0 , 0.0 ], 32 | ]) 33 | } 34 | 35 | /// Builds a matrix that will multiply the width and height by a certain factor. 36 | #[inline] 37 | pub fn scale_wh(w: f32, h: f32) -> Matrix { 38 | Matrix([ 39 | [ w, 0.0], 40 | [0.0, h ], 41 | [0.0, 0.0], 42 | ]) 43 | } 44 | 45 | /// Builds a matrix that will translate the object. 46 | #[inline] 47 | pub fn translate(x: f32, y: f32) -> Matrix { 48 | Matrix([ 49 | [1.0, 0.0], 50 | [0.0, 1.0], 51 | [ x, y ], 52 | ]) 53 | } 54 | 55 | /// Builds a matrix that will rotate the object. 56 | #[inline] 57 | pub fn rotate(radians: f32) -> Matrix { 58 | let cos = radians.cos(); 59 | let sin = radians.sin(); 60 | 61 | Matrix([ 62 | [cos, -sin], 63 | [sin, cos], 64 | [0.0, 0.0], 65 | ]) 66 | } 67 | 68 | /// Builds a matrix that will skew the x coordinate by a certain angle. 69 | #[inline] 70 | pub fn skew_x(radians: f32) -> Matrix { 71 | let tan = radians.tan(); 72 | 73 | Matrix([ 74 | [1.0, 0.0], 75 | [tan, 1.0], 76 | [0.0, 0.0], 77 | ]) 78 | } 79 | 80 | /// Builds the matrix's invert. 81 | /// 82 | /// Returns `None` if the determinant is zero, infinite or NaN. 83 | pub fn invert(&self) -> Option<[[f32; 3]; 3]> { 84 | let me = self.0; 85 | let det = me[0][0] * me[1][1] - me[1][0] * me[0][1]; 86 | 87 | if det == 0.0 || det != det { 88 | return None; 89 | } 90 | 91 | let det_inv = 1.0 / det; 92 | 93 | Some([ 94 | [det_inv * me[1][1], det_inv * -me[0][1], 0.0], 95 | [det_inv * -me[1][0], det_inv * me[0][0], 0.0], 96 | [ 97 | det_inv * (me[1][0]*me[2][1]-me[2][0]*me[1][1]), 98 | det_inv * (me[2][0]*me[0][1]-me[0][0]*me[2][1]), 99 | det_inv * (me[0][0]*me[1][1]-me[1][0]*me[0][1]) 100 | ] 101 | ]) 102 | } 103 | } 104 | 105 | impl ops::Mul for Matrix { 106 | type Output = Matrix; 107 | 108 | #[inline] 109 | fn mul(self, other: Matrix) -> Matrix { 110 | let me = self.0; 111 | let other = other.0; 112 | 113 | let a = me[0][0] * other[0][0] + me[1][0] * other[0][1]; 114 | let b = me[0][0] * other[1][0] + me[1][0] * other[1][1]; 115 | let c = me[0][0] * other[2][0] + me[1][0] * other[2][1] + me[2][0]; 116 | let d = me[0][1] * other[0][0] + me[1][1] * other[0][1]; 117 | let e = me[0][1] * other[1][0] + me[1][1] * other[1][1]; 118 | let f = me[0][1] * other[2][0] + me[1][1] * other[2][1] + me[2][1]; 119 | 120 | Matrix([ 121 | [a, d], 122 | [b, e], 123 | [c, f], 124 | ]) 125 | } 126 | } 127 | 128 | impl ops::Mul<[f32; 3]> for Matrix { 129 | type Output = [f32; 3]; 130 | 131 | #[inline] 132 | fn mul(self, other: [f32; 3]) -> [f32; 3] { 133 | let me = self.0; 134 | 135 | let x = me[0][0] * other[0] + me[1][0] * other[1] + me[2][0] * other[2]; 136 | let y = me[0][1] * other[0] + me[1][1] * other[1] + me[2][1] * other[2]; 137 | let z = other[2]; 138 | 139 | [x, y, z] 140 | } 141 | } 142 | 143 | impl Into<[[f32; 3]; 3]> for Matrix { 144 | #[inline] 145 | fn into(self) -> [[f32; 3]; 3] { 146 | let me = self.0; 147 | 148 | [ 149 | [me[0][0], me[0][1], 0.0], 150 | [me[1][0], me[1][1], 0.0], 151 | [me[2][0], me[2][1], 1.0], 152 | ] 153 | } 154 | } 155 | 156 | impl Into<[[f32; 4]; 4]> for Matrix { 157 | #[inline] 158 | fn into(self) -> [[f32; 4]; 4] { 159 | let m = self.0; 160 | 161 | [ 162 | [m[0][0], m[0][1], 0.0, 0.0], 163 | [m[1][0], m[1][1], 0.0, 0.0], 164 | [ 0.0, 0.0, 0.0, 0.0], 165 | [m[2][0], m[2][1], 0.0, 1.0] 166 | ] 167 | } 168 | } 169 | 170 | #[cfg(test)] 171 | mod tests { 172 | use std::f32::consts::PI; 173 | use matrix::Matrix; 174 | 175 | #[test] 176 | fn multiply() { 177 | assert_eq!(Matrix::scale(2.0) * Matrix::scale(3.0), 178 | Matrix::scale(6.0)); 179 | 180 | assert_eq!(Matrix::translate(1.0, 2.0) * Matrix::translate(4.0, -1.0), 181 | Matrix::translate(5.0, 1.0)); 182 | } 183 | 184 | #[test] 185 | fn invert() { 186 | assert_eq!(Matrix::scale(2.0).invert().unwrap(), 187 | Into::<[[f32; 3]; 3]>::into(Matrix::scale(0.5))); 188 | 189 | assert_eq!(Matrix::translate(4.0, 0.5).invert().unwrap(), 190 | Into::<[[f32; 3]; 3]>::into(Matrix::translate(-4.0, -0.5))); 191 | 192 | // Note that this rotation test works "by chance" because values are not 193 | // exactly 0.0 or 1.0. 194 | assert_eq!(Matrix::rotate(PI).invert().unwrap(), 195 | Into::<[[f32; 3]; 3]>::into(Matrix::rotate(-PI))); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/widgets/circular_progress_bar.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 immi Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | //! A circular progress bar is a circle that fills itself to indicate some sort of progression. 9 | //! 10 | //! A widget like this is composed of two images: 11 | //! 12 | //! - The widget when empty. 13 | //! - The widget when full. Since this one is drawn over the previous one, it can also just be the 14 | //! difference between empty and full. 15 | //! 16 | //! This module supposes that the center of the circular progress bar is the center of the image. 17 | //! The direction is always clockwise. <-- TODO: allow choosing this 18 | //! 19 | use Alignment; 20 | use DrawImage; 21 | use DrawContext; 22 | use Matrix; 23 | 24 | use widgets::image; 25 | 26 | /// Draws a circular progress bar and keeps the aspect ratio of the empty image. 27 | /// 28 | /// If the `full` image doesn't have the same aspect ratio, it will be stretched. 29 | /// 30 | /// # Panic 31 | /// 32 | /// Panicks if `progress` is not between 0.0 and 1.0. 33 | #[inline] 34 | pub fn draw, I: ?Sized>(draw: &DrawContext, empty: &I, 35 | full: &I, progress: f32, alignment: &Alignment) 36 | { 37 | let draw = draw.animation_stop(); 38 | let ratio = draw.draw().get_image_width_per_height(empty); 39 | stretch(&draw.enforce_aspect_ratio_downscale(ratio, alignment), empty, full, progress) 40 | } 41 | 42 | /// Draws a circular progress bar, stretching it over the whole area. 43 | /// 44 | /// # Panic 45 | /// 46 | /// Panicks if `progress` is not between 0.0 and 1.0. 47 | pub fn stretch, I: ?Sized>(draw: &DrawContext, empty: &I, 48 | full: &I, progress: f32) 49 | { 50 | assert!(progress >= 0.0); 51 | assert!(progress <= 1.0); 52 | 53 | // Drawing the empty image, which serves as a background. 54 | image::stretch(draw, empty); 55 | 56 | // The top image will be split in 4 rectangles, one for each quater (top-left, top-right, 57 | // bottom-left, bottom-right). These 4 rectangles are themselves split into two triangles each. 58 | // By adjusting the positions and uv coordinates of each triangle, we can show a progression. 59 | 60 | // Drawing the top-left triangle of each rectangle. 61 | for num in 0 .. 4 { 62 | let local_percent = (progress - 0.25 * num as f32) / 0.125; 63 | if local_percent <= 0.0 { continue; } 64 | let local_percent = if local_percent >= 1.0 { 1.0 } else { local_percent }; 65 | 66 | let local_matrix = Matrix::translate(1.0, 1.0); 67 | let local_matrix = Matrix::scale_wh(0.5 * local_percent, 0.5) * local_matrix; 68 | let local_matrix = Matrix::rotate(num as f32 * -3.141592 * 0.5) * local_matrix; 69 | 70 | let (uv1, uv3) = match num { 71 | 0 => ([0.5, 1.0], [0.5 + 0.5 * local_percent, 1.0]), 72 | 1 => ([1.0, 0.5], [1.0, 0.5 - 0.5 * local_percent]), 73 | 2 => ([0.5, 0.0], [0.5 - 0.5 * local_percent, 0.0]), 74 | 3 => ([0.0, 0.5], [0.0, 0.5 + 0.5 * local_percent]), 75 | _ => unreachable!() 76 | }; 77 | 78 | draw.draw().draw_triangle(full, &(draw.matrix() * local_matrix), [uv1, [0.5, 0.5], uv3]); 79 | } 80 | 81 | // Drawing the bottom-right image of each rectangle. 82 | for num in 0 .. 4 { 83 | let local_percent = (progress - 0.125 - 0.25 * num as f32) / 0.125; 84 | if local_percent <= 0.0 { continue; } 85 | let local_percent = if local_percent >= 1.0 { 1.0 } else { local_percent }; 86 | 87 | let local_matrix = Matrix::translate(1.0, 1.0); 88 | let local_matrix = Matrix::scale_wh(0.5 * local_percent, 0.5) * local_matrix; 89 | let local_matrix = Matrix::skew_x(-3.141592 / 4.0) * local_matrix; 90 | let local_matrix = Matrix::rotate((num + 1) as f32 * -3.141592 * 0.5) * local_matrix; 91 | 92 | let (uv1, uv3) = match num { 93 | 0 => ([1.0, 1.0], [1.0, 1.0 - 0.5 * local_percent]), 94 | 1 => ([1.0, 0.0], [1.0 - 0.5 * local_percent, 0.0]), 95 | 2 => ([0.0, 0.0], [0.0, 0.0 + 0.5 * local_percent]), 96 | 3 => ([0.0, 1.0], [0.0 + 0.5 * local_percent, 1.0]), 97 | _ => unreachable!() 98 | }; 99 | 100 | draw.draw().draw_triangle(full, &(draw.matrix() * local_matrix), [uv1, [0.5, 0.5], uv3]); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/widgets/image.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 immi Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | //! Non-interactive images used for decoration purposes. 9 | //! 10 | //! If you want to use an image as a button, see the other modules. 11 | 12 | use Alignment; 13 | use DrawImage; 14 | use DrawContext; 15 | 16 | /// Decreases the size of the image if necessary until it fits in the context, then draws it. 17 | pub fn draw, I: ?Sized>(draw: &DrawContext, image_name: &I, 18 | alignment: &Alignment) 19 | { 20 | let draw = draw.animation_stop(); 21 | 22 | let ratio = draw.draw().get_image_width_per_height(image_name); 23 | stretch(&draw.enforce_aspect_ratio_downscale(ratio, alignment), image_name) 24 | } 25 | 26 | /// Stretches the image if necessary so that it corresponds to the context's area, then draws it. 27 | pub fn stretch, I: ?Sized>(draw: &DrawContext, image_name: &I) { 28 | if !draw.cursor_hovered_widget() { 29 | if draw.is_cursor_hovering() { 30 | draw.set_cursor_hovered_widget(); 31 | } 32 | } 33 | 34 | draw.draw().draw_image(image_name, &draw.matrix()); 35 | } 36 | 37 | /// Increases the size of the image until it covers the context, then draws it. 38 | pub fn cover, I: ?Sized>(draw: &DrawContext, image_name: &I, 39 | alignment: &Alignment) 40 | { 41 | let draw = draw.animation_stop(); 42 | let ratio = draw.draw().get_image_width_per_height(image_name); 43 | stretch(&draw.enforce_aspect_ratio_upscale(ratio, alignment), image_name) 44 | } 45 | -------------------------------------------------------------------------------- /src/widgets/image9.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 immi Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | //! A 9-parts image is an image that can be stretched while still looking good. 9 | //! 10 | //! The image is split into 9 parts: the four corners, the four borders, and the middle. The 11 | //! dimensions are given by the `top_percent`, `right_percent`, `bottom_percent` and 12 | //! `left_percent` parameters. 13 | //! 14 | //! The whole context's area' is then filled with this image. The corners will always keep their 15 | //! aspect ratio, the top and bottom borders will be stretched horizontally, the left and 16 | //! right borders will be stretched vertically, and the middle will be stretched. 17 | //! 18 | //! The `left_border_percent` parameter is used to determine the percentage of the area that 19 | //! should be occupied by the left border of the image. The order borders are automatically 20 | //! calculated by maintaining the correct aspect ratio. 21 | 22 | use Alignment; 23 | use DrawImage; 24 | use DrawContext; 25 | 26 | /// Draws a 9-parts image. 27 | /// 28 | /// # Panic 29 | /// 30 | /// - Panics if `top_percent + bottom_percent > 1.0` or `left_percent + right_percent > 1.0`. 31 | /// 32 | pub fn draw, I: ?Sized>(draw: &DrawContext, left_border_percent: f32, 33 | image_name: &I, top_percent: f32, right_percent: f32, 34 | bottom_percent: f32, left_percent: f32) 35 | { 36 | assert!(top_percent + bottom_percent <= 1.0); 37 | assert!(left_percent + right_percent <= 1.0); 38 | 39 | let image_width_per_height = draw.draw().get_image_width_per_height(image_name); 40 | 41 | let top_border_percent = left_border_percent * top_percent / left_percent * draw.width_per_height() / image_width_per_height; 42 | let right_border_percent = top_border_percent * right_percent / top_percent / draw.width_per_height() * image_width_per_height; 43 | let bottom_border_percent = right_border_percent * bottom_percent / right_percent * draw.width_per_height() / image_width_per_height; 44 | 45 | // top left 46 | { 47 | let ctxt = draw.rescale(left_border_percent, top_border_percent, &Alignment::top_left()); 48 | draw.draw().draw_image_uv(image_name, &ctxt.matrix(), [0.0, 1.0], [left_percent, 1.0], 49 | [left_percent, 1.0 - top_percent], [0.0, 1.0 - top_percent]); 50 | } 51 | 52 | // top right 53 | { 54 | let ctxt = draw.rescale(right_border_percent, top_border_percent, &Alignment::top_right()); 55 | draw.draw().draw_image_uv(image_name, &ctxt.matrix(), [1.0 - right_percent, 1.0], [1.0, 1.0], 56 | [1.0, 1.0 - top_percent], [1.0 - right_percent, 1.0 - top_percent]); 57 | } 58 | 59 | // bottom right 60 | { 61 | let ctxt = draw.rescale(right_border_percent, bottom_border_percent, &Alignment::bottom_right()); 62 | draw.draw().draw_image_uv(image_name, &ctxt.matrix(), [1.0 - right_percent, bottom_percent], 63 | [1.0, bottom_percent], [1.0, 0.0], [1.0 - right_percent, 0.0]); 64 | } 65 | 66 | // bottom left 67 | { 68 | let ctxt = draw.rescale(left_border_percent, bottom_border_percent, &Alignment::bottom_left()); 69 | draw.draw().draw_image_uv(image_name, &ctxt.matrix(), [0.0, bottom_percent], 70 | [left_percent, bottom_percent], [left_percent, 0.0], [0.0, 0.0]); 71 | } 72 | 73 | // top 74 | { 75 | let ctxt = draw.rescale(1.0 - left_border_percent - right_border_percent, top_border_percent, &Alignment::top()); 76 | draw.draw().draw_image_uv(image_name, &ctxt.matrix(), [left_percent, 1.0], [1.0 - right_percent, 1.0], 77 | [1.0 - right_percent, 1.0 - top_percent], [left_percent, 1.0 - top_percent]); 78 | } 79 | 80 | // left 81 | { 82 | let ctxt = draw.rescale(left_border_percent, 1.0 - top_border_percent - bottom_border_percent, &Alignment::left()); 83 | draw.draw().draw_image_uv(image_name, &ctxt.matrix(), [0.0, 1.0 - top_percent], [left_percent, 1.0 - top_percent], 84 | [left_percent, bottom_percent], [0.0, bottom_percent]); 85 | } 86 | 87 | // bottom 88 | { 89 | let ctxt = draw.rescale(1.0 - left_border_percent - right_border_percent, bottom_border_percent, &Alignment::bottom()); 90 | draw.draw().draw_image_uv(image_name, &ctxt.matrix(), [left_percent, bottom_percent], [1.0 - right_percent, bottom_percent], 91 | [1.0 - right_percent, 0.0], [left_percent, 0.0]); 92 | } 93 | 94 | // right 95 | { 96 | let ctxt = draw.rescale(right_border_percent, 1.0 - top_border_percent - bottom_border_percent, &Alignment::right()); 97 | draw.draw().draw_image_uv(image_name, &ctxt.matrix(), [1.0 - right_percent, 1.0 - top_percent], 98 | [1.0, 1.0 - top_percent], [1.0, bottom_percent], [1.0 - right_percent, bottom_percent]); 99 | } 100 | 101 | // middle 102 | { 103 | let ctxt = draw.rescale(1.0 - left_border_percent - right_border_percent, 104 | 1.0 - top_border_percent - bottom_border_percent, 105 | &Alignment::center()); 106 | draw.draw().draw_image_uv(image_name, &ctxt.matrix(), [left_percent, 1.0 - top_percent], 107 | [1.0 - right_percent, 1.0 - top_percent], [1.0 - right_percent, bottom_percent], 108 | [left_percent, bottom_percent]); 109 | } 110 | 111 | if !draw.cursor_hovered_widget() { 112 | if draw.is_cursor_hovering() { 113 | draw.set_cursor_hovered_widget(); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/widgets/image9_button.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 immi Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | //! Same as `image9`, except that the image is clickable. 9 | 10 | use DrawImage; 11 | use DrawContext; 12 | use UiState; 13 | 14 | use widgets::Interaction; 15 | use widgets::image9; 16 | 17 | /// Same as `image9::draw`, except that the image is clickable. You can specify different images 18 | /// for when the button is non-hovered, hovered, or active. 19 | pub fn draw, I: ?Sized>(draw: &DrawContext, ui_state: &mut UiState, 20 | left_border_percent: f32, normal_image: &I, 21 | hovered_image: &I, active_image: &I, 22 | top_percent: f32, right_percent: f32, bottom_percent: f32, 23 | left_percent: f32) -> Interaction 24 | { 25 | let widget_id = draw.reserve_widget_id(); 26 | 27 | if draw.is_cursor_hovering() { 28 | if Some(widget_id.clone()) == ui_state.active_widget { 29 | image9::draw(draw, left_border_percent, active_image, top_percent, right_percent, 30 | bottom_percent, left_percent); 31 | 32 | if draw.cursor_was_released() { 33 | ui_state.active_widget = None; 34 | Interaction::Clicked 35 | } else { 36 | Interaction::None 37 | } 38 | 39 | } else if draw.cursor_was_pressed() { 40 | image9::draw(draw, left_border_percent, active_image, top_percent, right_percent, 41 | bottom_percent, left_percent); 42 | ui_state.active_widget = Some(widget_id.clone()); 43 | Interaction::None 44 | 45 | } else { 46 | image9::draw(draw, left_border_percent, hovered_image, top_percent, right_percent, 47 | bottom_percent, left_percent); 48 | Interaction::None 49 | } 50 | 51 | } else { 52 | image9::draw(draw, left_border_percent, normal_image, top_percent, right_percent, 53 | bottom_percent, left_percent); 54 | Interaction::None 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/widgets/image_button.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 immi Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | //! Image buttons are images that are clickable. 9 | //! 10 | //! You can specify different images for when the button is in a normal state, a hovered state, 11 | //! or an active state. 12 | //! 13 | //! All the functions in this module return an `Interaction` object that indicates whether they 14 | //! were clicked. 15 | 16 | use Alignment; 17 | use DrawImage; 18 | use DrawContext; 19 | use UiState; 20 | 21 | use widgets::Interaction; 22 | 23 | /// Same as `image::draw`, except that the image is clickable. 24 | pub fn draw, I: ?Sized>(draw: &DrawContext, ui_state: &mut UiState, 25 | normal_image: &I, hovered_image: &I, 26 | active_image: &I, alignment: &Alignment) 27 | -> Interaction 28 | { 29 | let draw = draw.animation_stop(); 30 | let ratio = draw.draw().get_image_width_per_height(normal_image); 31 | stretch(&draw.enforce_aspect_ratio_downscale(ratio, alignment), ui_state, normal_image, 32 | hovered_image, active_image) 33 | } 34 | 35 | /// Same as `image::stretch`, except that the image is clickable. 36 | pub fn stretch, I: ?Sized>(draw: &DrawContext, ui_state: &mut UiState, 37 | normal_image: &I, hovered_image: &I, 38 | active_image: &I) -> Interaction 39 | { 40 | let widget_id = draw.reserve_widget_id(); 41 | 42 | if draw.is_cursor_hovering() { 43 | draw.set_cursor_hovered_widget(); 44 | 45 | if Some(widget_id.clone()) == ui_state.active_widget { 46 | draw.draw().draw_image(active_image, &draw.matrix()); 47 | 48 | if draw.cursor_was_released() { 49 | ui_state.active_widget = None; 50 | Interaction::Clicked 51 | } else { 52 | Interaction::None 53 | } 54 | 55 | } else if draw.cursor_was_pressed() { 56 | draw.draw().draw_image(active_image, &draw.matrix()); 57 | ui_state.active_widget = Some(widget_id.clone()); 58 | Interaction::None 59 | 60 | } else { 61 | draw.draw().draw_image(hovered_image, &draw.matrix()); 62 | Interaction::None 63 | } 64 | 65 | } else { 66 | draw.draw().draw_image(normal_image, &draw.matrix()); 67 | Interaction::None 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/widgets/label.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 immi Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | //! A label is a single line of text. 9 | //! 10 | //! You usually want to use the `flow` function. 11 | 12 | use std::mem; 13 | 14 | use Alignment; 15 | use DrawText; 16 | use DrawContext; 17 | use HorizontalAlignment; 18 | use matrix::Matrix; 19 | 20 | /// Draws text. The text will always have the same height as the context and will stretch 21 | /// horizontally as needed to have a correct aspect ratio. 22 | /// 23 | /// This is usually the function that you want in order to draw text. Even though the text 24 | /// can overflow its container if it is too long, it is usually visually better to have an 25 | /// overflow than to have multiple texts of different heights when they should be the same. 26 | pub fn flow, T: ?Sized>(draw: &DrawContext, text_style: &T, text: &str, 27 | alignment: &HorizontalAlignment) 28 | { 29 | let draw = draw.animation_stop(); 30 | helper(&draw, text_style, text, |ratio| { 31 | let current_width_per_height = draw.width_per_height(); 32 | let draw = draw.horizontal_rescale(ratio / current_width_per_height, &alignment); 33 | 34 | if !draw.cursor_hovered_widget() { 35 | if draw.is_cursor_hovering() { 36 | draw.set_cursor_hovered_widget(); 37 | } 38 | } 39 | 40 | draw.matrix() 41 | }) 42 | } 43 | 44 | /// Draws text. The text will be sized so that it is entirely contained within the context, and 45 | /// either its width or its height is equal to the width or the height of the context. 46 | pub fn contain, T: ?Sized>(draw: &DrawContext, text_style: &T, text: &str, 47 | alignment: &Alignment) 48 | { 49 | let draw = draw.animation_stop(); 50 | helper(&draw, text_style, text, |ratio| { 51 | let draw = draw.enforce_aspect_ratio_downscale(ratio, alignment); 52 | 53 | if !draw.cursor_hovered_widget() { 54 | if draw.is_cursor_hovering() { 55 | draw.set_cursor_hovered_widget(); 56 | } 57 | } 58 | 59 | draw.matrix() 60 | }) 61 | } 62 | 63 | /// Draws text. The text will be sized so that it entirely covers the context, and either its 64 | /// width or its height is equal to the width or the height of the context. 65 | pub fn cover, T: ?Sized>(draw: &DrawContext, text_style: &T, text: &str, 66 | alignment: &Alignment) 67 | { 68 | let draw = draw.animation_stop(); 69 | helper(&draw, text_style, text, |ratio| { 70 | let draw = draw.enforce_aspect_ratio_upscale(ratio, alignment); 71 | 72 | if !draw.cursor_hovered_widget() { 73 | if draw.is_cursor_hovering() { 74 | draw.set_cursor_hovered_widget(); 75 | } 76 | } 77 | 78 | draw.matrix() 79 | }) 80 | } 81 | 82 | fn helper, T: ?Sized, F>(draw: &DrawContext, text_style: &T, text: &str, 83 | final_matrix: F) 84 | where F: FnOnce(f32) -> Matrix 85 | { 86 | let mut glyphs: Vec<(char, Matrix)> = Vec::with_capacity(text.len()); 87 | 88 | let mut previous_chr = None; 89 | let mut x = 0.0; 90 | for chr in text.chars() { 91 | let glyph_infos = draw.draw().glyph_infos(text_style, chr); 92 | let kerning = match mem::replace(&mut previous_chr, Some((chr, glyph_infos))) { 93 | Some((prev, _)) => draw.draw().kerning(text_style, prev, chr), 94 | None => 0.0 95 | }; 96 | 97 | x += kerning; 98 | 99 | let matrix = Matrix::translate(x + glyph_infos.x_offset, 100 | glyph_infos.y_offset - glyph_infos.height) 101 | * Matrix::scale_wh(glyph_infos.width, glyph_infos.height) 102 | * Matrix::translate(0.5, 0.5) 103 | * Matrix::scale(0.5); 104 | 105 | glyphs.push((chr, matrix)); 106 | x += glyph_infos.x_advance; 107 | } 108 | 109 | if let Some((_, prev_infos)) = previous_chr { 110 | x -= prev_infos.x_advance; 111 | x += prev_infos.x_offset; 112 | x += prev_infos.width; 113 | } 114 | 115 | // `x` now contains the width of the text in ems. 116 | 117 | // So far the matrix of each character is in a coordinate system where 1.0 unit is equal to 1.0 118 | // EM and the bottom-left corner of the first glyph is 0.0. Y=1.0 is the top of the line of 119 | // text. We have to adjust this coordinates system for the final output. 120 | let recenter_matrix = Matrix::scale_wh(2.0 / x, 2.0) 121 | * Matrix::translate(-x / 2.0, -0.5); 122 | 123 | let final_matrix = final_matrix(x); 124 | 125 | for (chr, matrix) in glyphs.into_iter() { 126 | draw.draw().draw_glyph(text_style, chr, &(final_matrix * recenter_matrix * matrix)); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/widgets/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 immi Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | //! The widgets that can be drawn. 9 | //! 10 | //! Each module corresponds to a widget. See the individual module documentations for more info. 11 | 12 | pub mod circular_progress_bar; 13 | pub mod image; 14 | pub mod image9; 15 | pub mod image_button; 16 | pub mod image9_button; 17 | pub mod label; 18 | pub mod progress_bar; 19 | 20 | /// Whether the cursor clicked on the widget. 21 | #[derive(Debug, Clone, PartialEq, Eq)] 22 | #[must_use] 23 | pub enum Interaction { 24 | /// The cursor clicked. 25 | Clicked, 26 | /// The cursor didn't click. 27 | None, 28 | } 29 | 30 | impl Interaction { 31 | /// Returns `true` if equal to `Clicked`. This function is useful so that you don't have to 32 | /// import the enum in scope. 33 | #[inline] 34 | pub fn clicked(&self) -> bool { 35 | match self { 36 | &Interaction::Clicked => true, 37 | _ => false, 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/widgets/progress_bar.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 immi Developers 2 | // 3 | // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 6 | // copied, modified, or distributed except according to those terms. 7 | 8 | //! A progress bar is a rectangle that fills itself to indicate some sort of progression. 9 | //! 10 | //! A widget like this is composed of two images: 11 | //! 12 | //! - The widget when empty. 13 | //! - The widget when full. Since this one is drawn over the previous one, it can also just be the 14 | //! difference between empty and full. 15 | //! 16 | 17 | use Alignment; 18 | use DrawImage; 19 | use DrawContext; 20 | use HorizontalAlignment; 21 | 22 | use widgets::image; 23 | 24 | /// Draws a progress bar and keeps the aspect ratio of the empty image. 25 | /// 26 | /// If the `full` image doesn't have the same aspect ratio, it will be stretched. 27 | /// 28 | /// # Panic 29 | /// 30 | /// Panicks if `progress` is not between 0.0 and 1.0. 31 | #[inline] 32 | pub fn draw, I: ?Sized>(draw: &DrawContext, empty: &I, 33 | full: &I, progress: f32, 34 | progress_direction: &HorizontalAlignment, 35 | alignment: &Alignment) 36 | { 37 | let draw = draw.animation_stop(); 38 | let ratio = draw.draw().get_image_width_per_height(empty); 39 | stretch(&draw.enforce_aspect_ratio_downscale(ratio, alignment), empty, full, progress, 40 | progress_direction) 41 | } 42 | 43 | /// Draws a progress bar, stretching it over the whole area. 44 | /// 45 | /// # Panic 46 | /// 47 | /// Panicks if `progress` is not between 0.0 and 1.0. 48 | pub fn stretch, I: ?Sized>(draw: &DrawContext, empty: &I, 49 | full: &I, progress: f32, 50 | progress_direction: &HorizontalAlignment) 51 | { 52 | assert!(progress >= 0.0); 53 | assert!(progress <= 1.0); 54 | 55 | // Drawing the empty image. 56 | image::stretch(draw, empty); 57 | 58 | // Drawing the full image. 59 | let draw = draw.horizontal_rescale(progress, progress_direction); 60 | draw.draw().draw_image_uv(full, &draw.matrix(), [0.0, 1.0], [progress, 1.0], [progress, 0.0], 61 | [0.0, 0.0]); 62 | } 63 | --------------------------------------------------------------------------------