use glyph_brush::{HorizontalAlign, VerticalAlign};
use serde::{Deserialize, Serialize};
#[cfg(feature = "profiler")]
use thread_profiler::profile_scope;
use amethyst_core::{
ecs::prelude::{
BitSet, ComponentEvent, Join, ReadExpect, ReadStorage, ReaderId, System, SystemData, World,
WriteStorage,
},
HierarchyEvent, Parent, ParentHierarchy, SystemDesc,
};
use amethyst_window::ScreenDimensions;
use super::UiTransform;
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
pub enum ScaleMode {
Pixel,
Percent,
}
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
pub enum Anchor {
TopLeft,
TopMiddle,
TopRight,
MiddleLeft,
Middle,
MiddleRight,
BottomLeft,
BottomMiddle,
BottomRight,
}
impl Anchor {
pub fn norm_offset(&self) -> (f32, f32) {
match self {
Anchor::TopLeft => (-0.5, 0.5),
Anchor::TopMiddle => (0.0, 0.5),
Anchor::TopRight => (0.5, 0.5),
Anchor::MiddleLeft => (-0.5, 0.0),
Anchor::Middle => (0.0, 0.0),
Anchor::MiddleRight => (0.5, 0.0),
Anchor::BottomLeft => (-0.5, -0.5),
Anchor::BottomMiddle => (0.0, -0.5),
Anchor::BottomRight => (0.5, -0.5),
}
}
pub(crate) fn vertical_align(&self) -> VerticalAlign {
match self {
Anchor::TopLeft => VerticalAlign::Top,
Anchor::TopMiddle => VerticalAlign::Top,
Anchor::TopRight => VerticalAlign::Top,
Anchor::MiddleLeft => VerticalAlign::Center,
Anchor::Middle => VerticalAlign::Center,
Anchor::MiddleRight => VerticalAlign::Center,
Anchor::BottomLeft => VerticalAlign::Bottom,
Anchor::BottomMiddle => VerticalAlign::Bottom,
Anchor::BottomRight => VerticalAlign::Bottom,
}
}
pub(crate) fn horizontal_align(&self) -> HorizontalAlign {
match self {
Anchor::TopLeft => HorizontalAlign::Left,
Anchor::TopMiddle => HorizontalAlign::Center,
Anchor::TopRight => HorizontalAlign::Right,
Anchor::MiddleLeft => HorizontalAlign::Left,
Anchor::Middle => HorizontalAlign::Center,
Anchor::MiddleRight => HorizontalAlign::Right,
Anchor::BottomLeft => HorizontalAlign::Left,
Anchor::BottomMiddle => HorizontalAlign::Center,
Anchor::BottomRight => HorizontalAlign::Right,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum Stretch {
NoStretch,
X {
x_margin: f32,
},
Y {
y_margin: f32,
},
XY {
x_margin: f32,
y_margin: f32,
keep_aspect_ratio: bool,
},
}
#[derive(Default, Debug)]
pub struct UiTransformSystemDesc;
impl<'a, 'b> SystemDesc<'a, 'b, UiTransformSystem> for UiTransformSystemDesc {
fn build(self, world: &mut World) -> UiTransformSystem {
<UiTransformSystem as System<'_>>::SystemData::setup(world);
let parent_events_id = world.fetch_mut::<ParentHierarchy>().track();
let mut transforms = WriteStorage::<UiTransform>::fetch(&world);
let transform_events_id = transforms.register_reader();
UiTransformSystem::new(transform_events_id, parent_events_id)
}
}
#[derive(Debug)]
pub struct UiTransformSystem {
transform_modified: BitSet,
transform_events_id: ReaderId<ComponentEvent>,
parent_events_id: ReaderId<HierarchyEvent>,
screen_size: (f32, f32),
}
impl UiTransformSystem {
pub fn new(
transform_events_id: ReaderId<ComponentEvent>,
parent_events_id: ReaderId<HierarchyEvent>,
) -> Self {
Self {
transform_modified: BitSet::default(),
transform_events_id,
parent_events_id,
screen_size: (0.0, 0.0),
}
}
}
impl<'a> System<'a> for UiTransformSystem {
type SystemData = (
WriteStorage<'a, UiTransform>,
ReadStorage<'a, Parent>,
ReadExpect<'a, ScreenDimensions>,
ReadExpect<'a, ParentHierarchy>,
);
fn run(&mut self, data: Self::SystemData) {
#[cfg(feature = "profiler")]
profile_scope!("ui_transform_system");
let (mut transforms, parents, screen_dim, hierarchy) = data;
self.transform_modified.clear();
let self_transform_modified = &mut self.transform_modified;
let self_transform_events_id = &mut self.transform_events_id;
transforms
.channel()
.read(self_transform_events_id)
.for_each(|event| match event {
ComponentEvent::Inserted(id) | ComponentEvent::Modified(id) => {
self_transform_modified.add(*id);
}
ComponentEvent::Removed(_id) => {}
});
for event in hierarchy.changed().read(&mut self.parent_events_id) {
if let HierarchyEvent::Modified(entity) = *event {
self_transform_modified.add(entity.id());
}
}
let current_screen_size = (screen_dim.width(), screen_dim.height());
let screen_resized = current_screen_size != self.screen_size;
self.screen_size = current_screen_size;
if screen_resized {
process_root_iter(
(&mut transforms, !&parents).join().map(|i| i.0),
&*screen_dim,
);
} else {
let self_transform_modified = &*self_transform_modified;
process_root_iter(
(&mut transforms, !&parents, self_transform_modified)
.join()
.map(|i| i.0),
&*screen_dim,
);
}
transforms
.channel()
.read(self_transform_events_id)
.for_each(|event| {
if let ComponentEvent::Modified(id) = event {
self_transform_modified.add(*id);
}
});
for entity in hierarchy.all() {
{
let self_dirty = self_transform_modified.contains(entity.id());
let parent_entity = match parents.get(*entity) {
Some(p) => p.entity,
None => continue,
};
let parent_dirty = self_transform_modified.contains(parent_entity.id());
if parent_dirty || self_dirty || screen_resized {
let parent_transform_copy = transforms.get(parent_entity).cloned();
let transform = transforms.get_mut(*entity);
let (transform, parent_transform_copy) =
match (transform, parent_transform_copy) {
(Some(v1), Some(v2)) => (v1, v2),
_ => continue,
};
let norm = transform.anchor.norm_offset();
transform.pixel_x =
parent_transform_copy.pixel_x + parent_transform_copy.pixel_width * norm.0;
transform.pixel_y =
parent_transform_copy.pixel_y + parent_transform_copy.pixel_height * norm.1;
transform.global_z = parent_transform_copy.global_z + transform.local_z;
let new_size = match transform.stretch {
Stretch::NoStretch => (transform.width, transform.height),
Stretch::X { x_margin } => (
parent_transform_copy.pixel_width - x_margin * 2.0,
transform.height,
),
Stretch::Y { y_margin } => (
transform.width,
parent_transform_copy.pixel_height - y_margin * 2.0,
),
Stretch::XY {
keep_aspect_ratio: false,
x_margin,
y_margin,
} => (
parent_transform_copy.pixel_width - x_margin * 2.0,
parent_transform_copy.pixel_height - y_margin * 2.0,
),
Stretch::XY {
keep_aspect_ratio: true,
x_margin,
y_margin,
} => {
let scale = f32::min(
(parent_transform_copy.pixel_width - x_margin * 2.0)
/ transform.width,
(parent_transform_copy.pixel_height - y_margin * 2.0)
/ transform.height,
);
(transform.width * scale, transform.height * scale)
}
};
transform.width = new_size.0;
transform.height = new_size.1;
match transform.scale_mode {
ScaleMode::Pixel => {
transform.pixel_x += transform.local_x;
transform.pixel_y += transform.local_y;
transform.pixel_width = transform.width;
transform.pixel_height = transform.height;
}
ScaleMode::Percent => {
transform.pixel_x +=
transform.local_x * parent_transform_copy.pixel_width;
transform.pixel_y +=
transform.local_y * parent_transform_copy.pixel_height;
transform.pixel_width =
transform.width * parent_transform_copy.pixel_width;
transform.pixel_height =
transform.height * parent_transform_copy.pixel_height;
}
}
let pivot_norm = transform.pivot.norm_offset();
transform.pixel_x += transform.pixel_width * -pivot_norm.0;
transform.pixel_y += transform.pixel_height * -pivot_norm.1;
}
}
transforms
.channel()
.read(self_transform_events_id)
.for_each(|event| {
if let ComponentEvent::Modified(id) = event {
self_transform_modified.add(*id);
}
});
}
transforms
.channel()
.read(self_transform_events_id)
.for_each(|event| match event {
ComponentEvent::Inserted(id) | ComponentEvent::Modified(id) => {
self_transform_modified.add(*id);
}
ComponentEvent::Removed(_id) => {}
});
}
}
fn process_root_iter<'a, I>(iter: I, screen_dim: &ScreenDimensions)
where
I: Iterator<Item = &'a mut UiTransform>,
{
for transform in iter {
let norm = transform.anchor.norm_offset();
transform.pixel_x = screen_dim.width() / 2.0 + screen_dim.width() * norm.0;
transform.pixel_y = screen_dim.height() / 2.0 + screen_dim.height() * norm.1;
transform.global_z = transform.local_z;
let new_size = match transform.stretch {
Stretch::NoStretch => (transform.width, transform.height),
Stretch::X { x_margin } => (screen_dim.width() - x_margin * 2.0, transform.height),
Stretch::Y { y_margin } => (transform.width, screen_dim.height() - y_margin * 2.0),
Stretch::XY {
keep_aspect_ratio: false,
x_margin,
y_margin,
} => (
screen_dim.width() - x_margin * 2.0,
screen_dim.height() - y_margin * 2.0,
),
Stretch::XY {
keep_aspect_ratio: true,
x_margin,
y_margin,
} => {
let scale = f32::min(
(screen_dim.width() - x_margin * 2.0) / transform.width,
(screen_dim.height() - y_margin * 2.0) / transform.height,
);
(transform.width * scale, transform.height * scale)
}
};
transform.width = new_size.0;
transform.height = new_size.1;
match transform.scale_mode {
ScaleMode::Pixel => {
transform.pixel_x += transform.local_x;
transform.pixel_y += transform.local_y;
transform.pixel_width = transform.width;
transform.pixel_height = transform.height;
}
ScaleMode::Percent => {
transform.pixel_x += transform.local_x * screen_dim.width();
transform.pixel_y += transform.local_y * screen_dim.height();
transform.pixel_width = transform.width * screen_dim.width();
transform.pixel_height = transform.height * screen_dim.height();
}
}
let pivot_norm = transform.pivot.norm_offset();
transform.pixel_x += transform.pixel_width * -pivot_norm.0;
transform.pixel_y += transform.pixel_height * -pivot_norm.1;
}
}