Compare commits

...

3 Commits

Author SHA1 Message Date
Mark 85c4265216
Reworked ui positioning 2024-01-30 17:06:14 -08:00
Mark ca15dcae34
Minor edits 2024-01-30 17:05:03 -08:00
Mark 2c54824619
Updated TODO 2024-01-30 17:03:47 -08:00
15 changed files with 482 additions and 319 deletions

View File

@ -1,10 +1,11 @@
# Specific projects # Specific projects
## Now: ## Now:
- renderer scenes
- outfitter - outfitter
## Small jobs ## Small jobs
- Fix window resizing
- Debug: show mouse position
- 🌟 Better planet desc formatting - 🌟 Better planet desc formatting
- Procedural suns - Procedural suns
- 🌟 Back arrow -> reverse - 🌟 Back arrow -> reverse

2
assets

@ -1 +1 @@
Subproject commit 481c1071a5c08124535daef78d746f6ec58c056e Subproject commit e5898796144c7590c3cf18e14867a5ec47637fd4

View File

@ -94,6 +94,9 @@ file = "ui/landscape-mask.png"
[sprite."ui::outfitbg"] [sprite."ui::outfitbg"]
file = "ui/outfit-bg.png" file = "ui/outfit-bg.png"
[sprite."ui::outfitterbox"]
file = "ui/outfitter-box.png"
[sprite."ui::shopsepbar"] [sprite."ui::shopsepbar"]
file = "ui/shop-sep-bar.png" file = "ui/shop-sep-bar.png"

View File

@ -1,12 +1,10 @@
[ui.status] [ui.status]
# TODO: unified color value # TODO: unified color value
# TODO: anchor
# TODO: bar type: linear/radial # TODO: bar type: linear/radial
# TODO: bar as ui util struct # TODO: bar as ui util struct
# TODO: mouse collider # TODO: mouse collider
# TODO: modular UI (how?) # TODO: modular UI (how?)
# TODO: textbox configuration
# shield_bar.pos = [-19, -19] # shield_bar.pos = [-19, -19]
# shield_bar.diameter = 182 # shield_bar.diameter = 182
@ -24,19 +22,59 @@
[ui.landed] [ui.landed]
frame.sprite = "ui::planet" frame.sprite = "ui::planet"
frame.pos = [0.0, 0.0] frame.rect.pos = [0.0, 0.0]
frame.dim = [800.0, 800.0] frame.rect.dim = [800.0, 800.0]
frame.rect.anchor_self = "center"
frame.rect.anchor_parent = "center"
landscape.mask = "ui::landscapemask" landscape.mask = "ui::landscapemask"
landscape.pos = [32.0, 75.0] landscape.rect.pos = [-350.0, 282.8]
landscape.dim = [344.0, 173.0] landscape.rect.dim = [537.5, 270.31]
landscape.loc_div = 512 landscape.rect.anchor_self = "northwest"
landscape.rect.anchor_parent = "center"
button.sprite = "ui::planet::button" button.sprite = "ui::planet::button"
button.pos = [356, 90] button.rect.pos = [178.12, 254.6]
button.dim = [113.569, 20] button.rect.dim = [175.16, 38.437]
button.loc_div = 512 button.rect.anchor_self = "northwest"
button.rect.anchor_parent = "center"
button.on_mouse_enter.edge = "on:top" button.on_mouse_enter.edge = "on:top"
button.on_mouse_enter.duration = 0.1 button.on_mouse_enter.duration = 0.1
button.on_mouse_leave.edge = "off:top" button.on_mouse_leave.edge = "off:top"
button.on_mouse_leave.duration = 0.1 button.on_mouse_leave.duration = 0.1
planet_name.rect.pos = [-143.89, 273]
planet_name.rect.dim = [121.98, 18.094]
planet_name.rect.anchor_self = "northwest"
planet_name.rect.anchor_parent = "center"
planet_name.font_size = 19
planet_name.line_height = 19
planet_name.align = "center"
planet_desc.rect.pos = [-358.43, -32]
planet_desc.rect.dim = [673.91, 153.75]
planet_desc.rect.anchor_self = "northwest"
planet_desc.rect.anchor_parent = "center"
planet_desc.font_size = 16
planet_desc.line_height = 18
planet_desc.align = "left"
[ui.outfitter]
se_box.sprite = "ui::outfitterbox"
se_box.rect.pos = [-10.0, -10.0]
se_box.rect.dim = [512.0, 337.0] # todo: auto aspect
se_box.rect.anchor_self = "southwest"
se_box.rect.anchor_parent = "southwest"
exit_button.sprite = "ui::button"
exit_button.rect.pos = [279.07, 135.38]
exit_button.rect.dim = [173.44, 45.01]
exit_button.rect.anchor_self = "northwest"
exit_button.rect.anchor_parent = "southwest"
exit_button.on_mouse_enter.edge = "on:top"
exit_button.on_mouse_enter.duration = 0.1
exit_button.on_mouse_leave.edge = "off:top"
exit_button.on_mouse_leave.duration = 0.1

View File

