[egui] handle dynamic changes to pixels_per_point (dpi scaling)
Also: egui::Context::new() no longer takes any arguments
This commit is contained in:
parent
efe90c9326
commit
8b1f02f22c
7 changed files with 107 additions and 98 deletions
|
@ -2,7 +2,7 @@ use criterion::{criterion_group, criterion_main, Criterion};
|
|||
|
||||
pub fn criterion_benchmark(c: &mut Criterion) {
|
||||
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 {
|
||||
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| {
|
||||
b.iter(|| {
|
||||
ctx.begin_frame(raw_input.clone());
|
||||
let mut ui = ctx.fullscreen_ui();
|
||||
let mut ui = ctx.begin_frame(raw_input.clone());
|
||||
example_app.ui(&mut ui, "");
|
||||
ctx.end_frame()
|
||||
})
|
||||
|
|
|
@ -16,13 +16,14 @@ struct PaintStats {
|
|||
/// `Ui`:s keep an Arc pointer to this.
|
||||
/// This allows us to create several child `Ui`:s at once,
|
||||
/// all working against the same shared Context.
|
||||
#[derive(Default)]
|
||||
pub struct Context {
|
||||
/// The default style for new `Ui`:s
|
||||
style: Mutex<Style>,
|
||||
paint_options: Mutex<paint::PaintOptions>,
|
||||
fonts: Arc<Fonts>,
|
||||
/// HACK: set a new font next frame
|
||||
new_fonts: Mutex<Option<Arc<Fonts>>>,
|
||||
/// None until first call to `begin_frame`.
|
||||
fonts: Option<Arc<Fonts>>,
|
||||
font_definitions: Mutex<FontDefinitions>,
|
||||
memory: Arc<Mutex<Memory>>,
|
||||
|
||||
input: InputState,
|
||||
|
@ -42,7 +43,7 @@ impl Clone for Context {
|
|||
style: Mutex::new(self.style()),
|
||||
paint_options: Mutex::new(*self.paint_options.lock()),
|
||||
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(),
|
||||
input: self.input.clone(),
|
||||
graphics: Mutex::new(self.graphics.lock().clone()),
|
||||
|
@ -54,21 +55,8 @@ impl Clone for Context {
|
|||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new(pixels_per_point: f32) -> Arc<Context> {
|
||||
Arc::new(Context {
|
||||
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 new() -> Arc<Self> {
|
||||
Arc::new(Self::default())
|
||||
}
|
||||
|
||||
pub fn rect(&self) -> Rect {
|
||||
|
@ -91,17 +79,25 @@ impl Context {
|
|||
&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 {
|
||||
&*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 {
|
||||
self.fonts().texture()
|
||||
}
|
||||
|
||||
/// Will become active next frame
|
||||
pub fn set_fonts(&self, fonts: Fonts) {
|
||||
*self.new_fonts.lock() = Some(Arc::new(fonts));
|
||||
/// Will become active at the start of the next frame.
|
||||
/// `pixels_per_point` will be ignored (overwitten at start of each frame with the contents of input)
|
||||
pub fn set_fonts(&self, font_definitions: FontDefinitions) {
|
||||
*self.font_definitions.lock() = font_definitions;
|
||||
}
|
||||
|
||||
// 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();
|
||||
self_.begin_frame_mut(new_input);
|
||||
*self = Arc::new(self_);
|
||||
self.fullscreen_ui()
|
||||
}
|
||||
|
||||
fn begin_frame_mut(&mut self, new_raw_input: RawInput) {
|
||||
|
@ -150,13 +149,19 @@ impl Context {
|
|||
|
||||
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);
|
||||
|
||||
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) {
|
||||
self.memory().end_frame();
|
||||
let output: Output = std::mem::take(&mut self.output());
|
||||
|
@ -195,7 +200,7 @@ impl Context {
|
|||
// ---------------------------------------------------------------------
|
||||
|
||||
/// 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 id = Id::background();
|
||||
let layer = Layer {
|
||||
|
@ -365,7 +370,7 @@ impl Context {
|
|||
let align = (Align::Min, Align::Min);
|
||||
let layer = Layer::debug();
|
||||
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 rect = align_rect(Rect::from_min_size(pos, galley.size), align);
|
||||
self.add_paint_cmd(
|
||||
|
@ -422,7 +427,7 @@ impl Context {
|
|||
align: (Align, Align),
|
||||
text_color: Option<Color>,
|
||||
) -> Rect {
|
||||
let font = &self.fonts[text_style];
|
||||
let font = &self.fonts()[text_style];
|
||||
let galley = font.layout_multiline(text, f32::INFINITY);
|
||||
let rect = align_rect(Rect::from_min_size(pos, galley.size), align);
|
||||
self.add_galley(layer, rect.min, galley, text_style, text_color);
|
||||
|
@ -471,17 +476,10 @@ impl Context {
|
|||
CollapsingHeader::new("Fonts")
|
||||
.default_open(false)
|
||||
.show(ui, |ui| {
|
||||
let old_font_definitions = self.fonts().definitions();
|
||||
let mut new_font_definitions = old_font_definitions.clone();
|
||||
font_definitions_ui(&mut new_font_definitions, ui);
|
||||
let mut font_definitions = self.fonts().definitions().clone();
|
||||
font_definitions.ui(ui);
|
||||
self.fonts().texture().ui(ui);
|
||||
if *old_font_definitions != new_font_definitions {
|
||||
let fonts = Fonts::from_definitions(
|
||||
new_font_definitions,
|
||||
self.input().pixels_per_point,
|
||||
);
|
||||
self.set_fonts(fonts);
|
||||
}
|
||||
self.set_fonts(font_definitions);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
pub fn style_ui(&self, ui: &mut Ui) {
|
||||
let mut style = self.style();
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
//! uis for egui types.
|
||||
use crate::{
|
||||
containers::show_tooltip,
|
||||
label,
|
||||
math::*,
|
||||
paint::{color::WHITE, PaintCmd, Texture, Triangles, Vertex},
|
||||
paint::{self, color::WHITE, PaintCmd, Texture, Triangles, Vertex},
|
||||
*,
|
||||
};
|
||||
|
||||
impl Texture {
|
||||
pub fn ui(&self, ui: &mut crate::Ui) {
|
||||
pub fn ui(&self, ui: &mut Ui) {
|
||||
ui.add(label!(
|
||||
"Texture size: {} x {} (hover to zoom)",
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,37 +28,47 @@ pub enum FontFamily {
|
|||
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 {
|
||||
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
|
||||
pub fonts: BTreeMap<TextStyle, (FontFamily, f32)>,
|
||||
}
|
||||
|
||||
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 {
|
||||
pixels_per_point: f32,
|
||||
definitions: FontDefinitions,
|
||||
fonts: BTreeMap<TextStyle, Font>,
|
||||
texture: Texture,
|
||||
}
|
||||
|
||||
impl Fonts {
|
||||
pub fn new(pixels_per_point: f32) -> Fonts {
|
||||
Fonts::from_definitions(default_font_definitions(), pixels_per_point)
|
||||
}
|
||||
|
||||
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);
|
||||
pub fn from_definitions(definitions: FontDefinitions) -> Fonts {
|
||||
let mut fonts = Self::default();
|
||||
fonts.set_definitions(definitions);
|
||||
fonts
|
||||
}
|
||||
|
||||
|
@ -66,7 +76,7 @@ impl Fonts {
|
|||
&self.definitions
|
||||
}
|
||||
|
||||
pub fn set_sizes(&mut self, definitions: FontDefinitions) {
|
||||
pub fn set_definitions(&mut self, definitions: FontDefinitions) {
|
||||
if self.definitions == definitions {
|
||||
return;
|
||||
}
|
||||
|
@ -93,7 +103,11 @@ impl Fonts {
|
|||
// let variable_typeface_data = include_bytes!("../../fonts/DejaVuSans.ttf"); // Basic, boring, takes up more space
|
||||
|
||||
self.definitions = definitions.clone();
|
||||
self.fonts = definitions
|
||||
let FontDefinitions {
|
||||
pixels_per_point,
|
||||
fonts,
|
||||
} = definitions;
|
||||
self.fonts = fonts
|
||||
.into_iter()
|
||||
.map(|(text_style, (family, size))| {
|
||||
let typeface_data: &[u8] = match family {
|
||||
|
@ -103,7 +117,7 @@ impl Fonts {
|
|||
|
||||
(
|
||||
text_style,
|
||||
Font::new(atlas.clone(), typeface_data, size, self.pixels_per_point),
|
||||
Font::new(atlas.clone(), typeface_data, size, pixels_per_point),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
|
|
@ -14,7 +14,7 @@ serde = "1"
|
|||
serde_json = "1"
|
||||
wasm-bindgen = "0.2"
|
||||
|
||||
egui = { path = "../egui" }
|
||||
egui = { path = "../egui", features = ["with_serde"] }
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
|
|
|
@ -50,7 +50,7 @@ fn main() {
|
|||
let context = glutin::ContextBuilder::new();
|
||||
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
|
||||
.gl_window()
|
||||
|
@ -67,7 +67,7 @@ fn main() {
|
|||
|
||||
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", || {
|
||||
egui_glium::Painter::new(&display)
|
||||
});
|
||||
|
@ -111,8 +111,7 @@ fn main() {
|
|||
}
|
||||
|
||||
let egui_start = Instant::now();
|
||||
ctx.begin_frame(raw_input.clone()); // TODO: avoid clone
|
||||
let mut ui = ctx.fullscreen_ui();
|
||||
let mut ui = ctx.begin_frame(raw_input.clone()); // TODO: avoid clone
|
||||
example_app.ui(&mut ui, "");
|
||||
let mut ui = ui.centered_column(ui.available().width().min(480.0));
|
||||
ui.set_layout(Layout::vertical(Align::Min));
|
||||
|
|
|
@ -38,8 +38,8 @@ pub struct State {
|
|||
}
|
||||
|
||||
impl State {
|
||||
fn new(canvas_id: &str, pixels_per_point: f32) -> Result<State, JsValue> {
|
||||
let ctx = Context::new(pixels_per_point);
|
||||
fn new(canvas_id: &str) -> Result<State, JsValue> {
|
||||
let ctx = Context::new();
|
||||
egui_wasm::load_memory(&ctx);
|
||||
Ok(State {
|
||||
example_app: Default::default(),
|
||||
|
@ -52,9 +52,7 @@ impl State {
|
|||
fn run(&mut self, web_input: WebInput) -> Result<Output, JsValue> {
|
||||
let everything_start = now_sec();
|
||||
|
||||
self.ctx.begin_frame(web_input.egui);
|
||||
|
||||
let mut ui = self.ctx.fullscreen_ui();
|
||||
let mut ui = self.ctx.begin_frame(web_input.egui);
|
||||
self.example_app.ui(&mut ui, &web_input.web.location_hash);
|
||||
let mut ui = ui.centered_column(ui.available().width().min(480.0));
|
||||
ui.set_layout(Layout::vertical(Align::Min));
|
||||
|
@ -111,8 +109,8 @@ impl State {
|
|||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn new_webgl_gui(canvas_id: &str, pixels_per_point: f32) -> Result<State, JsValue> {
|
||||
State::new(canvas_id, pixels_per_point)
|
||||
pub fn new_webgl_gui(canvas_id: &str) -> Result<State, JsValue> {
|
||||
State::new(canvas_id)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
|
|
Loading…
Reference in a new issue