calculating line height from the font

This commit is contained in:
Dustin Carlino 2019-01-23 13:46:05 -08:00
parent 9c94e2f374
commit f1b81456a6
10 changed files with 75 additions and 58 deletions

View File

@ -66,9 +66,9 @@
## Switch to OpenGL (for speed) ## Switch to OpenGL (for speed)
- no bugs - no bugs
- text entry needs to draw the cursor differently anyway. - text entry needs to draw the cursor differently
- top menu is very buggy - top menu is very buggy. MAX_CHAR_WIDTH is weird
- need padding around text; and selecting stuff from menus is weird. LINE_HEIGHT. - need padding around text; and selecting stuff from menus is weird
- forking is buggy (traffic signal diagram) - forking is buggy (traffic signal diagram)
- arrows - arrows
- some colors are wrong - some colors are wrong
@ -76,4 +76,7 @@
- make polygon store points and indices efficiently - make polygon store points and indices efficiently
- change ezgui API to allow uploading geometry once - change ezgui API to allow uploading geometry once
- undo the y inversion hacks at last! - undo the y inversion hacks at last!
- probably use f32, not f64 everywhere - refactoring
- pass canvas to text module, make it do the glyph borrowing?
- pass dims to draw_text_bubble; all callers have it anyway, right?
- probably use f32, not f64 everywhere

View File