@ -1,5 +1,6 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use nalgebra::{Point2, Vector2}; use nalgebra::{Point2, Vector2};
use serde::Deserialize;
use crate::{handle::SpriteHandle, Content, ContentBuildContext, SectionEdge}; use crate::{handle::SpriteHandle, Content, ContentBuildContext, SectionEdge};
@ -14,6 +15,7 @@ pub(crate) mod syntax {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Ui { pub struct Ui {
pub landed: UiLanded, pub landed: UiLanded,
pub outfitter: UiOutfitter,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -21,17 +23,22 @@ pub(crate) mod syntax {
pub frame: UiSprite, pub frame: UiSprite,
pub landscape: UiSprite, pub landscape: UiSprite,
pub button: UiSprite, pub button: UiSprite,
pub planet_name: UiText,
pub planet_desc: UiText,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct UiSprite { pub struct UiOutfitter {
pub sprite: Option<String>, pub se_box: UiSprite,
pub exit_button: UiSprite,
}
#[derive(Debug, Deserialize)]
pub struct UiRect {
pub pos: [f32; 2], pub pos: [f32; 2],
pub dim: [f32; 2], pub dim: [f32; 2],
pub loc_div: Option<f32>, pub anchor_self: super::UiPositionAnchor,
pub mask: Option<String>, pub anchor_parent: super::UiPositionAnchor,
pub on_mouse_enter: Option<EdgeSpec>,
pub on_mouse_leave: Option<EdgeSpec>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -40,15 +47,53 @@ pub(crate) mod syntax {
pub duration: f32, pub duration: f32,
} }
#[derive(Debug, Deserialize)]
pub struct UiText {
pub rect: UiRect,
pub font_size: f32,
pub line_height: f32,
pub align: super::UiTextAlign,
}
impl UiText {
pub fn build(
self,
_build_context: &ContentBuildContext,
_ct: &Content,
) -> Result<super::UiTextConfig> {
let rect = {
super::UiRect {
pos: Point2::new(self.rect.pos[0], self.rect.pos[1]),
dim: Vector2::new(self.rect.dim[0], self.rect.dim[1]),
anchor_self: self.rect.anchor_self,
anchor_parent: self.rect.anchor_parent,
}
};
return Ok(super::UiTextConfig {
rect,
font_size: self.font_size,
line_height: self.line_height,
align: self.align,
});
}
}
#[derive(Debug, Deserialize)]
pub struct UiSprite {
pub sprite: Option<String>,
pub rect: UiRect,
pub mask: Option<String>,
pub on_mouse_enter: Option<EdgeSpec>,
pub on_mouse_leave: Option<EdgeSpec>,
}
impl UiSprite { impl UiSprite {
pub fn build( pub fn build(
self, self,
build_context: &ContentBuildContext, build_context: &ContentBuildContext,
ct: &Content, ct: &Content,
// If true, this ui sprite will be positioned relative to another
is_child: bool,
// If true, fail if self.sprite is missing. // If true, fail if self.sprite is missing.
// If false, fail if self.sprite exists. // If false, fail if self.sprite exists.
// This is false for sprites that may change---for example, planet landscapes // This is false for sprites that may change---for example, planet landscapes
@ -112,19 +157,12 @@ pub(crate) mod syntax {
} }
}; };
let d = self.loc_div.unwrap_or(1.0);
let rect = { let rect = {
if is_child { super::UiRect {
super::UiSpriteRect::Relative { pos: Point2::new(self.rect.pos[0], self.rect.pos[1]),
pos: Point2::new(self.pos[0], self.pos[1]) / d, dim: Vector2::new(self.rect.dim[0], self.rect.dim[1]),
dim: Vector2::new(self.dim[0], self.dim[1]) / d, anchor_self: self.rect.anchor_self,
} anchor_parent: self.rect.anchor_parent,
} else {
super::UiSpriteRect::Absolute {
pos: Point2::new(self.pos[0], self.pos[1]) / d,
dim: Vector2::new(self.dim[0], self.dim[1]) / d,
}
} }
}; };
@ -139,6 +177,42 @@ pub(crate) mod syntax {
} }
} }
/// How to align text in a text box
#[derive(Debug, Deserialize, Clone, Copy)]
pub enum UiTextAlign {
/// Center-align
#[serde(rename = "center")]
Center,
/// Left-align
#[serde(rename = "left")]
Left,
}
/// How to position a UI sprite
#[derive(Debug, Deserialize, Clone, Copy)]
pub enum UiPositionAnchor {
/// Anchored at center
#[serde(rename = "center")]
Center,
/// Anchored at top-left
#[serde(rename = "northwest")]
NorthWest,
/// Anchored at top-right
#[serde(rename = "northeast")]
NorthEast,
/// Anchored at bottom-left
#[serde(rename = "southwest")]
SouthWest,
/// Anchored at bottom-right
#[serde(rename = "southeast")]
SouthEast,
}
/// UI Configuration /// UI Configuration
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Ui { pub struct Ui {
@ -150,54 +224,35 @@ pub struct Ui {
/// Test button /// Test button
pub landed_button: UiSpriteConfig, pub landed_button: UiSpriteConfig,
/// Landed planet name
pub landed_planet_name: UiTextConfig,
/// Landed planet description
pub landed_planet_desc: UiTextConfig,
/// Outfitter exit button
pub outfitter_exit_button: UiSpriteConfig,
/// Outfitter south-east box
pub outfitter_se_box: UiSpriteConfig,
} }
/// A UI sprite's position /// A UI sprite's position
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum UiSpriteRect { pub struct UiRect {
/// Positioning relative to a parent sprite
Relative {
// Note that both pos and dim include transparent pixels,
// of this sprite AND its parent.
// We use the top left corner here because that's how inkscape
// positions its objects. This makes it very easy to compute position.
// TODO: maybe add anchors here too?
/// The position of this sprite's northeast corner, relative to its parent's NE corner.
/// Positive X is right, positive Y is down.
pos: Point2<f32>,
/// This sprite's w and h, as a fraction of the parent's width and height.
dim: Vector2<f32>,
},
/// Absolute positioning
Absolute {
/// The position of the center of this sprite, in logical pixels, /// The position of the center of this sprite, in logical pixels,
/// with 0, 0 at the center of the screen /// with 0, 0 at the center of the screen
pos: Point2<f32>, pub pos: Point2<f32>,
/// This sprite's w and h, in logical pixels. /// This sprite's w and h, in logical pixels.
dim: Vector2<f32>, pub dim: Vector2<f32>,
},
}
impl UiSpriteRect { /// The point on this sprite that pos is anchored to
/// Get this rectangle's position pub anchor_self: UiPositionAnchor,
pub fn get_pos(&self) -> &Point2<f32> {
match self {
Self::Relative { pos, .. } => pos,
Self::Absolute { pos, .. } => pos,
}
}
/// Get this rectangle's dimensions /// The point on the parent that pos is relative to
pub fn get_dim(&self) -> &Vector2<f32> { pub anchor_parent: UiPositionAnchor,
match self {
Self::Relative { dim, .. } => dim,
Self::Absolute { dim, .. } => dim,
}
}
} }
/// A single UI sprite instance /// A single UI sprite instance
@ -210,7 +265,7 @@ pub struct UiSpriteConfig {
pub mask: Option<SpriteHandle>, pub mask: Option<SpriteHandle>,
/// This sprite's position and size /// This sprite's position and size
pub rect: UiSpriteRect, pub rect: UiRect,
/// Animation edge to take when mouse enters this sprite /// Animation edge to take when mouse enters this sprite
pub on_mouse_enter: Option<SectionEdge>, pub on_mouse_enter: Option<SectionEdge>,
@ -219,6 +274,22 @@ pub struct UiSpriteConfig {
pub on_mouse_leave: Option<SectionEdge>, pub on_mouse_leave: Option<SectionEdge>,
} }
/// A UI text box
#[derive(Debug, Clone)]
pub struct UiTextConfig {
/// Text box location and dimensions
pub rect: UiRect,
/// Text box font size
pub font_size: f32,
/// Text box line height
pub line_height: f32,
/// Text box alignment
pub align: UiTextAlign,
}
impl crate::Build for Ui { impl crate::Build for Ui {
type InputSyntaxType = syntax::Ui; type InputSyntaxType = syntax::Ui;
@ -231,18 +302,39 @@ impl crate::Build for Ui {
landed_frame: ui landed_frame: ui
.landed .landed
.frame .frame
.build(build_context, ct, false, true) .build(build_context, ct, true)
.with_context(|| format!("in ui config (frame)"))?, .with_context(|| format!("in ui config (landed_frame)"))?,
landed_landscape: ui landed_landscape: ui
.landed .landed
.landscape .landscape
.build(build_context, ct, true, false) .build(build_context, ct, false)
.with_context(|| format!("in ui config (image)"))?, .with_context(|| format!("in ui config (landed_landscape)"))?,
landed_button: ui landed_button: ui
.landed .landed
.button .button
.build(build_context, ct, true, true) .build(build_context, ct, true)
.with_context(|| format!("in ui config (button)"))?, .with_context(|| format!("in ui config (landed_button)"))?,
landed_planet_name: ui
.landed
.planet_name
.build(build_context, ct)
.with_context(|| format!("in ui config (landed_planet_name)"))?,
landed_planet_desc: ui
.landed
.planet_desc
.build(build_context, ct)
.with_context(|| format!("in ui config (landed_planet_desc)"))?,
outfitter_exit_button: ui
.outfitter
.exit_button
.build(build_context, ct, true)
.with_context(|| format!("in ui config (outfitter_exit_button)"))?,
outfitter_se_box: ui
.outfitter
.se_box
.build(build_context, ct, true)
.with_context(|| format!("in ui config (outfitter_se_box)"))?,
}); });
return Ok(()); return Ok(());

View File

@ -7,6 +7,7 @@ fn anchor(
dim: vec2<f32>, // Sprite dimensions (width, height) dim: vec2<f32>, // Sprite dimensions (width, height)
) -> vec2<f32> { ) -> vec2<f32> {
// TODO: remove, do in ui rust lib
var trans: vec2<f32> = vec2(0.0, 0.0); var trans: vec2<f32> = vec2(0.0, 0.0);
let window_dim = ( let window_dim = (
vec2(global_data.window_size_w, global_data.window_size_h) / vec2(global_data.window_size_w, global_data.window_size_h) /

View File

@ -4,7 +4,7 @@ struct InstanceInput {
@location(2) anchor: u32, @location(2) anchor: u32,
@location(3) position: vec2<f32>, @location(3) position: vec2<f32>,
@location(4) angle: f32, @location(4) angle: f32,
@location(5) size: f32, @location(5) dim: vec2<f32>,
@location(6) color_transform: vec4<f32>, @location(6) color_transform: vec4<f32>,
@location(7) texture_index: vec2<u32>, @location(7) texture_index: vec2<u32>,
@location(8) texture_fade: f32, @location(8) texture_fade: f32,
@ -44,30 +44,26 @@ fn transform_vertex(
texture_index: u32, texture_index: u32,
) -> vec4<f32> { ) -> vec4<f32> {
// Window size in logical pixels
let window_dim = ( let window_dim = (
vec2(global_data.window_size_w, global_data.window_size_h) vec2(global_data.window_size_w, global_data.window_size_h)
/ global_data.window_scale / global_data.window_scale
); );
let scale = instance.size / window_dim.y; let scale = instance.dim.y / window_dim.y;
let aspect = (
global_atlas[instance.texture_index.x].width /
global_atlas[instance.texture_index.x].height
);
// Apply scale and sprite aspect
// Note that our mesh starts centered at (0, 0). This is important!
var pos: vec2<f32> = vec2( var pos: vec2<f32> = vec2(
vertex_position.x * scale * aspect, vertex_position.x * scale * (instance.dim.x / instance.dim.y),
vertex_position.y * scale vertex_position.y * scale
); );
// Apply rotation (and adjust sprite angle, since sprites point north)
pos = mat2x2( pos = mat2x2(
vec2(cos(instance.angle - 1.5708), sin(instance.angle - 1.5708)), vec2(cos(instance.angle - 1.5708), sin(instance.angle - 1.5708)),
vec2(-sin(instance.angle - 1.5708), cos(instance.angle - 1.5708)) vec2(-sin(instance.angle - 1.5708), cos(instance.angle - 1.5708))
) * pos; ) * pos;
// Apply rotation (and adjust sprite angle, since sprites point north)
// Correct for screen aspect, preserving height // Correct for screen aspect, preserving height
pos = vec2( pos = vec2(
pos.x / global_data.window_aspect, pos.x / global_data.window_aspect,
@ -77,7 +73,7 @@ fn transform_vertex(
pos = pos + anchor( pos = pos + anchor(
instance.anchor, instance.anchor,
instance.position, instance.position,
vec2(instance.size * aspect, instance.size) instance.dim
); );
return vec4<f32>(pos, 0.0, 1.0); return vec4<f32>(pos, 0.0, 1.0);
@ -90,9 +86,6 @@ fn vertex_main(
) -> VertexOutput { ) -> VertexOutput {
var out: VertexOutput; var out: VertexOutput;
// TODO: this will break if we try to use texture 0.
// implement animations for ui sprites & fix that here.
// Pick texture size by the size of the visible texture // Pick texture size by the size of the visible texture
// (texture index 0 is special, it's the "hidden" texture) // (texture index 0 is special, it's the "hidden" texture)
if instance.texture_index.x == 0u && instance.texture_index.y == 0u { if instance.texture_index.x == 0u && instance.texture_index.y == 0u {

View File

@ -63,7 +63,7 @@ impl Radar {
anchor: PositionAnchor::NwNw.to_int(), anchor: PositionAnchor::NwNw.to_int(),
position: [10.0, -10.0], position: [10.0, -10.0],
angle: 0.0, angle: 0.0,
size: radar_size, dim: [sprite.aspect * radar_size, radar_size],
color: [1.0, 1.0, 1.0, 1.0], color: [1.0, 1.0, 1.0, 1.0],
texture_index: [texture_a, texture_a], texture_index: [texture_a, texture_a],
texture_fade: 1.0, texture_fade: 1.0,
@ -96,7 +96,7 @@ impl Radar {
+ (d * (radar_size / 2.0))) + (d * (radar_size / 2.0)))
.into(), .into(),
angle: o.angle, angle: o.angle,
size, dim: [sprite.aspect * size, size],
color: [0.5, 0.5, 0.5, 1.0], color: [0.5, 0.5, 0.5, 1.0],
texture_index: [texture_a, texture_a], texture_index: [texture_a, texture_a],
texture_fade: 1.0, texture_fade: 1.0,
@ -154,7 +154,7 @@ impl Radar {
anchor: PositionAnchor::NwC.to_int(), anchor: PositionAnchor::NwC.to_int(),
position: position.into(), position: position.into(),
angle: player_ship.rigidbody.rotation().angle(), angle: player_ship.rigidbody.rotation().angle(),
size, dim: [sprite.aspect * size, size],
color, color,
texture_index: [texture_a, texture_a], texture_index: [texture_a, texture_a],
texture_fade: 1.0, texture_fade: 1.0,
@ -186,7 +186,7 @@ impl Radar {
) )
.into(), .into(),
angle: to_radians(90.0), angle: to_radians(90.0),
size, dim: [sprite.aspect * size, size],
color, color,
texture_index: [texture_a, texture_a], texture_index: [texture_a, texture_a],
texture_fade: 1.0, texture_fade: 1.0,
@ -201,7 +201,7 @@ impl Radar {
) )
.into(), .into(),
angle: to_radians(180.0), angle: to_radians(180.0),
size, dim: [sprite.aspect * size, size],
color, color,
texture_index: [texture_a, texture_a], texture_index: [texture_a, texture_a],
texture_fade: 1.0, texture_fade: 1.0,
@ -216,7 +216,7 @@ impl Radar {
) )
.into(), .into(),
angle: to_radians(270.0), angle: to_radians(270.0),
size, dim: [sprite.aspect * size, size],
color, color,
texture_index: [texture_a, texture_a], texture_index: [texture_a, texture_a],
texture_fade: 1.0, texture_fade: 1.0,
@ -231,7 +231,7 @@ impl Radar {
) )
.into(), .into(),
angle: to_radians(0.0), angle: to_radians(0.0),
size, dim: [sprite.aspect * size, size],
color, color,
texture_index: [texture_a, texture_a], texture_index: [texture_a, texture_a],
texture_fade: 1.0, texture_fade: 1.0,
@ -254,7 +254,7 @@ impl Radar {
anchor: PositionAnchor::NwC.to_int(), anchor: PositionAnchor::NwC.to_int(),
position: position.into(), position: position.into(),
angle, angle,
size: 10.0, dim: [10.0 * sprite.aspect, 10.0],
color: [1.0, 1.0, 1.0, 1f32.min((m - 200.0) / 400.0)], color: [1.0, 1.0, 1.0, 1f32.min((m - 200.0) / 400.0)],
texture_index: [texture_a, texture_a], texture_index: [texture_a, texture_a],
texture_fade: 1.0, texture_fade: 1.0,

View File

@ -68,7 +68,7 @@ impl Status {
anchor: PositionAnchor::NeNe.to_int(), anchor: PositionAnchor::NeNe.to_int(),
position: [-10.0, -10.0], position: [-10.0, -10.0],
angle: 0.0, angle: 0.0,
size: 200.0, dim: [sprite.aspect * 200.0, 200.0],
color: [1.0, 1.0, 1.0, 1.0], color: [1.0, 1.0, 1.0, 1.0],
texture_index: [texture_a, texture_a], texture_index: [texture_a, texture_a],
texture_fade: 1.0, texture_fade: 1.0,

View File

@ -1,12 +1,11 @@
use galactica_content::{Content, SystemObject, SystemObjectHandle}; use galactica_content::{Content, SystemObject, SystemObjectHandle};
use galactica_system::{data::ShipState, phys::PhysSimShipHandle}; use galactica_system::{data::ShipState, phys::PhysSimShipHandle};
use glyphon::{cosmic_text::Align, Attrs, Color, Metrics, TextArea, Weight}; use glyphon::{Attrs, TextArea, Weight};
use nalgebra::{Point2, Vector2};
use crate::{ use crate::{
ui::{ ui::{
manager::{UiScene, UiSceneStepResult, UiScenes}, manager::{UiScene, UiSceneStepResult, UiScenes},
util::{SpriteRect, UiSprite, UiTextArea}, util::{UiSprite, UiTextArea},
}, },
RenderInput, RenderState, RenderInput, RenderState,
}; };
@ -45,35 +44,8 @@ impl UiLandedScene {
current_object: None, current_object: None,
leftclick_down: false, leftclick_down: false,
description: UiTextArea::new( description: UiTextArea::from(ct, state, &ct.get_ui().landed_planet_desc),
ct, title: UiTextArea::from(ct, state, &ct.get_ui().landed_planet_name),
state,
frame.get_sprite(),
Point2::new(0.0, 0.0),
frame.get_height(),
SpriteRect {
pos: Point2::new(25.831, 284.883) / 512.0,
dim: Vector2::new(433.140, 97.220) / 512.0,
},
Metrics::new(16.0, 18.0),
Color::rgb(255, 255, 255),
Align::Left,
),
title: UiTextArea::new(
ct,
state,
frame.get_sprite(),
Point2::new(0.0, 0.0),
frame.get_height(),
SpriteRect {
pos: Point2::new(165.506, 82.0) / 512.0,
dim: Vector2::new(74.883, 17.0) / 512.0,
},
Metrics::new(19.0, 19.0),
Color::rgb(255, 255, 255),
Align::Center,
),
frame, frame,
landscape, landscape,
@ -105,15 +77,14 @@ impl UiLandedScene {
impl<'this> UiScene<'this> for UiLandedScene { impl<'this> UiScene<'this> for UiLandedScene {
fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult { fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult {
let frame_rect = Some(self.frame.get_rect(input)); self.button.step(input, state);
self.button.step(input, state, frame_rect); self.landscape.step(input, state);
self.landscape.step(input, state, frame_rect); self.frame.step(input, state);
self.frame.step(input, state, None);
let mut new_scene = None; let mut new_scene = None;
if input.player.input.pressed_leftclick() && !self.leftclick_down { if input.player.input.pressed_leftclick() && !self.leftclick_down {
self.leftclick_down = true; self.leftclick_down = true;
if self.button.contains_mouse(input, state, frame_rect) { if self.button.contains_mouse(input, state) {
new_scene = Some(UiScenes::Outfitter(UiOutfitterScene::new(input.ct, state))); new_scene = Some(UiScenes::Outfitter(UiOutfitterScene::new(input.ct, state)));
} }
} else if !input.player.input.pressed_leftclick() { } else if !input.player.input.pressed_leftclick() {
@ -147,19 +118,18 @@ impl<'this> UiScene<'this> for UiLandedScene {
} }
// Draw elements // Draw elements
let frame_rect = Some(self.frame.get_rect(input)); self.button.push_to_buffer(input, state);
self.button.push_to_buffer(input, state, frame_rect); self.landscape.push_to_buffer(input, state);
self.landscape.push_to_buffer(input, state, frame_rect); self.frame.push_to_buffer(input, state);
self.frame.push_to_buffer(input, state, None);
} }
fn get_textareas( fn get_textareas(
&'this self, &'this self,
v: &mut Vec<TextArea<'this>>, v: &mut Vec<TextArea<'this>>,
input: &RenderInput, _input: &RenderInput,
state: &RenderState, state: &RenderState,
) { ) {
v.push(self.description.get_textarea(input, state)); v.push(self.description.get_textarea(state));
v.push(self.title.get_textarea(input, state)); v.push(self.title.get_textarea(state));
} }
} }

View File

@ -1,4 +1,4 @@
use galactica_content::{Content, SystemObject, SystemObjectHandle}; use galactica_content::{Content, SystemObject, SystemObjectHandle, UiPositionAnchor};
use galactica_system::{data::ShipState, phys::PhysSimShipHandle}; use galactica_system::{data::ShipState, phys::PhysSimShipHandle};
use glyphon::{cosmic_text::Align, Attrs, Color, Metrics, TextArea, Weight}; use glyphon::{cosmic_text::Align, Attrs, Color, Metrics, TextArea, Weight};
use nalgebra::{Point2, Vector2}; use nalgebra::{Point2, Vector2};
@ -15,9 +15,9 @@ use super::UiLandedScene;
pub struct UiOutfitterScene { pub struct UiOutfitterScene {
// UI elements // UI elements
description: UiTextArea, se_box: UiSprite,
landscape: UiSprite, exit_button: UiSprite,
button: UiSprite, exit_text: UiTextArea,
/// What object we're displaying currently. /// What object we're displaying currently.
/// Whenever this changes, we need to reflow text. /// Whenever this changes, we need to reflow text.
@ -30,55 +30,39 @@ pub struct UiOutfitterScene {
impl UiOutfitterScene { impl UiOutfitterScene {
pub fn new(ct: &Content, state: &mut RenderState) -> Self { pub fn new(ct: &Content, state: &mut RenderState) -> Self {
let button = UiSprite::new( let exit_button = UiSprite::from(ct, &ct.get_ui().outfitter_exit_button);
ct, let se_box = UiSprite::from(ct, &ct.get_ui().outfitter_se_box);
ct.get_sprite_handle("ui::button"),
None,
SpriteRect {
pos: Point2::new(0.0, 0.0),
dim: Vector2::new(200.0, 200.0),
},
None,
None,
);
let landscape = UiSprite::from_with_sprite(
ct,
&ct.get_ui().landed_landscape,
ct.get_sprite_handle("ui::landscape::test"),
);
let s = Self { let s = Self {
// height of element in logical pixels // height of element in logical pixels
current_object: None, current_object: None,
leftclick_down: false, leftclick_down: false,
description: UiTextArea::new( exit_text: UiTextArea::new(
ct, ct,
state, state,
button.get_sprite(),
Point2::new(0.0, 0.0),
800.0,
SpriteRect { SpriteRect {
pos: Point2::new(25.831, 284.883) / 512.0, pos: Point2::new(0.0, 0.0),
dim: Vector2::new(433.140, 97.220) / 512.0, dim: Vector2::new(200.0, 200.0), // TODO: do this better
anchor_self: UiPositionAnchor::Center,
anchor_parent: UiPositionAnchor::Center,
}, },
Metrics::new(16.0, 18.0), Metrics::new(16.0, 18.0),
Color::rgb(255, 255, 255), Color::rgb(255, 255, 255),
Align::Left, Align::Center,
), ),
landscape, se_box,
button, exit_button,
}; };
return s; return s;
} }
fn reflow(&mut self, planet: &SystemObject, state: &mut RenderState) { fn reflow(&mut self, planet: &SystemObject, state: &mut RenderState) {
self.description.set_text( self.exit_text.set_text(
state, state,
&planet.desc, "Exit",
Attrs::new() Attrs::new()
.weight(Weight::NORMAL) .weight(Weight::NORMAL)
.family(glyphon::Family::SansSerif), .family(glyphon::Family::SansSerif),
@ -90,13 +74,13 @@ impl UiOutfitterScene {
impl<'this> UiScene<'this> for UiOutfitterScene { impl<'this> UiScene<'this> for UiOutfitterScene {
fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult { fn step(&mut self, input: &RenderInput, state: &mut RenderState) -> UiSceneStepResult {
self.button.step(input, state, None); self.se_box.step(input, state);
self.landscape.step(input, state, None); self.exit_button.step(input, state);
let mut new_scene = None; let mut new_scene = None;
if input.player.input.pressed_leftclick() && !self.leftclick_down { if input.player.input.pressed_leftclick() && !self.leftclick_down {
self.leftclick_down = true; self.leftclick_down = true;
if self.button.contains_mouse(input, state, None) { if self.exit_button.contains_mouse(input, state) {
new_scene = Some(UiScenes::Landed(UiLandedScene::new(input.ct, state))); new_scene = Some(UiScenes::Landed(UiLandedScene::new(input.ct, state)));
} }
} else if !input.player.input.pressed_leftclick() { } else if !input.player.input.pressed_leftclick() {
@ -130,16 +114,16 @@ impl<'this> UiScene<'this> for UiOutfitterScene {
} }
// Draw elements // Draw elements
self.button.push_to_buffer(input, state, None); self.se_box.push_to_buffer(input, state);
self.landscape.push_to_buffer(input, state, None); self.exit_button.push_to_buffer(input, state);
} }
fn get_textareas( fn get_textareas(
&'this self, &'this self,
v: &mut Vec<TextArea<'this>>, v: &mut Vec<TextArea<'this>>,
input: &RenderInput, _input: &RenderInput,
state: &RenderState, state: &RenderState,
) { ) {
v.push(self.description.get_textarea(input, state)); v.push(self.exit_text.get_textarea(state));
} }
} }

View File

@ -1,14 +1,16 @@
mod sprite; mod sprite;
mod textarea; mod textarea;
use galactica_content::UiPositionAnchor;
pub(super) use sprite::UiSprite; pub(super) use sprite::UiSprite;
pub(super) use textarea::UiTextArea; pub(super) use textarea::UiTextArea;
use nalgebra::{Point2, Vector2}; use nalgebra::{Point2, Vector2};
use winit::dpi::LogicalSize;
use crate::{RenderInput, RenderState}; use crate::{RenderInput, RenderState};
/// Represents a rectangular region inside a sprite. /// Represents a rectangular region
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(crate) struct SpriteRect { pub(crate) struct SpriteRect {
/// The position of the top-left corner of this rectangle, in fractional units. /// The position of the top-left corner of this rectangle, in fractional units.
@ -18,17 +20,143 @@ pub(crate) struct SpriteRect {
/// The width and height of this rectangle, in fractional units. /// 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 /// 1.0 will be as tall as the sprite, 0.5 will be half as tall
pub dim: Vector2<f32>, pub dim: Vector2<f32>,
/// How to compute this rectangle's coordinates relative to its parent
pub anchor_self: UiPositionAnchor,
/// How to compute this rectangle's coordinates relative to its parent
pub anchor_parent: UiPositionAnchor,
} }
impl SpriteRect { impl SpriteRect {
/// Northeast corner of this rect /*
pub fn ne_corner(&self) -> Point2<f32> { pub fn to_centered_relative(&self, parent: SpriteRectCentered) -> SpriteRectCentered {
self.pos + Vector2::new(-self.dim.x, self.dim.y) / 2.0 let dim = Vector2::new(self.dim.x * parent.dim.x, self.dim.y * parent.dim.y);
let pos = Vector2::new(self.pos.x * parent.dim.x, self.pos.y * parent.dim.y);
let mut zero = match self.anchor_parent {
PositionAnchor::Center => parent.pos,
PositionAnchor::NorthWest => Point2::new(
parent.pos.x - (parent.dim.x / 2.0),
parent.pos.y + (parent.dim.y / 2.0),
),
PositionAnchor::SouthWest => Point2::new(
parent.pos.x - (parent.dim.x / 2.0),
parent.pos.y - (parent.dim.y / 2.0),
),
PositionAnchor::NorthEast => Point2::new(
parent.pos.x + (parent.dim.x / 2.0),
parent.pos.y + (parent.dim.y / 2.0),
),
PositionAnchor::SouthEast => Point2::new(
parent.pos.x + (parent.dim.x / 2.0),
parent.pos.y - (parent.dim.y / 2.0),
),
};
match self.anchor_self {
PositionAnchor::Center => {}
PositionAnchor::NorthWest => {
zero += Vector2::new(dim.x, -dim.y) / 2.0;
} }
/// Southwest corner of this rect PositionAnchor::NorthEast => {
pub fn sw_corner(&self) -> Point2<f32> { zero += Vector2::new(-dim.x, -dim.y) / 2.0;
self.pos + Vector2::new(self.dim.x, -self.dim.y) / 2.0 }
PositionAnchor::SouthWest => {
zero += Vector2::new(dim.x, dim.y) / 2.0;
}
PositionAnchor::SouthEast => {
zero += Vector2::new(-dim.x, dim.y) / 2.0;
}
};
return SpriteRectCentered {
dim,
pos: zero + pos,
};
}
*/
/// Convert this rectangle to a centered rectangle.
pub fn to_centered(&self, state: &RenderState) -> CenteredSpriteRect {
let w: LogicalSize<f32> = state.window_size.to_logical(state.window.scale_factor());
let w = Vector2::new(w.width, w.height);
let mut pos = self.pos;
let dim = self.dim;
// Origin
match self.anchor_parent {
UiPositionAnchor::Center => {}
UiPositionAnchor::NorthWest => {
pos += Vector2::new(-w.x, w.y) / 2.0;
}
UiPositionAnchor::SouthWest => {
pos += Vector2::new(-w.x, -w.y) / 2.0;
}
UiPositionAnchor::NorthEast => {
pos += Vector2::new(w.x, w.y) / 2.0;
}
UiPositionAnchor::SouthEast => {
pos += Vector2::new(w.x, -w.y) / 2.0;
}
}
// Offset for self dimensions
match self.anchor_self {
UiPositionAnchor::Center => {}
UiPositionAnchor::NorthWest => {
pos += Vector2::new(dim.x, -dim.y) / 2.0;
}
UiPositionAnchor::NorthEast => {
pos += Vector2::new(-dim.x, -dim.y) / 2.0;
}
UiPositionAnchor::SouthWest => {
pos += Vector2::new(dim.x, dim.y) / 2.0;
}
UiPositionAnchor::SouthEast => {
pos += Vector2::new(dim.x, dim.y) / 2.0;
}
};
return CenteredSpriteRect { pos, dim };
}
}
/// Represents a rectangular region, in absolute coordinates relative to the screen center.
#[derive(Debug, Clone, Copy)]
pub(crate) struct CenteredSpriteRect {
/// 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)
pub pos: Point2<f32>,
/// 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
pub dim: Vector2<f32>,
}
impl CenteredSpriteRect {
pub fn contains_point(&self, pt: Point2<f32>) -> bool {
let ne = self.pos + Vector2::new(-self.dim.x, self.dim.y) / 2.0;
let sw = self.pos + Vector2::new(self.dim.x, -self.dim.y) / 2.0;
return (pt.y < ne.y && pt.y > sw.y) && (pt.x > ne.x && pt.x < sw.x);
} }
} }

View File

@ -2,8 +2,8 @@ use galactica_content::{Content, SectionEdge, SpriteAutomaton, SpriteHandle, UiS
use galactica_util::to_radians; use galactica_util::to_radians;
use nalgebra::{Point2, Vector2}; use nalgebra::{Point2, Vector2};
use super::SpriteRect; use super::{CenteredSpriteRect, SpriteRect};
use crate::{vertexbuffer::types::UiInstance, PositionAnchor, RenderInput, RenderState}; use crate::{vertexbuffer::types::UiInstance, RenderInput, RenderState};
pub struct UiSprite { pub struct UiSprite {
pub anim: SpriteAutomaton, pub anim: SpriteAutomaton,
@ -49,22 +49,21 @@ impl UiSprite {
sprite, sprite,
ui.mask, ui.mask,
SpriteRect { SpriteRect {
pos: *ui.rect.get_pos(), pos: ui.rect.pos,
dim: *ui.rect.get_dim(), dim: ui.rect.dim,
anchor_self: ui.rect.anchor_self,
anchor_parent: ui.rect.anchor_parent,
}, },
ui.on_mouse_enter, ui.on_mouse_enter,
ui.on_mouse_leave, ui.on_mouse_leave,
) )
} }
pub fn step(&mut self, input: &RenderInput, state: &RenderState, parent: Option<SpriteRect>) { pub fn step(&mut self, input: &RenderInput, state: &RenderState) {
if self.contains_mouse(input, state, parent) if self.contains_mouse(input, state) && !self.has_mouse && self.on_mouse_enter.is_some() {
&& !self.has_mouse
&& self.on_mouse_enter.is_some()
{
self.has_mouse = true; self.has_mouse = true;
self.anim.jump_to(input.ct, self.on_mouse_enter.unwrap()) self.anim.jump_to(input.ct, self.on_mouse_enter.unwrap())
} else if !self.contains_mouse(input, state, parent) } else if !self.contains_mouse(input, state)
&& self.has_mouse && self.has_mouse
&& self.on_mouse_leave.is_some() && self.on_mouse_leave.is_some()
{ {
@ -75,13 +74,8 @@ impl UiSprite {
self.anim.step(input.ct, input.time_since_last_run); self.anim.step(input.ct, input.time_since_last_run);
} }
pub fn contains_mouse( pub fn contains_mouse(&self, input: &RenderInput, state: &RenderState) -> bool {
&self, let rect = self.get_rect(state);
input: &RenderInput,
state: &RenderState,
parent: Option<SpriteRect>,
) -> bool {
let rect = self.get_relative_rect(input, parent);
let fac = state.window.scale_factor() as f32; let fac = state.window.scale_factor() as f32;
let window_size = Vector2::new( let window_size = Vector2::new(
@ -90,69 +84,40 @@ impl UiSprite {
); );
let pos = input.player.input.get_mouse_pos(); let pos = input.player.input.get_mouse_pos();
let mouse_pos = Vector2::new( let mouse_pos = Point2::new(
pos.x / fac - window_size.x / 2.0, pos.x / fac - window_size.x / 2.0,
window_size.y / 2.0 - pos.y / fac, window_size.y / 2.0 - pos.y / fac,
); );
let ne = rect.ne_corner(); return rect.contains_point(mouse_pos);
let sw = rect.sw_corner();
return (mouse_pos.y < ne.y && mouse_pos.y > sw.y)
&& (mouse_pos.x > ne.x && mouse_pos.x < sw.x);
} }
pub fn get_rect(&self, input: &RenderInput) -> SpriteRect { pub fn get_rect(
let pos = Point2::new(self.rect.pos.x, self.rect.pos.y); &self,
let dim = Vector2::new( state: &RenderState,
self.rect.dim.y * input.ct.get_sprite(self.anim.get_sprite()).aspect, //parent: Option<SpriteRectCentered>,
self.rect.dim.y, ) -> CenteredSpriteRect {
); /*
return SpriteRect { dim, pos };
}
pub fn get_relative_rect(&self, input: &RenderInput, parent: Option<SpriteRect>) -> SpriteRect {
if let Some(parent) = parent { if let Some(parent) = parent {
let zero = Point2::new( return self.rect.to_centered_relative(parent);
parent.pos.x - (parent.dim.x / 2.0),
parent.pos.y + (parent.dim.y / 2.0),
);
let mut pos = zero
+ Vector2::new(
self.rect.pos.x * parent.dim.x,
-self.rect.pos.y * parent.dim.y,
);
let dim = Vector2::new(
self.rect.dim.x * parent.dim.x,
self.rect.dim.y * parent.dim.y,
);
pos += Vector2::new(dim.x, -dim.y) / 2.0;
return SpriteRect { dim, pos };
} else { } else {
return self.get_rect(input); return self.rect.to_centered(state);
} }*/
return self.rect.to_centered(state);
} }
/// Add this image to the gpu sprite buffer /// Add this image to the gpu sprite buffer
pub fn push_to_buffer( pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
&self, let rect = self.get_rect(state);
input: &RenderInput,
state: &mut RenderState,
parent: Option<SpriteRect>,
) {
let rect = self.get_relative_rect(input, parent.clone());
// TODO: use both dimensions, // TODO: use both dimensions,
// not just height // not just height
let anim_state = self.anim.get_texture_idx(); let anim_state = self.anim.get_texture_idx();
state.push_ui_buffer(UiInstance { state.push_ui_buffer(UiInstance {
anchor: PositionAnchor::CC.to_int(), anchor: crate::PositionAnchor::CC.to_int(),
position: rect.pos.into(), position: rect.pos.into(),
angle: to_radians(90.0), angle: to_radians(90.0),
size: rect.dim.y, dim: rect.dim.into(),
color: [1.0, 1.0, 1.0, 1.0], color: [1.0, 1.0, 1.0, 1.0],
texture_index: anim_state.texture_index(), texture_index: anim_state.texture_index(),
texture_fade: anim_state.fade, texture_fade: anim_state.fade,
@ -166,12 +131,4 @@ impl UiSprite {
.unwrap_or([0, 0]), .unwrap_or([0, 0]),
}); });
} }
pub fn get_sprite(&self) -> SpriteHandle {
self.anim.get_sprite()
}
pub fn get_height(&self) -> f32 {
self.rect.dim.y
}
} }

View File

@ -1,22 +1,12 @@
use galactica_content::{Content, SpriteHandle}; use galactica_content::{Content, UiTextAlign, UiTextConfig};
use glyphon::{cosmic_text::Align, Attrs, Buffer, Color, Metrics, Shaping, TextArea, TextBounds}; use glyphon::{cosmic_text::Align, Attrs, Buffer, Color, Metrics, Shaping, TextArea, TextBounds};
use nalgebra::{Point2, Vector2}; use nalgebra::Vector2;
use super::SpriteRect; use super::SpriteRect;
use crate::{RenderInput, RenderState}; use crate::RenderState;
/// Represents a text area inside a sprite. /// Represents a text area inside a sprite.
pub(crate) struct UiTextArea { pub(crate) struct UiTextArea {
/// Parent sprite
sprite: SpriteHandle,
/// Position of parent sprite's center, in logical pixels,
/// with 0, 0 at the center of the screen
sprite_position: Point2<f32>,
/// Height of parent sprite, in logical pixels
sprite_size: f32,
/// Bounds of text area /// Bounds of text area
rect: SpriteRect, rect: SpriteRect,
@ -32,11 +22,8 @@ pub(crate) struct UiTextArea {
impl UiTextArea { impl UiTextArea {
pub fn new( pub fn new(
ct: &Content, _ct: &Content,
state: &mut RenderState, state: &mut RenderState,
sprite: SpriteHandle,
sprite_position: Point2<f32>,
sprite_size: f32,
rect: SpriteRect, rect: SpriteRect,
text_metrics: Metrics, text_metrics: Metrics,
color: Color, color: Color,
@ -44,25 +31,37 @@ impl UiTextArea {
) -> Self { ) -> Self {
let mut s = Self { let mut s = Self {
buffer: Buffer::new(&mut state.text_font_system, text_metrics), buffer: Buffer::new(&mut state.text_font_system, text_metrics),
sprite_size: f32::NAN,
sprite,
sprite_position,
rect, rect,
align, align,
color, color,
}; };
s.set_size(ct, state, sprite_size);
s.buffer.set_size(
&mut state.text_font_system,
s.rect.dim.x * state.window.scale_factor() as f32,
s.rect.dim.y * state.window.scale_factor() as f32,
);
return s; return s;
} }
pub fn set_size(&mut self, ct: &Content, state: &mut RenderState, sprite_size: f32) { pub fn from(ct: &Content, state: &mut RenderState, ui: &UiTextConfig) -> Self {
self.sprite_size = sprite_size; Self::new(
self.buffer.set_size( ct,
&mut state.text_font_system, state,
(self.rect.dim.x * self.sprite_size) * state.window.scale_factor() as f32, SpriteRect {
(self.rect.dim.y * self.sprite_size * ct.get_sprite(self.sprite).aspect) pos: ui.rect.pos,
* state.window.scale_factor() as f32, dim: ui.rect.dim,
); anchor_self: ui.rect.anchor_self,
anchor_parent: ui.rect.anchor_parent,
},
Metrics::new(ui.font_size, ui.line_height),
Color::rgb(255, 255, 255),
match ui.align {
UiTextAlign::Center => Align::Center,
UiTextAlign::Left => Align::Left,
},
)
} }
pub fn set_text(&mut self, state: &mut RenderState, text: &str, attrs: Attrs) { pub fn set_text(&mut self, state: &mut RenderState, text: &str, attrs: Attrs) {
@ -75,31 +74,27 @@ impl UiTextArea {
self.buffer.shape_until_scroll(&mut state.text_font_system); self.buffer.shape_until_scroll(&mut state.text_font_system);
} }
pub fn get_textarea(&self, input: &RenderInput, state: &RenderState) -> TextArea { pub fn get_textarea(&self, state: &RenderState) -> TextArea {
let h = self.sprite_size; let rect = self.rect.to_centered(state);
let w = input.ct.get_sprite(self.sprite).aspect * h;
// Glypon works with physical pixels, so we must convert // Glypon works with physical pixels, so we must do some conversion
let fac = state.window.scale_factor() as f32; let fac = state.window.scale_factor() as f32;
let corner_ne = Vector2::new(
// All the units below are in logical pixels (rect.pos.x - rect.dim.x / 2.0) * fac + state.window_size.width as f32 / 2.0,
let zero = Vector2::new( state.window_size.height as f32 / 2.0 - (rect.pos.y * fac + rect.dim.y / 2.0),
(state.window_size.width as f32 / (2.0 * fac)) - (w / 2.0) + self.sprite_position.x,
(state.window_size.height as f32 / (2.0 * fac)) - (h / 2.0) - self.sprite_position.y,
); );
let corner_ne = zero + Vector2::new(self.rect.pos.x * w, self.rect.pos.y * h); let corner_sw = corner_ne + rect.dim * fac;
let corner_sw = corner_ne + Vector2::new(self.rect.dim.x * w, self.rect.dim.y * h);
TextArea { TextArea {
buffer: &self.buffer, buffer: &self.buffer,
top: corner_ne.y * fac, top: corner_ne.y,
left: corner_ne.x * fac, left: corner_ne.x,
scale: 1.0, scale: 1.0,
bounds: TextBounds { bounds: TextBounds {
top: (corner_ne.y * fac) as i32, top: (corner_ne.y) as i32,
bottom: (corner_sw.y * fac) as i32, bottom: (corner_sw.y) as i32,
left: (corner_ne.x * fac) as i32, left: (corner_ne.x) as i32,
right: (corner_sw.x * fac) as i32, right: (corner_sw.x) as i32,
}, },
default_color: self.color, default_color: self.color,
} }

View File

@ -153,8 +153,9 @@ pub struct UiInstance {
/// The angle of this sprite, in radians /// The angle of this sprite, in radians
pub angle: f32, pub angle: f32,
/// The height of this sprite, in logical pixels /// The width and height of this sprite, in logical pixels.
pub size: f32, /// Aspect ratio is not automatically preserved.
pub dim: [f32; 2],
/// This lets us color ui sprites dynamically. /// This lets us color ui sprites dynamically.
/// Each fragment's color is multiplied by this value. /// Each fragment's color is multiplied by this value.
@ -200,33 +201,33 @@ impl BufferObject for UiInstance {
shader_location: 4, shader_location: 4,
format: wgpu::VertexFormat::Float32, format: wgpu::VertexFormat::Float32,
}, },
// Size // Dim
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress, offset: mem::size_of::<[f32; 4]>() as wgpu::BufferAddress,
shader_location: 5, shader_location: 5,
format: wgpu::VertexFormat::Float32, format: wgpu::VertexFormat::Float32x2,
}, },
// Color // Color
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 5]>() as wgpu::BufferAddress, offset: mem::size_of::<[f32; 6]>() as wgpu::BufferAddress,
shader_location: 6, shader_location: 6,
format: wgpu::VertexFormat::Float32x4, format: wgpu::VertexFormat::Float32x4,
}, },
// Texture // Texture
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 9]>() as wgpu::BufferAddress, offset: mem::size_of::<[f32; 10]>() as wgpu::BufferAddress,
shader_location: 7, shader_location: 7,
format: wgpu::VertexFormat::Uint32x2, format: wgpu::VertexFormat::Uint32x2,
}, },
// Texture fade // Texture fade
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 11]>() as wgpu::BufferAddress, offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress,
shader_location: 8, shader_location: 8,
format: wgpu::VertexFormat::Float32, format: wgpu::VertexFormat::Float32,
}, },
// Mask // Mask
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: mem::size_of::<[f32; 12]>() as wgpu::BufferAddress, offset: mem::size_of::<[f32; 13]>() as wgpu::BufferAddress,
shader_location: 9, shader_location: 9,
format: wgpu::VertexFormat::Uint32x2, format: wgpu::VertexFormat::Uint32x2,
}, },