[egui] handle dynamic changes to pixels_per_point (dpi scaling)

Also: egui::Context::new() no longer takes any arguments
This commit is contained in:
Emil Ernerfeldt 2020-05-30 14:56:38 +02:00
parent efe90c9326
commit 8b1f02f22c
7 changed files with 107 additions and 98 deletions

View file

@ -2,7 +2,7 @@ use criterion::{criterion_group, criterion_main, Criterion};
pub fn criterion_benchmark(c: &mut Criterion) { pub fn criterion_benchmark(c: &mut Criterion) {
let mut example_app = egui::examples::ExampleApp::default(); let mut example_app = egui::examples::ExampleApp::default();
let mut ctx = egui::Context::new(1.0); let mut ctx = egui::Context::new();
let raw_input = egui::RawInput { let raw_input = egui::RawInput {
screen_size: egui::vec2(1280.0, 1024.0), screen_size: egui::vec2(1280.0, 1024.0),
@ -11,8 +11,7 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("example_app", |b| { c.bench_function("example_app", |b| {
b.iter(|| { b.iter(|| {
ctx.begin_frame(raw_input.clone()); let mut ui = ctx.begin_frame(raw_input.clone());
let mut ui = ctx.fullscreen_ui();
example_app.ui(&mut ui, ""); example_app.ui(&mut ui, "");
ctx.end_frame() ctx.end_frame()
}) })

View file

@ -16,13 +16,14 @@ struct PaintStats {
/// `Ui`:s keep an Arc pointer to this. /// `Ui`:s keep an Arc pointer to this.
/// This allows us to create several child `Ui`:s at once, /// This allows us to create several child `Ui`:s at once,
/// all working against the same shared Context. /// all working against the same shared Context.
#[derive(Default)]
pub struct Context { pub struct Context {
/// The default style for new `Ui`:s /// The default style for new `Ui`:s
style: Mutex<Style>, style: Mutex<Style>,
paint_options: Mutex<paint::PaintOptions>, paint_options: Mutex<paint::PaintOptions>,
fonts: Arc<Fonts>, /// None until first call to `begin_frame`.
/// HACK: set a new font next frame fonts: Option<Arc<Fonts>>,
new_fonts: Mutex<Option<Arc<Fonts>>>, font_definitions: Mutex<FontDefinitions>,
memory: Arc<Mutex<Memory>>, memory: Arc<Mutex<Memory>>,
input: InputState, input: InputState,
@ -42,7 +43,7 @@ impl Clone for Context {
style: Mutex::new(self.style()), style: Mutex::new(self.style()),
paint_options: Mutex::new(*self.paint_options.lock()), paint_options: Mutex::new(*self.paint_options.lock()),
fonts: self.fonts.clone(), fonts: self.fonts.clone(),
new_fonts: Mutex::new(self.new_fonts.lock().clone()), font_definitions: Mutex::new(self.font_definitions.lock().clone()),
memory: self.memory.clone(), memory: self.memory.clone(),
input: self.input.clone(), input: self.input.clone(),
graphics: Mutex::new(self.graphics.lock().clone()), graphics: Mutex::new(self.graphics.lock().clone()),
@ -54,21 +55,8 @@ impl Clone for Context {
} }
impl Context { impl Context {
pub fn new(pixels_per_point: f32) -> Arc<Context> { pub fn new() -> Arc<Self> {
Arc::new(Context { Arc::new(Self::default())
style: Default::default(),
paint_options: Default::default(),
fonts: Arc::new(Fonts::new(pixels_per_point)),
new_fonts: Default::default(),
memory: Default::default(),
input: Default::default(),
graphics: Default::default(),
output: Default::default(),
used_ids: Default::default(),
paint_stats: Default::default(),
})
} }
pub fn rect(&self) -> Rect { pub fn rect(&self) -> Rect {
@ -91,17 +79,25 @@ impl Context {
&self.input &self.input
} }
/// Not valid until first call to `begin_frame()`
/// That's because since we don't know the proper `pixels_per_point` until then.
pub fn fonts(&self) -> &Fonts { pub fn fonts(&self) -> &Fonts {
&*self.fonts &*self
.fonts
.as_ref()
.expect("No fonts available until first call to Contex::begin_frame()`")
} }
/// Not valid until first call to `begin_frame()`
/// That's because since we don't know the proper `pixels_per_point` until then.
pub fn texture(&self) -> &paint::Texture { pub fn texture(&self) -> &paint::Texture {
self.fonts().texture() self.fonts().texture()
} }
/// Will become active next frame /// Will become active at the start of the next frame.
pub fn set_fonts(&self, fonts: Fonts) { /// `pixels_per_point` will be ignored (overwitten at start of each frame with the contents of input)
*self.new_fonts.lock() = Some(Arc::new(fonts)); pub fn set_fonts(&self, font_definitions: FontDefinitions) {
*self.font_definitions.lock() = font_definitions;
} }
// TODO: return MutexGuard // TODO: return MutexGuard
@ -139,10 +135,13 @@ impl Context {
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
pub fn begin_frame(self: &mut Arc<Self>, new_input: RawInput) { /// Call at the start of every frame.
/// Returns a master fullscreen UI, covering the entire screen.
pub fn begin_frame(self: &mut Arc<Self>, new_input: RawInput) -> Ui {
let mut self_: Self = (**self).clone(); let mut self_: Self = (**self).clone();
self_.begin_frame_mut(new_input); self_.begin_frame_mut(new_input);
*self = Arc::new(self_); *self = Arc::new(self_);
self.fullscreen_ui()
} }
fn begin_frame_mut(&mut self, new_raw_input: RawInput) { fn begin_frame_mut(&mut self, new_raw_input: RawInput) {
@ -150,13 +149,19 @@ impl Context {
self.used_ids.lock().clear(); self.used_ids.lock().clear();
if let Some(new_fonts) = self.new_fonts.lock().take() {
self.fonts = new_fonts;
}
self.input = std::mem::take(&mut self.input).begin_frame(new_raw_input); self.input = std::mem::take(&mut self.input).begin_frame(new_raw_input);
let mut font_definitions = self.font_definitions.lock();
font_definitions.pixels_per_point = self.input.pixels_per_point;
if self.fonts.is_none() || *self.fonts.as_ref().unwrap().definitions() != *font_definitions
{
self.fonts = Some(Arc::new(Fonts::from_definitions(font_definitions.clone())));
}
} }
/// Call at the end of each frame.
/// Returns what has happened this frame (`Output`) as well as what you need to paint.
#[must_use]
pub fn end_frame(&self) -> (Output, PaintBatches) { pub fn end_frame(&self) -> (Output, PaintBatches) {
self.memory().end_frame(); self.memory().end_frame();
let output: Output = std::mem::take(&mut self.output()); let output: Output = std::mem::take(&mut self.output());
@ -195,7 +200,7 @@ impl Context {
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
/// A `Ui` for the entire screen, behind any windows. /// A `Ui` for the entire screen, behind any windows.
pub fn fullscreen_ui(self: &Arc<Self>) -> Ui { fn fullscreen_ui(self: &Arc<Self>) -> Ui {
let rect = Rect::from_min_size(Default::default(), self.input().screen_size); let rect = Rect::from_min_size(Default::default(), self.input().screen_size);
let id = Id::background(); let id = Id::background();
let layer = Layer { let layer = Layer {
@ -365,7 +370,7 @@ impl Context {
let align = (Align::Min, Align::Min); let align = (Align::Min, Align::Min);
let layer = Layer::debug(); let layer = Layer::debug();
let text_style = TextStyle::Monospace; let text_style = TextStyle::Monospace;
let font = &self.fonts[text_style]; let font = &self.fonts()[text_style];
let galley = font.layout_multiline(text, f32::INFINITY); let galley = font.layout_multiline(text, f32::INFINITY);
let rect = align_rect(Rect::from_min_size(pos, galley.size), align); let rect = align_rect(Rect::from_min_size(pos, galley.size), align);
self.add_paint_cmd( self.add_paint_cmd(
@ -422,7 +427,7 @@ impl Context {
align: (Align, Align), align: (Align, Align),
text_color: Option<Color>, text_color: Option<Color>,
) -> Rect { ) -> Rect {
let font = &self.fonts[text_style]; let font = &self.fonts()[text_style];
let galley = font.layout_multiline(text, f32::INFINITY); let galley = font.layout_multiline(text, f32::INFINITY);
let rect = align_rect(Rect::from_min_size(pos, galley.size), align); let rect = align_rect(Rect::from_min_size(pos, galley.size), align);
self.add_galley(layer, rect.min, galley, text_style, text_color); self.add_galley(layer, rect.min, galley, text_style, text_color);
@ -471,17 +476,10 @@ impl Context {
CollapsingHeader::new("Fonts") CollapsingHeader::new("Fonts")
.default_open(false) .default_open(false)
.show(ui, |ui| { .show(ui, |ui| {
let old_font_definitions = self.fonts().definitions(); let mut font_definitions = self.fonts().definitions().clone();
let mut new_font_definitions = old_font_definitions.clone(); font_definitions.ui(ui);
font_definitions_ui(&mut new_font_definitions, ui);
self.fonts().texture().ui(ui); self.fonts().texture().ui(ui);
if *old_font_definitions != new_font_definitions { self.set_fonts(font_definitions);
let fonts = Fonts::from_definitions(
new_font_definitions,
self.input().pixels_per_point,
);
self.set_fonts(fonts);
}
}); });
} }
@ -564,21 +562,6 @@ impl Context {
} }
} }
fn font_definitions_ui(font_definitions: &mut paint::FontDefinitions, ui: &mut Ui) {
use crate::widgets::*;
for (text_style, (_family, size)) in font_definitions.iter_mut() {
// TODO: radiobutton for family
ui.add(
Slider::f32(size, 4.0..=40.0)
.precision(0)
.text(format!("{:?}", text_style)),
);
}
if ui.add(Button::new("Reset fonts")).clicked {
*font_definitions = paint::fonts::default_font_definitions();
}
}
impl Context { impl Context {
pub fn style_ui(&self, ui: &mut Ui) { pub fn style_ui(&self, ui: &mut Ui) {
let mut style = self.style(); let mut style = self.style();

View file

@ -1,13 +1,13 @@
//! uis for egui types. //! uis for egui types.
use crate::{ use crate::{
containers::show_tooltip, containers::show_tooltip,
label,
math::*, math::*,
paint::{color::WHITE, PaintCmd, Texture, Triangles, Vertex}, paint::{self, color::WHITE, PaintCmd, Texture, Triangles, Vertex},
*,
}; };
impl Texture { impl Texture {
pub fn ui(&self, ui: &mut crate::Ui) { pub fn ui(&self, ui: &mut Ui) {
ui.add(label!( ui.add(label!(
"Texture size: {} x {} (hover to zoom)", "Texture size: {} x {} (hover to zoom)",
self.width, self.width,
@ -60,3 +60,19 @@ impl Texture {
} }
} }
} }
impl paint::FontDefinitions {
pub fn ui(&mut self, ui: &mut Ui) {
for (text_style, (_family, size)) in self.fonts.iter_mut() {
// TODO: radiobutton for family
ui.add(
Slider::f32(size, 4.0..=40.0)
.precision(0)
.text(format!("{:?}", text_style)),
);
}
if ui.add(Button::new("Reset fonts")).clicked {
*self = paint::FontDefinitions::with_pixels_per_point(self.pixels_per_point);
}
}
}

View file

@ -28,37 +28,47 @@ pub enum FontFamily {
VariableWidth, VariableWidth,
} }
pub type FontDefinitions = BTreeMap<TextStyle, (FontFamily, f32)>; #[derive(Clone, Debug, PartialEq)]
pub struct FontDefinitions {
/// The dpi scale factor. Needed to get pixel perfect fonts.
pub pixels_per_point: f32,
pub fn default_font_definitions() -> FontDefinitions { pub fonts: BTreeMap<TextStyle, (FontFamily, f32)>,
let mut definitions = FontDefinitions::new();
definitions.insert(TextStyle::Body, (FontFamily::VariableWidth, 14.0));
definitions.insert(TextStyle::Button, (FontFamily::VariableWidth, 16.0));
definitions.insert(TextStyle::Heading, (FontFamily::VariableWidth, 24.0));
definitions.insert(TextStyle::Monospace, (FontFamily::Monospace, 13.0));
definitions
} }
impl Default for FontDefinitions {
fn default() -> Self {
Self::with_pixels_per_point(f32::NAN) // must be set later
}
}
impl FontDefinitions {
pub fn with_pixels_per_point(pixels_per_point: f32) -> Self {
let mut fonts = BTreeMap::new();
fonts.insert(TextStyle::Body, (FontFamily::VariableWidth, 14.0));
fonts.insert(TextStyle::Button, (FontFamily::VariableWidth, 16.0));
fonts.insert(TextStyle::Heading, (FontFamily::VariableWidth, 24.0));
fonts.insert(TextStyle::Monospace, (FontFamily::Monospace, 13.0));
Self {
pixels_per_point,
fonts,
}
}
}
/// Note: the `default()` fonts are invalid (missing `pixels_per_point`).
#[derive(Default)]
pub struct Fonts { pub struct Fonts {
pixels_per_point: f32,
definitions: FontDefinitions, definitions: FontDefinitions,
fonts: BTreeMap<TextStyle, Font>, fonts: BTreeMap<TextStyle, Font>,
texture: Texture, texture: Texture,
} }
impl Fonts { impl Fonts {
pub fn new(pixels_per_point: f32) -> Fonts { pub fn from_definitions(definitions: FontDefinitions) -> Fonts {
Fonts::from_definitions(default_font_definitions(), pixels_per_point) let mut fonts = Self::default();
} fonts.set_definitions(definitions);
pub fn from_definitions(definitions: FontDefinitions, pixels_per_point: f32) -> Fonts {
let mut fonts = Fonts {
pixels_per_point,
definitions: Default::default(),
fonts: Default::default(),
texture: Default::default(),
};
fonts.set_sizes(definitions);
fonts fonts
} }
@ -66,7 +76,7 @@ impl Fonts {
&self.definitions &self.definitions
} }
pub fn set_sizes(&mut self, definitions: FontDefinitions) { pub fn set_definitions(&mut self, definitions: FontDefinitions) {
if self.definitions == definitions { if self.definitions == definitions {
return; return;
} }
@ -93,7 +103,11 @@ impl Fonts {
// let variable_typeface_data = include_bytes!("../../fonts/DejaVuSans.ttf"); // Basic, boring, takes up more space // let variable_typeface_data = include_bytes!("../../fonts/DejaVuSans.ttf"); // Basic, boring, takes up more space
self.definitions = definitions.clone(); self.definitions = definitions.clone();
self.fonts = definitions let FontDefinitions {
pixels_per_point,
fonts,
} = definitions;
self.fonts = fonts
.into_iter() .into_iter()
.map(|(text_style, (family, size))| { .map(|(text_style, (family, size))| {
let typeface_data: &[u8] = match family { let typeface_data: &[u8] = match family {
@ -103,7 +117,7 @@ impl Fonts {
( (
text_style, text_style,
Font::new(atlas.clone(), typeface_data, size, self.pixels_per_point), Font::new(atlas.clone(), typeface_data, size, pixels_per_point),
) )
}) })
.collect(); .collect();

View file

@ -14,7 +14,7 @@ serde = "1"
serde_json = "1" serde_json = "1"
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
egui = { path = "../egui" } egui = { path = "../egui", features = ["with_serde"] }
[dependencies.web-sys] [dependencies.web-sys]
version = "0.3" version = "0.3"

View file

@ -50,7 +50,7 @@ fn main() {
let context = glutin::ContextBuilder::new(); let context = glutin::ContextBuilder::new();
let display = glium::Display::new(window, context, &events_loop).unwrap(); let display = glium::Display::new(window, context, &events_loop).unwrap();
let size = window_settings.size.unwrap_or(vec2(1024.0, 800.0)); let size = window_settings.size.unwrap_or_else(|| vec2(1024.0, 800.0));
display display
.gl_window() .gl_window()
@ -67,7 +67,7 @@ fn main() {
let pixels_per_point = display.gl_window().get_hidpi_factor() as f32; let pixels_per_point = display.gl_window().get_hidpi_factor() as f32;
let mut ctx = profile("initializing emilib", || Context::new(pixels_per_point)); let mut ctx = profile("initializing emilib", Context::new);
let mut painter = profile("initializing painter", || { let mut painter = profile("initializing painter", || {
egui_glium::Painter::new(&display) egui_glium::Painter::new(&display)
}); });
@ -111,8 +111,7 @@ fn main() {
} }
let egui_start = Instant::now(); let egui_start = Instant::now();
ctx.begin_frame(raw_input.clone()); // TODO: avoid clone let mut ui = ctx.begin_frame(raw_input.clone()); // TODO: avoid clone
let mut ui = ctx.fullscreen_ui();
example_app.ui(&mut ui, ""); example_app.ui(&mut ui, "");
let mut ui = ui.centered_column(ui.available().width().min(480.0)); let mut ui = ui.centered_column(ui.available().width().min(480.0));
ui.set_layout(Layout::vertical(Align::Min)); ui.set_layout(Layout::vertical(Align::Min));

View file

@ -38,8 +38,8 @@ pub struct State {
} }
impl State { impl State {
fn new(canvas_id: &str, pixels_per_point: f32) -> Result<State, JsValue> { fn new(canvas_id: &str) -> Result<State, JsValue> {
let ctx = Context::new(pixels_per_point); let ctx = Context::new();
egui_wasm::load_memory(&ctx); egui_wasm::load_memory(&ctx);
Ok(State { Ok(State {
example_app: Default::default(), example_app: Default::default(),
@ -52,9 +52,7 @@ impl State {
fn run(&mut self, web_input: WebInput) -> Result<Output, JsValue> { fn run(&mut self, web_input: WebInput) -> Result<Output, JsValue> {
let everything_start = now_sec(); let everything_start = now_sec();
self.ctx.begin_frame(web_input.egui); let mut ui = self.ctx.begin_frame(web_input.egui);
let mut ui = self.ctx.fullscreen_ui();
self.example_app.ui(&mut ui, &web_input.web.location_hash); self.example_app.ui(&mut ui, &web_input.web.location_hash);
let mut ui = ui.centered_column(ui.available().width().min(480.0)); let mut ui = ui.centered_column(ui.available().width().min(480.0));
ui.set_layout(Layout::vertical(Align::Min)); ui.set_layout(Layout::vertical(Align::Min));
@ -111,8 +109,8 @@ impl State {
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn new_webgl_gui(canvas_id: &str, pixels_per_point: f32) -> Result<State, JsValue> { pub fn new_webgl_gui(canvas_id: &str) -> Result<State, JsValue> {
State::new(canvas_id, pixels_per_point) State::new(canvas_id)
} }
#[wasm_bindgen] #[wasm_bindgen]