@ -2,7 +2,7 @@ use crate::objects::{Ctx, ID};
use crate::plugins::{Plugin, PluginCtx}; use crate::plugins::{Plugin, PluginCtx};
use crate::render::{draw_signal_diagram, DrawTurn}; use crate::render::{draw_signal_diagram, DrawTurn};
use dimensioned::si; use dimensioned::si;
use ezgui::{Color, GfxCtx, Key, TOP_MENU_HEIGHT}; use ezgui::{Color, GfxCtx, Key};
use map_model::{IntersectionID, LaneID, TurnType}; use map_model::{IntersectionID, LaneID, TurnType};
pub struct TurnCyclerState { pub struct TurnCyclerState {
@ -101,7 +101,7 @@ impl Plugin for TurnCyclerState {
i, i,
cycle.idx, cycle.idx,
Some(time_left), Some(time_left),
TOP_MENU_HEIGHT + 10.0, ctx.canvas.top_menu_height() + 10.0,
g, g,
ctx, ctx,
); );

View File

@ -25,6 +25,7 @@ pub struct Canvas {
// TODO Super gross, but we can't create this immediately. // TODO Super gross, but we can't create this immediately.
pub(crate) glyphs: RefCell<Option<GlyphBrush<'static, 'static>>>, pub(crate) glyphs: RefCell<Option<GlyphBrush<'static, 'static>>>,
pub(crate) line_height: f64,
// TODO Bit weird and hacky to mutate inside of draw() calls. // TODO Bit weird and hacky to mutate inside of draw() calls.
covered_areas: RefCell<Vec<ScreenRectangle>>, covered_areas: RefCell<Vec<ScreenRectangle>>,
@ -46,6 +47,7 @@ impl Canvas {
window_height: f64::from(initial_height), window_height: f64::from(initial_height),
glyphs: RefCell::new(None), glyphs: RefCell::new(None),
line_height: 0.0,
covered_areas: RefCell::new(Vec::new()), covered_areas: RefCell::new(Vec::new()),
} }
} }
@ -95,7 +97,7 @@ impl Canvas {
pub fn draw_mouse_tooltip(&self, g: &mut GfxCtx, txt: Text) { pub fn draw_mouse_tooltip(&self, g: &mut GfxCtx, txt: Text) {
let mut glyphs = self.glyphs.borrow_mut(); let mut glyphs = self.glyphs.borrow_mut();
let (width, height) = txt.dims(glyphs.as_mut().unwrap()); let (width, height) = txt.dims(glyphs.as_mut().unwrap(), self);
let x1 = self.cursor_x - (width / 2.0); let x1 = self.cursor_x - (width / 2.0);
let y1 = self.cursor_y - (height / 2.0); let y1 = self.cursor_y - (height / 2.0);
// No need to cover the tooltip; this tooltip follows the mouse anyway. // No need to cover the tooltip; this tooltip follows the mouse anyway.
@ -111,7 +113,7 @@ impl Canvas {
// TODO Rename these draw_nonblocking_text_* // TODO Rename these draw_nonblocking_text_*
pub fn draw_text_at(&self, g: &mut GfxCtx, txt: Text, map_pt: Pt2D) { pub fn draw_text_at(&self, g: &mut GfxCtx, txt: Text, map_pt: Pt2D) {
let mut glyphs = self.glyphs.borrow_mut(); let mut glyphs = self.glyphs.borrow_mut();
let (width, height) = txt.dims(glyphs.as_mut().unwrap()); let (width, height) = txt.dims(glyphs.as_mut().unwrap(), self);
let pt = self.map_to_screen(map_pt); let pt = self.map_to_screen(map_pt);
text::draw_text_bubble( text::draw_text_bubble(
g, g,
@ -147,7 +149,7 @@ impl Canvas {
return; return;
} }
let mut glyphs = self.glyphs.borrow_mut(); let mut glyphs = self.glyphs.borrow_mut();
let (width, height) = txt.dims(glyphs.as_mut().unwrap()); let (width, height) = txt.dims(glyphs.as_mut().unwrap(), self);
let x1 = match horiz { let x1 = match horiz {
HorizontalAlignment::Left => 0.0, HorizontalAlignment::Left => 0.0,
HorizontalAlignment::Center => (self.window_width - width) / 2.0, HorizontalAlignment::Center => (self.window_width - width) / 2.0,
@ -155,7 +157,7 @@ impl Canvas {
}; };
let y1 = match vert { let y1 = match vert {
VerticalAlignment::Top => 0.0, VerticalAlignment::Top => 0.0,
VerticalAlignment::BelowTopMenu => text::LINE_HEIGHT, VerticalAlignment::BelowTopMenu => self.line_height,
VerticalAlignment::Center => (self.window_height - height) / 2.0, VerticalAlignment::Center => (self.window_height - height) / 2.0,
VerticalAlignment::Bottom => self.window_height - height, VerticalAlignment::Bottom => self.window_height - height,
}; };
@ -169,7 +171,7 @@ impl Canvas {
} }
pub fn text_dims(&self, txt: &Text) -> (f64, f64) { pub fn text_dims(&self, txt: &Text) -> (f64, f64) {
txt.dims(self.glyphs.borrow_mut().as_mut().unwrap()) txt.dims(self.glyphs.borrow_mut().as_mut().unwrap(), self)
} }
fn zoom_towards_mouse(&mut self, delta_zoom: f64) { fn zoom_towards_mouse(&mut self, delta_zoom: f64) {
@ -237,6 +239,12 @@ impl Canvas {
b.update(self.screen_to_map(ScreenPt::new(self.window_width, self.window_height))); b.update(self.screen_to_map(ScreenPt::new(self.window_width, self.window_height)));
b b
} }
// TODO Not super happy about exposing this; fork_screenspace for external callers should be
// smarter.
pub fn top_menu_height(&self) -> f64 {
self.line_height
}
} }
pub enum HorizontalAlignment { pub enum HorizontalAlignment {

View File

@ -27,10 +27,6 @@ pub use crate::wizard::{Wizard, WrappedWizard};
use geom::{Angle, Circle, Line, Polygon, Pt2D, Triangle}; use geom::{Angle, Circle, Line, Polygon, Pt2D, Triangle};
use glium::{implement_vertex, uniform, Surface}; use glium::{implement_vertex, uniform, Surface};
// TODO Not super happy about exposing this; fork_screenspace for external callers should be
// smarter.
pub const TOP_MENU_HEIGHT: f64 = text::LINE_HEIGHT;
pub struct ToggleableLayer { pub struct ToggleableLayer {
layer_name: String, layer_name: String,
// If None, never automatically enable at a certain zoom level. // If None, never automatically enable at a certain zoom level.

View File

@ -66,7 +66,7 @@ impl LogScroller {
let can_fit = { let can_fit = {
// Subtract 1 for the title, and an additional TODO hacky // Subtract 1 for the title, and an additional TODO hacky
// few to avoid the bottom OSD and stuff. // few to avoid the bottom OSD and stuff.
let n = (canvas.window_height / text::LINE_HEIGHT).floor() as isize - 1 - 6; let n = (canvas.window_height / canvas.line_height).floor() as isize - 1 - 6;
if n <= 0 { if n <= 0 {
0 0
} else { } else {

View File

@ -1,5 +1,4 @@
use crate::screen_geom::ScreenRectangle; use crate::screen_geom::ScreenRectangle;
use crate::text::LINE_HEIGHT;
use crate::{text, Canvas, Event, GfxCtx, InputResult, Key, ScreenPt, Text}; use crate::{text, Canvas, Event, GfxCtx, InputResult, Key, ScreenPt, Text};
// Stores some associated data with each choice // Stores some associated data with each choice
@ -66,7 +65,7 @@ impl<T: Clone> Menu<T> {
} else { } else {
total_width total_width
}; };
ScreenPt::new(canvas.window_width - w, LINE_HEIGHT) ScreenPt::new(canvas.window_width - w, canvas.line_height)
} }
}; };

View File

@ -1,8 +1,9 @@
use crate::input::{ContextMenu, ModalMenuState}; use crate::input::{ContextMenu, ModalMenuState};
use crate::{Canvas, Event, GfxCtx, ModalMenu, TopMenu, UserInput}; use crate::{text, Canvas, Event, GfxCtx, ModalMenu, TopMenu, UserInput};
use abstutil::Timer; use abstutil::Timer;
use glium::glutin; use glium::glutin;
use glium_glyph::glyph_brush::rusttype::Font; use glium_glyph::glyph_brush::rusttype::Font;
use glium_glyph::glyph_brush::rusttype::Scale;
use glium_glyph::GlyphBrush; use glium_glyph::GlyphBrush;
use std::cell::RefCell; use std::cell::RefCell;
use std::io::Write; use std::io::Write;
@ -275,6 +276,10 @@ pub fn run<T, G: GUI<T>>(mut gui: G, window_title: &str) {
let dejavu: &[u8] = include_bytes!("DejaVuSans.ttf"); let dejavu: &[u8] = include_bytes!("DejaVuSans.ttf");
let fonts = vec![Font::from_bytes(dejavu).unwrap()]; let fonts = vec![Font::from_bytes(dejavu).unwrap()];
let vmetrics = fonts[0].v_metrics(Scale::uniform(text::FONT_SIZE));
// TODO This works for this font, but could be more paranoid with abs()
gui.get_mut_canvas().line_height =
(vmetrics.ascent - vmetrics.descent + vmetrics.line_gap) as f64;
gui.get_mut_canvas().glyphs = RefCell::new(Some(GlyphBrush::new(&display, fonts))); gui.get_mut_canvas().glyphs = RefCell::new(Some(GlyphBrush::new(&display, fonts)));
let mut state = State { let mut state = State {

View File

@ -59,7 +59,7 @@ impl<T: Clone> ScrollingMenu<T> {
let can_fit = { let can_fit = {
// Subtract 1 for the prompt, and an additional TODO hacky // Subtract 1 for the prompt, and an additional TODO hacky
// few to avoid the bottom OSD and stuff. // few to avoid the bottom OSD and stuff.
let n = (canvas.window_height / text::LINE_HEIGHT).floor() as isize - 1 - 6; let n = (canvas.window_height / canvas.line_height).floor() as isize - 1 - 6;
if n <= 0 { if n <= 0 {
// Weird small window, just display the prompt and bail out. // Weird small window, just display the prompt and bail out.
canvas.draw_blocking_text(g, txt, CENTERED); canvas.draw_blocking_text(g, txt, CENTERED);

View File

@ -14,10 +14,8 @@ pub const SELECTED_COLOR: Color = Color::RED;
pub const HOTKEY_COLOR: Color = Color::GREEN; pub const HOTKEY_COLOR: Color = Color::GREEN;
pub const INACTIVE_CHOICE_COLOR: Color = Color::grey(0.4); pub const INACTIVE_CHOICE_COLOR: Color = Color::grey(0.4);
const FONT_SIZE: f32 = 24.0; pub const FONT_SIZE: f32 = 24.0;
// TODO These are dependent on FONT_SIZE, but hand-tuned. Glyphs all have 0 as their height, and // TODO Don't do this!
// they need adjustments to their positioning.
pub const LINE_HEIGHT: f64 = 32.0;
const MAX_CHAR_WIDTH: f64 = 25.0; const MAX_CHAR_WIDTH: f64 = 25.0;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -117,34 +115,36 @@ impl Text {
self.lines.is_empty() self.lines.is_empty()
} }
pub(crate) fn dims(&self, glyphs: &mut GlyphBrush<'static, 'static>) -> (f64, f64) { pub(crate) fn dims(
let mut widths: Vec<i32> = Vec::new(); &self,
let mut total_height: i32 = 0; glyphs: &mut GlyphBrush<'static, 'static>,
canvas: &Canvas,
) -> (f64, f64) {
let width = self
.lines
.iter()
.map(|(_, l)| {
let full_line = l.iter().fold(String::new(), |mut so_far, span| {
so_far.push_str(&span.text);
so_far
});
if let Some(rect) = glyphs.pixel_bounds(Section {
text: &full_line,
scale: Scale::uniform(FONT_SIZE),
..Section::default()
}) {
rect.width()
} else {
// TODO Sometimes we want to space something like " ", but no drawn glyphs
// means pixel_bounds fails. Hack?
(MAX_CHAR_WIDTH * (full_line.len() as f64)) as i32
}
})
.max()
.unwrap() as f64;
for (_, l) in &self.lines { // Always use the max height, since other stuff like menus assume a fixed height.
let full_line = l.iter().fold(String::new(), |mut so_far, span| { (width, (self.lines.len() as f64) * canvas.line_height)
so_far.push_str(&span.text);
so_far
});
if let Some(rect) = glyphs.pixel_bounds(Section {
text: &full_line,
scale: Scale::uniform(FONT_SIZE),
..Section::default()
}) {
widths.push(rect.width());
total_height += rect.height();
} else {
// TODO Sometimes we want to space something like " ", but no drawn glyphs
// means pixel_bounds fails. Hack?
widths.push((MAX_CHAR_WIDTH * (full_line.len() as f64)) as i32);
total_height += LINE_HEIGHT as i32;
}
}
(
widths.into_iter().max().unwrap() as f64,
total_height as f64,
)
} }
} }
@ -158,7 +158,7 @@ pub fn draw_text_bubble(
// TODO Is it expensive to constantly change uniforms and the shader program? // TODO Is it expensive to constantly change uniforms and the shader program?
g.fork_screenspace(canvas); g.fork_screenspace(canvas);
let (total_width, total_height) = txt.dims(glyphs); let (total_width, total_height) = txt.dims(glyphs, canvas);
if let Some(c) = txt.bg_color { if let Some(c) = txt.bg_color {
g.draw_polygon( g.draw_polygon(
c, c,
@ -185,16 +185,19 @@ pub fn draw_text_bubble(
.collect(), .collect(),
..VariedSection::default() ..VariedSection::default()
}; };
let height = glyphs.pixel_bounds(section.clone()).unwrap().height() as f64;
if let Some(c) = line_color { if let Some(c) = line_color {
g.draw_polygon( g.draw_polygon(
*c, *c,
&Polygon::rectangle_topleft(Pt2D::new(top_left.x, y), total_width, height), &Polygon::rectangle_topleft(
Pt2D::new(top_left.x, y),
total_width,
canvas.line_height,
),
); );
} }
y += height; y += canvas.line_height;
glyphs.queue(section); glyphs.queue(section);
} }

View File

@ -1,7 +1,6 @@
use crate::menu::{Menu, Position}; use crate::menu::{Menu, Position};
use crate::screen_geom::ScreenRectangle; use crate::screen_geom::ScreenRectangle;
use crate::text; use crate::text;
use crate::TOP_MENU_HEIGHT;
use crate::{Canvas, GfxCtx, InputResult, Key, ScreenPt, Text, UserInput}; use crate::{Canvas, GfxCtx, InputResult, Key, ScreenPt, Text, UserInput};
use geom::{Polygon, Pt2D}; use geom::{Polygon, Pt2D};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@ -132,13 +131,17 @@ impl TopMenu {
x1: 0.0, x1: 0.0,
y1: 0.0, y1: 0.0,
x2: canvas.window_width, x2: canvas.window_width,
y2: TOP_MENU_HEIGHT, y2: canvas.line_height,
}); });
g.fork_screenspace(canvas); g.fork_screenspace(canvas);
g.draw_polygon( g.draw_polygon(
text::BG_COLOR, text::BG_COLOR,
&Polygon::rectangle_topleft(Pt2D::new(0.0, 0.0), canvas.window_width, TOP_MENU_HEIGHT), &Polygon::rectangle_topleft(
Pt2D::new(0.0, 0.0),
canvas.window_width,
canvas.line_height,
),
); );
if let Some(idx) = self.highlighted { if let Some(idx) = self.highlighted {