diff --git a/TODO.md b/TODO.md index 9ab947f..a5ffe6e 100644 --- a/TODO.md +++ b/TODO.md @@ -8,7 +8,6 @@ - Mouse colliders - UI captures input? - No UI zoom scroll -- Preserve aspect for icons - outfitter ## Small jobs diff --git a/assets b/assets index dc5962f..fba4f10 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit dc5962ffb96f6d4c7fc271a1b132865b280b528c +Subproject commit fba4f1083b5a07a10445cf28bcae4bb05c2cede6 diff --git a/content/ui/outfitter.rhai b/content/ui/outfitter.rhai index c92b112..1c3642c 100644 --- a/content/ui/outfitter.rhai +++ b/content/ui/outfitter.rhai @@ -57,6 +57,7 @@ fn init(state) { Anchor::NorthWest ) ); + sprite::preserve_aspect("ship_thumb", true); textbox::add( "ship_name", 10.0, 10.0, @@ -119,6 +120,8 @@ fn init(state) { Anchor::NorthEast ) ); + sprite::preserve_aspect("outfit_thumb", true); + textbox::add( "outfit_name", 16.0, 16.0, diff --git a/crates/render/src/ui/api/functions/sprite.rs b/crates/render/src/ui/api/functions/sprite.rs index e27135e..ddf6bff 100644 --- a/crates/render/src/ui/api/functions/sprite.rs +++ b/crates/render/src/ui/api/functions/sprite.rs @@ -39,6 +39,17 @@ pub fn build_sprite_module(ct_src: Arc, state_src: Rc> }, ); + let state = state_src.clone(); + let _ = FuncRegistration::new("exists") + .with_namespace(FnNamespace::Internal) + .set_into_module(&mut module, move |name: ImmutableString| { + let mut ui_state = state.borrow_mut(); + match ui_state.get_mut_by_name(&name) { + Some(UiElement::Sprite(_)) => true, + _ => false, + } + }); + let state = state_src.clone(); let _ = FuncRegistration::new("remove") .with_namespace(FnNamespace::Internal) @@ -155,15 +166,16 @@ pub fn build_sprite_module(ct_src: Arc, state_src: Rc> }); let state = state_src.clone(); - let _ = FuncRegistration::new("exists") + let _ = FuncRegistration::new("preserve_aspect") .with_namespace(FnNamespace::Internal) - .set_into_module(&mut module, move |name: ImmutableString| { + .set_into_module(&mut module, move |name: ImmutableString, x: bool| { let mut ui_state = state.borrow_mut(); match ui_state.get_mut_by_name(&name) { - Some(UiElement::Sprite(_)) => true, - _ => false, + Some(UiElement::Sprite(s)) => s.set_preserve_aspect(x), + _ => { + error!("called `sprite::set_preserve_aspect` on an invalid name `{name}`") + } } }); - return module; } diff --git a/crates/render/src/ui/api/helpers/rect.rs b/crates/render/src/ui/api/helpers/rect.rs index 59bad45..1bd0bcc 100644 --- a/crates/render/src/ui/api/helpers/rect.rs +++ b/crates/render/src/ui/api/helpers/rect.rs @@ -85,12 +85,11 @@ impl CustomType for Rect { /// Represents a rectangular region, in absolute coordinates relative to the screen center. #[derive(Debug, Clone, Copy)] pub(crate) struct CenteredRect { - /// The position of the top-left corner of this rectangle, in fractional units. - /// (0.0 is left edge of sprite, 1.0 is right edge) + /// The position of the center of this rectangle, in post-scale logical pixels, + /// relative to (0, 0) in the center of the screen. pub pos: Point2, - /// The width and height of this rectangle, in fractional units. - /// 1.0 will be as tall as the sprite, 0.5 will be half as tall + /// The width and height of this rectangle, in post-scale logical pixels. pub dim: Vector2, } diff --git a/crates/render/src/ui/elements/sprite.rs b/crates/render/src/ui/elements/sprite.rs index 40493c0..85c69f1 100644 --- a/crates/render/src/ui/elements/sprite.rs +++ b/crates/render/src/ui/elements/sprite.rs @@ -16,6 +16,10 @@ pub struct Sprite { /// 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, color: Color, @@ -38,6 +42,7 @@ impl Sprite { has_mouse: false, has_click: false, waiting_for_release: false, + preserve_aspect: false, } } @@ -57,15 +62,32 @@ impl Sprite { 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 rect = self + let mut rect = self .rect .to_centered(&state.window, input.ct.get_config().ui_scale); - // TODO: use both dimensions, - // not just height - let anim_state = self.anim.get_texture_idx(); + if self.preserve_aspect { + let rect_aspect = rect.dim.x / rect.dim.y; + let sprite_aspect = input.ct.get_sprite(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),