use std::sync::Arc; use super::super::api::Rect; use crate::{ ui::{api::Color, event::Event}, vertexbuffer::types::UiInstance, RenderInput, RenderState, }; use galactica_content::{Sprite, SpriteAutomaton}; use galactica_util::to_radians; use rhai::ImmutableString; #[derive(Debug, Clone)] pub struct UiSprite { pub anim: SpriteAutomaton, pub name: ImmutableString, /// Sprite angle, in degrees angle: f32, /// If true, this sprite will be scaled to fit in its box without affecting aspect ratio. /// If false, this sprite will be stretched to fit in its box preserve_aspect: bool, rect: Rect, mask: Option<Arc<Sprite>>, color: Color, /// If true, ignore mouse events until click is released waiting_for_release: bool, has_mouse: bool, has_click: bool, } impl UiSprite { pub fn new(name: ImmutableString, sprite: Arc<Sprite>, rect: Rect) -> Self { Self { name, anim: SpriteAutomaton::new(sprite), rect, angle: 0.0, color: Color::new(1.0, 1.0, 1.0, 1.0), mask: None, has_mouse: false, has_click: false, waiting_for_release: false, preserve_aspect: false, } } pub fn set_mask(&mut self, mask: Option<Arc<Sprite>>) { self.mask = mask; } pub fn set_angle(&mut self, angle: f32) { self.angle = angle; } pub fn set_rect(&mut self, rect: Rect) { self.rect = rect; } pub fn set_color(&mut self, color: Color) { self.color = color; } pub fn set_preserve_aspect(&mut self, preserve_aspect: bool) { self.preserve_aspect = preserve_aspect; } pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) { let mut rect = self .rect .to_centered(&state.window, input.ct.config.ui_scale); if self.preserve_aspect { let rect_aspect = rect.dim.x / rect.dim.y; let sprite_aspect = self.anim.get_sprite().aspect; // "wide rect" case => match height, reduce width if rect_aspect > sprite_aspect { let shrink = rect.dim.x - rect.dim.y * sprite_aspect; rect.dim.x -= shrink; // "tall rect" case => match width, reduce height } else if rect_aspect < sprite_aspect { let shrink = rect.dim.y - rect.dim.x / sprite_aspect; rect.dim.y -= shrink; } } let anim_state = self.anim.get_texture_idx(); state.push_ui_buffer(UiInstance { position: rect.pos.into(), angle: to_radians(90.0 + self.angle), dim: rect.dim.into(), color: self.color.as_array(), texture_index: anim_state.texture_index(), texture_fade: anim_state.fade, mask_index: self .mask .as_ref() .map(|x| { let texture_b = x.get_first_frame(); // TODO: animate? [1, texture_b] }) .unwrap_or([0, 0]), }); } pub fn check_events(&mut self, input: &RenderInput, state: &mut RenderState) -> Event { let r = self .rect .to_centered(&state.window, input.ct.config.ui_scale); if self.waiting_for_release && self.has_mouse && !input.player.input.pressed_leftclick() { self.waiting_for_release = false; } if !self.waiting_for_release && self.has_mouse && !self.has_click && input.player.input.pressed_leftclick() { self.has_click = true; return Event::MouseClick; } if self.has_mouse && self.has_click && !input.player.input.pressed_leftclick() { self.has_click = false; return Event::MouseRelease; } // Release mouse when cursor leaves box if self.has_click && !self.has_mouse { self.has_click = false; return Event::MouseRelease; } if r.contains_mouse(input, state) && !self.has_mouse { if input.player.input.pressed_leftclick() { // If we're holding click when the cursor enters, // don't trigger the `Click` event. self.waiting_for_release = true; } self.has_mouse = true; return Event::MouseHover; } if !r.contains_mouse(input, state) && self.has_mouse { self.waiting_for_release = false; self.has_mouse = false; return Event::MouseUnhover; } return Event::None; } pub fn step(&mut self, input: &RenderInput, _state: &mut RenderState) { self.anim.step(input.time_since_last_run); } }