HIDPI Text (#1132)

HIDPI Text 
* add more operator overloads to `bevy::math::Size`
* render UI text at physical resolution
This commit is contained in:
Nathan Jeffords 2020-12-30 14:40:50 -08:00 committed by GitHub
parent 2754a9dde8
commit 6531fcdfd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 158 additions and 12 deletions

View File

@ -1,6 +1,6 @@
use bevy_reflect::Reflect;
use glam::Vec2;
use std::ops::{Add, AddAssign};
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
/// A two dimensional "size" as defined by a width and height
#[derive(Copy, Clone, PartialEq, Debug, Reflect)]
@ -81,3 +81,102 @@ where
self.height += rhs.y;
}
}
impl<T: Reflect> Sub<Vec2> for Size<T>
where
T: Sub<f32, Output = T>,
{
type Output = Size<T>;
fn sub(self, rhs: Vec2) -> Self::Output {
Self {
width: self.width - rhs.x,
height: self.height - rhs.y,
}
}
}
impl<T: Reflect> SubAssign<Vec2> for Size<T>
where
T: SubAssign<f32>,
{
fn sub_assign(&mut self, rhs: Vec2) {
self.width -= rhs.x;
self.height -= rhs.y;
}
}
impl<T: Reflect> Mul<f32> for Size<T>
where
T: Mul<f32, Output = T>,
{
type Output = Size<T>;
fn mul(self, rhs: f32) -> Self::Output {
Self::Output {
width: self.width * rhs,
height: self.height * rhs,
}
}
}
impl<T: Reflect> MulAssign<f32> for Size<T>
where
T: MulAssign<f32>,
{
fn mul_assign(&mut self, rhs: f32) {
self.width *= rhs;
self.height *= rhs;
}
}
impl<T: Reflect> Div<f32> for Size<T>
where
T: Div<f32, Output = T>,
{
type Output = Size<T>;
fn div(self, rhs: f32) -> Self::Output {
Self::Output {
width: self.width / rhs,
height: self.height / rhs,
}
}
}
impl<T: Reflect> DivAssign<f32> for Size<T>
where
T: DivAssign<f32>,
{
fn div_assign(&mut self, rhs: f32) {
self.width /= rhs;
self.height /= rhs;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn size_ops() {
type SizeF = Size<f32>;
assert_eq!(
SizeF::new(10., 10.) + Vec2::new(10., 10.),
SizeF::new(20., 20.)
);
assert_eq!(
SizeF::new(20., 20.) - Vec2::new(10., 10.),
SizeF::new(10., 10.)
);
assert_eq!(SizeF::new(10., 10.) * 2., SizeF::new(20., 20.));
assert_eq!(SizeF::new(20., 20.) / 2., SizeF::new(10., 10.));
let mut size = SizeF::new(10., 10.);
size += Vec2::new(10., 10.);
assert_eq!(size, SizeF::new(20., 20.));
}
}

View File

@ -48,6 +48,7 @@ impl Default for TextStyle {
pub struct DrawableText<'a> {
pub render_resource_bindings: &'a mut RenderResourceBindings,
pub position: Vec3,
pub scale_factor: f32,
pub style: &'a TextStyle,
pub text_glyphs: &'a Vec<PositionedGlyph>,
pub msaa: &'a Msaa,
@ -105,7 +106,20 @@ impl<'a> Drawable for DrawableText<'a> {
color: self.style.color,
};
let transform = Mat4::from_translation(self.position + tv.position.extend(0.));
// To get the rendering right for non-one scaling factors, we need
// the sprite to be drawn in "physical" coordinates. This is because
// the shader uses the size of the sprite to control the size on
// screen. To accomplish this we make the sprite transform
// convert from physical coordinates to logical coordinates in
// addition to altering the origin. Since individual glyphs will
// already be in physical coordinates, we just need to convert the
// overall position to physical coordinates to get the sprites
// physical position.
let transform = Mat4::from_scale(Vec3::splat(1. / self.scale_factor))
* Mat4::from_translation(
self.position * self.scale_factor + tv.position.extend(0.),
);
let transform_buffer = context.get_uniform_buffer(&transform).unwrap();
let sprite_buffer = context.get_uniform_buffer(&sprite).unwrap();

View File

@ -99,6 +99,7 @@ pub fn draw_text2d_system(
msaa: &msaa,
text_glyphs: &text_glyphs.glyphs,
font_quad_vertex_descriptor: &vertex_buffer_descriptor,
scale_factor: 1.,
style: &text.style,
};
@ -121,8 +122,8 @@ pub fn text2d_system(
mut font_atlas_set_storage: ResMut<Assets<FontAtlasSet>>,
mut text_pipeline: ResMut<DefaultTextPipeline>,
mut text_queries: QuerySet<(
Query<Entity, Changed<Text>>,
Query<(&Text, &mut CalculatedSize)>,
Query<Entity, (With<MainPass>, Changed<Text>)>,
Query<(&Text, &mut CalculatedSize), With<MainPass>>,
)>,
) {
// Adds all entities where the text or the style has changed to the local queue

View File

@ -14,31 +14,38 @@ use bevy_text::{
CalculatedSize, DefaultTextPipeline, DrawableText, Font, FontAtlasSet, Text, TextError,
};
use bevy_transform::prelude::GlobalTransform;
use bevy_window::Windows;
#[derive(Debug, Default)]
pub struct QueuedText {
entities: Vec<Entity>,
}
fn scale_value(value: f32, factor: f64) -> f32 {
(value as f64 * factor) as f32
}
/// Defines how min_size, size, and max_size affects the bounds of a text
/// block.
pub fn text_constraint(min_size: Val, size: Val, max_size: Val) -> f32 {
pub fn text_constraint(min_size: Val, size: Val, max_size: Val, scale_factor: f64) -> f32 {
// Needs support for percentages
match (min_size, size, max_size) {
(_, _, Val::Px(max)) => max,
(Val::Px(min), _, _) => min,
(Val::Undefined, Val::Px(size), Val::Undefined) => size,
(Val::Auto, Val::Px(size), Val::Auto) => size,
(_, _, Val::Px(max)) => scale_value(max, scale_factor),
(Val::Px(min), _, _) => scale_value(min, scale_factor),
(Val::Undefined, Val::Px(size), Val::Undefined) => scale_value(size, scale_factor),
(Val::Auto, Val::Px(size), Val::Auto) => scale_value(size, scale_factor),
_ => f32::MAX,
}
}
/// Computes the size of a text block and updates the TextGlyphs with the
/// new computed glyphs from the layout
#[allow(clippy::too_many_arguments)]
pub fn text_system(
mut queued_text: Local<QueuedText>,
mut textures: ResMut<Assets<Texture>>,
fonts: Res<Assets<Font>>,
windows: Res<Windows>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
mut font_atlas_set_storage: ResMut<Assets<FontAtlasSet>>,
mut text_pipeline: ResMut<DefaultTextPipeline>,
@ -47,6 +54,14 @@ pub fn text_system(
Query<(&Text, &Style, &mut CalculatedSize)>,
)>,
) {
let scale_factor = if let Some(window) = windows.get_primary() {
window.scale_factor()
} else {
1.
};
let inv_scale_factor = 1. / scale_factor;
// Adds all entities where the text or the style has changed to the local queue
for entity in text_queries.q0_mut().iter_mut() {
queued_text.entities.push(entity);
@ -62,11 +77,17 @@ pub fn text_system(
for entity in queued_text.entities.drain(..) {
if let Ok((text, style, mut calculated_size)) = query.get_mut(entity) {
let node_size = Size::new(
text_constraint(style.min_size.width, style.size.width, style.max_size.width),
text_constraint(
style.min_size.width,
style.size.width,
style.max_size.width,
scale_factor,
),
text_constraint(
style.min_size.height,
style.size.height,
style.max_size.height,
scale_factor,
),
);
@ -75,7 +96,7 @@ pub fn text_system(
text.font.clone(),
&fonts,
&text.value,
text.style.font_size,
scale_value(text.style.font_size, scale_factor),
text.style.alignment,
node_size,
&mut *font_atlas_set_storage,
@ -93,7 +114,10 @@ pub fn text_system(
let text_layout_info = text_pipeline.get_glyphs(&entity).expect(
"Failed to get glyphs from the pipeline that have just been computed",
);
calculated_size.size = text_layout_info.size;
calculated_size.size = Size {
width: scale_value(text_layout_info.size.width, inv_scale_factor),
height: scale_value(text_layout_info.size.height, inv_scale_factor),
};
}
}
}
@ -106,11 +130,18 @@ pub fn text_system(
pub fn draw_text_system(
mut context: DrawContext,
msaa: Res<Msaa>,
windows: Res<Windows>,
meshes: Res<Assets<Mesh>>,
mut render_resource_bindings: ResMut<RenderResourceBindings>,
text_pipeline: Res<DefaultTextPipeline>,
mut query: Query<(Entity, &mut Draw, &Visible, &Text, &Node, &GlobalTransform)>,
) {
let scale_factor = if let Some(window) = windows.get_primary() {
window.scale_factor()
} else {
1.
};
let font_quad = meshes.get(&QUAD_HANDLE).unwrap();
let vertex_buffer_descriptor = font_quad.get_vertex_buffer_descriptor();
@ -125,6 +156,7 @@ pub fn draw_text_system(
let mut drawable_text = DrawableText {
render_resource_bindings: &mut render_resource_bindings,
position,
scale_factor: scale_factor as f32,
msaa: &msaa,
text_glyphs: &text_glyphs.glyphs,
font_quad_vertex_descriptor: &vertex_buffer_descriptor,