use super::*;
impl SpriteSheet {
pub fn from_image(image: HtmlImageElement, frame_width: f64, frame_height: f64) -> SpriteSheet {
let total_width: f64 = image.width() as f64;
let total_height: f64 = image.height() as f64;
let columns: u32 = (total_width / frame_width).max(1.0) as u32;
let rows: u32 = (total_height / frame_height).max(1.0) as u32;
SpriteSheet::new(image, frame_width, frame_height, columns, rows)
}
pub fn frame_source(&self, index: u32) -> Rect {
let column: u32 = index % self.get_columns();
let row: u32 = index / self.get_columns();
Rect::new(
column as f64 * self.get_frame_width(),
row as f64 * self.get_frame_height(),
self.get_frame_width(),
self.get_frame_height(),
)
}
pub fn frame(&self, index: u32) -> SpriteFrame {
SpriteFrame::new(self.frame_source(index), SPRITE_DEFAULT_FRAME_DURATION)
}
pub fn animation(
&self,
name: &str,
start: u32,
end: u32,
mode: AnimationMode,
) -> SpriteAnimation {
let frames: Vec<SpriteFrame> = (start..end).map(|index: u32| self.frame(index)).collect();
SpriteAnimation::new(name.to_string(), frames, mode)
}
pub fn draw_frame(
&self,
context: &CanvasRenderingContext2d,
frame_index: u32,
transform: &Transform2D,
) {
let source: Rect = self.frame_source(frame_index);
let rotation: f64 = transform.get_rotation();
let cos: f64 = rotation.cos();
let sin: f64 = rotation.sin();
let scale_x: f64 = transform.get_scale().get_x();
let scale_y: f64 = transform.get_scale().get_y();
let _: Result<(), JsValue> = context.set_transform(
cos * scale_x,
sin * scale_x,
-sin * scale_y,
cos * scale_y,
transform.get_position().get_x(),
transform.get_position().get_y(),
);
let _: Result<(), JsValue> = context
.draw_image_with_html_image_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
self.get_image(),
source.get_x(),
source.get_y(),
source.get_width(),
source.get_height(),
-source.get_width() * 0.5,
-source.get_height() * 0.5,
source.get_width(),
source.get_height(),
);
let _: Result<(), JsValue> = context.set_transform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
}
}
impl Animator {
pub fn create() -> Animator {
Animator::new(AnimationState::Paused, 1)
}
pub fn play(&mut self, animation: SpriteAnimation) {
self.set_current_animation(Some(animation));
self.set_current_frame_index(0);
self.set_elapsed_time(0.0);
self.set_state(AnimationState::Playing);
self.set_direction(1);
}
pub fn pause(&mut self) {
if self.get_state() == AnimationState::Playing {
self.set_state(AnimationState::Paused);
}
}
pub fn resume(&mut self) {
if self.get_state() == AnimationState::Paused {
self.set_state(AnimationState::Playing);
}
}
pub fn stop(&mut self) {
self.set_current_frame_index(0);
self.set_elapsed_time(0.0);
self.set_state(AnimationState::Paused);
self.set_direction(1);
}
pub fn update(&mut self, delta_time: f64) {
if self.get_state() != AnimationState::Playing {
return;
}
let current_frame_index: usize = self.get_current_frame_index();
let (frame_count, current_duration, mode) = {
let Some(animation) = self.get_mut_current_animation().as_ref() else {
return;
};
if animation.get_frames().is_empty() {
return;
}
(
animation.get_frames().len(),
animation.get_frames()[current_frame_index].get_duration(),
animation.get_mode(),
)
};
*self.get_mut_elapsed_time() += delta_time;
if self.get_elapsed_time() < current_duration {
return;
}
self.set_elapsed_time(0.0);
self.advance_frame(frame_count, mode);
}
pub fn current_frame_source(&self) -> Option<Rect> {
let animation: Option<SpriteAnimation> = self.get_current_animation();
let animation: &SpriteAnimation = animation.as_ref()?;
let frame: &SpriteFrame = animation.get_frames().get(self.get_current_frame_index())?;
Some(frame.get_source())
}
fn advance_frame(&mut self, frame_count: usize, mode: AnimationMode) {
match mode {
AnimationMode::Loop => {
self.set_current_frame_index((self.get_current_frame_index() + 1) % frame_count);
}
AnimationMode::Once => {
if self.get_current_frame_index() + 1 < frame_count {
*self.get_mut_current_frame_index() += 1;
} else {
self.set_state(AnimationState::Finished);
}
}
AnimationMode::PingPong => {
if self.get_direction() > 0 {
if self.get_current_frame_index() + 1 < frame_count {
*self.get_mut_current_frame_index() += 1;
} else {
self.set_direction(-1);
if self.get_current_frame_index() > 0 {
*self.get_mut_current_frame_index() -= 1;
}
}
} else if self.get_current_frame_index() > 0 {
*self.get_mut_current_frame_index() -= 1;
} else {
self.set_direction(1);
if self.get_current_frame_index() + 1 < frame_count {
*self.get_mut_current_frame_index() += 1;
}
}
}
}
}
}
impl Updatable for Animator {
fn update(&mut self, delta_time: f64) {
Animator::update(self, delta_time);
}
}
impl Animator {
pub fn draw(
&self,
context: &CanvasRenderingContext2d,
sheet: &SpriteSheet,
transform: &Transform2D,
) {
let Some(source) = self.current_frame_source() else {
return;
};
let rotation: f64 = transform.get_rotation();
let cos: f64 = rotation.cos();
let sin: f64 = rotation.sin();
let scale_x: f64 = if self.get_flip_x() {
-transform.get_scale().get_x()
} else {
transform.get_scale().get_x()
};
let scale_y: f64 = if self.get_flip_y() {
-transform.get_scale().get_y()
} else {
transform.get_scale().get_y()
};
let _: Result<(), JsValue> = context.set_transform(
cos * scale_x,
sin * scale_x,
-sin * scale_y,
cos * scale_y,
transform.get_position().get_x(),
transform.get_position().get_y(),
);
let _: Result<(), JsValue> = context
.draw_image_with_html_image_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
sheet.get_image(),
source.get_x(),
source.get_y(),
source.get_width(),
source.get_height(),
-source.get_width() * 0.5,
-source.get_height() * 0.5,
source.get_width(),
source.get_height(),
);
let _: Result<(), JsValue> = context.set_transform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
}
}
impl Default for Animator {
fn default() -> Animator {
Animator::create()
}
}
impl NineSliceRects {
pub fn get_top_left(&self) -> Rect {
self.get_grid()[NINE_SLICE_ROW_TOP][NINE_SLICE_COL_LEFT]
}
pub fn get_top(&self) -> Rect {
self.get_grid()[NINE_SLICE_ROW_TOP][NINE_SLICE_COL_CENTER]
}
pub fn get_top_right(&self) -> Rect {
self.get_grid()[NINE_SLICE_ROW_TOP][NINE_SLICE_COL_RIGHT]
}
pub fn get_left(&self) -> Rect {
self.get_grid()[NINE_SLICE_ROW_MIDDLE][NINE_SLICE_COL_LEFT]
}
pub fn get_center(&self) -> Rect {
self.get_grid()[NINE_SLICE_ROW_MIDDLE][NINE_SLICE_COL_CENTER]
}
pub fn get_right(&self) -> Rect {
self.get_grid()[NINE_SLICE_ROW_MIDDLE][NINE_SLICE_COL_RIGHT]
}
pub fn get_bottom_left(&self) -> Rect {
self.get_grid()[NINE_SLICE_ROW_BOTTOM][NINE_SLICE_COL_LEFT]
}
pub fn get_bottom(&self) -> Rect {
self.get_grid()[NINE_SLICE_ROW_BOTTOM][NINE_SLICE_COL_CENTER]
}
pub fn get_bottom_right(&self) -> Rect {
self.get_grid()[NINE_SLICE_ROW_BOTTOM][NINE_SLICE_COL_RIGHT]
}
pub fn to_vec(&self) -> Vec<Rect> {
let grid: [[Rect; 3]; 3] = self.get_grid();
vec![
grid[NINE_SLICE_ROW_TOP][NINE_SLICE_COL_LEFT],
grid[NINE_SLICE_ROW_TOP][NINE_SLICE_COL_CENTER],
grid[NINE_SLICE_ROW_TOP][NINE_SLICE_COL_RIGHT],
grid[NINE_SLICE_ROW_MIDDLE][NINE_SLICE_COL_LEFT],
grid[NINE_SLICE_ROW_MIDDLE][NINE_SLICE_COL_CENTER],
grid[NINE_SLICE_ROW_MIDDLE][NINE_SLICE_COL_RIGHT],
grid[NINE_SLICE_ROW_BOTTOM][NINE_SLICE_COL_LEFT],
grid[NINE_SLICE_ROW_BOTTOM][NINE_SLICE_COL_CENTER],
grid[NINE_SLICE_ROW_BOTTOM][NINE_SLICE_COL_RIGHT],
]
}
}
impl NineSliceInsets {
pub fn source_rects(&self, source: Rect) -> NineSliceRects {
let width: f64 = Numeric::clamp(source.get_width(), 0.0, f64::MAX);
let height: f64 = Numeric::clamp(source.get_height(), 0.0, f64::MAX);
let left: f64 = Numeric::clamp(self.get_left(), 0.0, width);
let right: f64 = Numeric::clamp(self.get_right(), 0.0, width - left);
let top: f64 = Numeric::clamp(self.get_top(), 0.0, height);
let bottom: f64 = Numeric::clamp(self.get_bottom(), 0.0, height - top);
let center_x: f64 = source.get_x() + left;
let center_y: f64 = source.get_y() + top;
let center_width: f64 = width - left - right;
let center_height: f64 = height - top - bottom;
let right_x: f64 = center_x + center_width;
let bottom_y: f64 = center_y + center_height;
NineSliceRects::new([
[
Rect::new(source.get_x(), source.get_y(), left, top),
Rect::new(center_x, source.get_y(), center_width, top),
Rect::new(right_x, source.get_y(), right, top),
],
[
Rect::new(source.get_x(), center_y, left, center_height),
Rect::new(center_x, center_y, center_width, center_height),
Rect::new(right_x, center_y, right, center_height),
],
[
Rect::new(source.get_x(), bottom_y, left, bottom),
Rect::new(center_x, bottom_y, center_width, bottom),
Rect::new(right_x, bottom_y, right, bottom),
],
])
}
pub fn dest_rects(&self, dest: Rect) -> NineSliceRects {
let left: f64 = Numeric::clamp(self.get_left(), 0.0, f64::MAX);
let right: f64 = Numeric::clamp(self.get_right(), 0.0, f64::MAX);
let top: f64 = Numeric::clamp(self.get_top(), 0.0, f64::MAX);
let bottom: f64 = Numeric::clamp(self.get_bottom(), 0.0, f64::MAX);
let left_edge: f64 = Numeric::clamp(left, 0.0, dest.get_width());
let right_edge: f64 = Numeric::clamp(right, 0.0, dest.get_width() - left_edge);
let top_edge: f64 = Numeric::clamp(top, 0.0, dest.get_height());
let bottom_edge: f64 = Numeric::clamp(bottom, 0.0, dest.get_height() - top_edge);
let center_x: f64 = dest.get_x() + left_edge;
let center_y: f64 = dest.get_y() + top_edge;
let center_width: f64 = dest.get_width() - left_edge - right_edge;
let center_height: f64 = dest.get_height() - top_edge - bottom_edge;
let right_x: f64 = center_x + center_width;
let bottom_y: f64 = center_y + center_height;
NineSliceRects::new([
[
Rect::new(dest.get_x(), dest.get_y(), left_edge, top_edge),
Rect::new(center_x, dest.get_y(), center_width, top_edge),
Rect::new(right_x, dest.get_y(), right_edge, top_edge),
],
[
Rect::new(dest.get_x(), center_y, left_edge, center_height),
Rect::new(center_x, center_y, center_width, center_height),
Rect::new(right_x, center_y, right_edge, center_height),
],
[
Rect::new(dest.get_x(), bottom_y, left_edge, bottom_edge),
Rect::new(center_x, bottom_y, center_width, bottom_edge),
Rect::new(right_x, bottom_y, right_edge, bottom_edge),
],
])
}
}
impl NineSlice {
pub fn from_image(image: HtmlImageElement, border: f64) -> NineSlice {
NineSlice::new(image, NineSliceInsets::new(border, border, border, border))
}
pub fn source_rects(&self) -> NineSliceRects {
let image: &HtmlImageElement = &self.get_image();
let source: Rect = Rect::new(0.0, 0.0, image.width() as f64, image.height() as f64);
self.get_insets().source_rects(source)
}
pub fn dest_rects(&self, dest: Rect) -> NineSliceRects {
self.get_insets().dest_rects(dest)
}
pub fn draw_into(&self, context: &CanvasRenderingContext2d, dest: Rect) {
let sources: Vec<Rect> = self.source_rects().to_vec();
let dests: Vec<Rect> = self.dest_rects(dest).to_vec();
let image: &HtmlImageElement = &self.get_image();
for index in 0..sources.len() {
let source: Rect = sources[index];
let target: Rect = dests[index];
let _: Result<(), JsValue> = context
.draw_image_with_html_image_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
image,
source.get_x(),
source.get_y(),
source.get_width(),
source.get_height(),
target.get_x(),
target.get_y(),
target.get_width(),
target.get_height(),
);
}
}
pub fn record(
&self,
list: &mut DrawList,
dest_position: Vector2D,
dest_width: f64,
dest_height: f64,
) {
list.get_mut_commands().push(DrawCommand::DrawNineSlice {
image: self.get_image(),
insets: self.get_insets(),
dest_position,
dest_width,
dest_height,
});
}
}
impl AtlasRegions {
pub fn insert(&mut self, name: &str, region: Rect) {
self.get_mut_regions().insert(name.to_string(), region);
}
pub fn get(&self, name: &str) -> Option<Rect> {
self.get_regions().get(name).copied()
}
pub fn contains(&self, name: &str) -> bool {
self.get_regions().contains_key(name)
}
pub fn len(&self) -> usize {
self.get_regions().len()
}
pub fn is_empty(&self) -> bool {
self.get_regions().is_empty()
}
pub fn names(&self) -> Vec<String> {
self.get_regions().keys().cloned().collect()
}
}
impl SpriteAtlas {
pub fn create(image: HtmlImageElement) -> SpriteAtlas {
SpriteAtlas::new(image, AtlasRegions::default())
}
pub fn insert(&mut self, name: &str, region: Rect) {
self.get_mut_regions().insert(name, region);
}
pub fn get(&self, name: &str) -> Option<Rect> {
self.get_regions().get(name)
}
pub fn contains(&self, name: &str) -> bool {
self.get_regions().contains(name)
}
pub fn len(&self) -> usize {
self.get_regions().len()
}
pub fn is_empty(&self) -> bool {
self.get_regions().is_empty()
}
pub fn uv(&self, region: Rect) -> UvRect {
let image: &HtmlImageElement = &self.get_image();
let width: f64 = image.natural_width() as f64;
let height: f64 = image.natural_height() as f64;
Self::normalize_uv(region, width, height)
}
pub fn normalize_uv(region: Rect, width: f64, height: f64) -> UvRect {
if width <= 0.0 || height <= 0.0 {
return UvRect::new(0.0, 0.0, 0.0, 0.0);
}
UvRect::new(
region.get_x() / width,
region.get_y() / height,
(region.get_x() + region.get_width()) / width,
(region.get_y() + region.get_height()) / height,
)
}
pub fn draw(&self, context: &CanvasRenderingContext2d, name: &str, dest: Rect) {
let Some(source) = self.get(name) else {
return;
};
let _: Result<(), JsValue> = context
.draw_image_with_html_image_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
&self.get_image(),
source.get_x(),
source.get_y(),
source.get_width(),
source.get_height(),
dest.get_x(),
dest.get_y(),
dest.get_width(),
dest.get_height(),
);
}
pub fn record(
&self,
list: &mut DrawList,
name: &str,
dest_position: Vector2D,
dest_width: f64,
dest_height: f64,
) {
let Some(source) = self.get(name) else {
return;
};
list.get_mut_commands().push(DrawCommand::DrawAtlasRegion {
image: self.get_image(),
source,
dest_position,
dest_width,
dest_height,
});
}
}