1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
use std::marker::PhantomData; use amethyst_core::ecs::{ prelude::{ Component, DenseVecStorage, Entities, Entity, FlaggedStorage, Join, ReadStorage, World, }, shred::{ResourceId, SystemData}, }; use serde::{Deserialize, Serialize}; use super::{Anchor, ScaleMode, Stretch}; /// Utility `SystemData` for finding UI entities based on `UiTransform` id #[derive(SystemData)] #[allow(missing_debug_implementations)] pub struct UiFinder<'a> { entities: Entities<'a>, storage: ReadStorage<'a, UiTransform>, } impl<'a> UiFinder<'a> { /// Find the `UiTransform` entity with the given id pub fn find(&self, id: &str) -> Option<Entity> { (&*self.entities, &self.storage) .join() .find(|(_, transform)| transform.id == id) .map(|(entity, _)| entity) } } /// The UiTransform represents the transformation of a ui element. /// Values are in pixel and the position is calculated from the bottom left of the screen /// to the center of the ui element's area. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct UiTransform { /// An identifier. Serves no purpose other than to help you distinguish between UI elements. pub id: String, /// Indicates where the element sits, relative to the parent (or to the screen, if there is no parent) pub anchor: Anchor, /// Indicates where the element sits, relative to itself pub pivot: Anchor, /// If a child ui element needs to fill its parent this can be used to stretch it to the appropriate size. pub stretch: Stretch, /// X coordinate, 0 is the left edge of the screen. If scale_mode is set to pixel then the width of the /// screen in pixel is the right edge. If scale_mode is percent then the right edge is 1. /// /// Centered in the middle of the ui element. pub local_x: f32, /// Y coordinate, 0 is the bottom edge of the screen. If scale_mode is set to pixel then the height of the /// screen in pixel is the top edge. If scale_mode is percent then the top edge is 1. /// /// Centered in the middle of the ui element. pub local_y: f32, /// Z order, entities with a higher Z order will be rendered on top of entities with a lower /// Z order. pub local_z: f32, /// The width of this UI element. pub width: f32, /// The height of this UI element. pub height: f32, /// Global x position set by the `UiTransformSystem`. pub(crate) pixel_x: f32, /// Global y position set by the `UiTransformSystem`. pub(crate) pixel_y: f32, /// Global z position set by the `UiTransformSystem`. pub(crate) global_z: f32, /// Width in pixels, used for rendering. Duplicate of `width` if `scale_mode == ScaleMode::Pixel`. pub(crate) pixel_width: f32, /// Height in pixels, used for rendering. Duplicate of `height` if `scale_mode == ScaleMode::Pixel`. pub(crate) pixel_height: f32, /// The scale mode indicates if the position is in pixel or is relative (%) (WIP!) to the parent's size. pub scale_mode: ScaleMode, /// Indicates if actions on the ui can go through this element. /// If set to false, the element will behaves as if it was transparent and will let events go to /// the next element (for example, the text on a button). pub opaque: bool, /// A private field to keep this from being initialized without new. pd: PhantomData<()>, } impl UiTransform { /// Creates a new UiTransform. /// By default, it is considered opaque. pub fn new( id: String, anchor: Anchor, pivot: Anchor, x: f32, y: f32, z: f32, width: f32, height: f32, ) -> UiTransform { UiTransform { id, anchor, pivot, stretch: Stretch::NoStretch, local_x: x, local_y: y, local_z: z, width, height, pixel_x: x, pixel_y: y, global_z: z, pixel_width: width, pixel_height: height, scale_mode: ScaleMode::Pixel, opaque: true, pd: PhantomData, } } /// Checks if the input position is in the UiTransform rectangle. /// Uses local coordinates (ignores layouting). pub fn position_inside_local(&self, x: f32, y: f32) -> bool { x > self.local_x - self.width / 2.0 && y > self.local_y - self.height / 2.0 && x < self.local_x + self.width / 2.0 && y < self.local_y + self.height / 2.0 } /// Checks if the input position is in the UiTransform rectangle. pub fn position_inside(&self, x: f32, y: f32) -> bool { x > self.pixel_x - self.pixel_width / 2.0 && y > self.pixel_y - self.pixel_height / 2.0 && x < self.pixel_x + self.pixel_width / 2.0 && y < self.pixel_y + self.pixel_height / 2.0 } /// Renders this UI element by evaluating transform as a percentage of the parent size, /// rather than rendering it with pixel units. pub fn into_percent(mut self) -> Self { self.scale_mode = ScaleMode::Percent; self } /// Sets the opaque variable to false, allowing ui events to go through this ui element. pub fn into_transparent(mut self) -> Self { self.opaque = false; self } /// Adds stretching to this ui element so it can fill its parent. pub fn with_stretch(mut self, stretch: Stretch) -> Self { self.stretch = stretch; self } /// Returns the global x coordinate of this UiTransform as computed by the `UiTransformSystem`. pub fn pixel_x(&self) -> f32 { self.pixel_x } /// Returns the global y coordinate of this UiTransform as computed by the `UiTransformSystem`. pub fn pixel_y(&self) -> f32 { self.pixel_y } /// Returns the global z order of this UiTransform as computed by the `UiTransformSystem`. pub fn global_z(&self) -> f32 { self.global_z } } impl Component for UiTransform { type Storage = FlaggedStorage<Self, DenseVecStorage<Self>>; } #[cfg(test)] mod tests { use super::*; #[test] fn inside_local() { let tr = UiTransform::new( "".to_string(), Anchor::TopLeft, Anchor::Middle, 0.0, 0.0, 0.0, 1.0, 1.0, ); let pos = (-0.49, 0.20); assert!(tr.position_inside_local(pos.0, pos.1)); let pos = (-1.49, 1.20); assert!(!tr.position_inside_local(pos.0, pos.1)); } #[test] fn inside_global() { let tr = UiTransform::new( "".to_string(), Anchor::TopLeft, Anchor::Middle, 0.0, 0.0, 0.0, 1.0, 1.0, ); let pos = (-0.49, 0.20); assert!(tr.position_inside(pos.0, pos.1)); let pos = (-1.49, 1.20); assert!(!tr.position_inside(pos.0, pos.1)); } }