Merge struct Emigui into Context

This commit is contained in:
Emil Ernerfeldt 2020-05-08 22:25:28 +02:00
parent e317f697c0
commit 9f6e9c94d6
7 changed files with 205 additions and 203 deletions

View file

@ -84,7 +84,7 @@ Add extremely quick animations for some things, maybe 2-3 frames. For instance:
### Names and structure
* [ ] Rename things to be more consistent with Dear ImGui
* [ ] Combine Emigui and Context?
* [x] Combine Emigui and Context?
* [ ] Solve which parts of Context are behind a mutex
* [ ] All of Context behind one mutex?
* [ } Break up Context into Input, State, Output ?

View file

@ -4,6 +4,14 @@ use parking_lot::Mutex;
use crate::{layout::align_rect, *};
#[derive(Clone, Copy, Default)]
struct PaintStats {
num_batches: usize,
num_primitives: usize,
num_vertices: usize,
num_triangles: usize,
}
/// Contains the input, style and output of all GUI commands.
/// Regions keep an Arc pointer to this.
/// This allows us to create several child regions at once,
@ -11,10 +19,11 @@ use crate::{layout::align_rect, *};
pub struct Context {
/// The default style for new regions
style: Mutex<Style>,
mesher_options: Mutex<mesher::MesherOptions>,
fonts: Arc<Fonts>,
memory: Mutex<Memory>,
/// Used to debug name clashes of e.g. windows
used_ids: Mutex<HashMap<Id, Pos2>>,
/// HACK: set a new font next frame
new_fonts: Mutex<Option<Arc<Fonts>>>,
memory: Arc<Mutex<Memory>>,
// Input releated stuff:
/// Raw input from last frame. Use `input()` instead.
@ -25,6 +34,10 @@ pub struct Context {
// The output of a frame:
graphics: Mutex<GraphicLayers>,
output: Mutex<Output>,
/// Used to debug name clashes of e.g. windows
used_ids: Mutex<HashMap<Id, Pos2>>,
paint_stats: Mutex<PaintStats>,
}
// TODO: remove this impl.
@ -32,31 +45,39 @@ impl Clone for Context {
fn clone(&self) -> Self {
Context {
style: Mutex::new(self.style()),
mesher_options: Mutex::new(*self.mesher_options.lock()),
fonts: self.fonts.clone(),
new_fonts: Mutex::new(self.new_fonts.lock().clone()),
memory: self.memory.clone(),
last_raw_input: self.last_raw_input.clone(),
input: self.input.clone(),
mouse_tracker: self.mouse_tracker.clone(),
memory: Mutex::new(self.memory.lock().clone()),
graphics: Mutex::new(self.graphics.lock().clone()),
output: Mutex::new(self.output.lock().clone()),
used_ids: Mutex::new(self.used_ids.lock().clone()),
paint_stats: Mutex::new(*self.paint_stats.lock()),
}
}
}
impl Context {
pub fn new(pixels_per_point: f32) -> Context {
Context {
pub fn new(pixels_per_point: f32) -> Arc<Context> {
Arc::new(Context {
style: Default::default(),
mesher_options: Default::default(),
fonts: Arc::new(Fonts::new(pixels_per_point)),
new_fonts: Default::default(),
memory: Default::default(),
last_raw_input: Default::default(),
input: Default::default(),
mouse_tracker: MovementTracker::new(1000, 0.1),
memory: Default::default(),
graphics: Default::default(),
output: Default::default(),
used_ids: Default::default(),
}
paint_stats: Default::default(),
})
}
pub fn memory(&self) -> parking_lot::MutexGuard<'_, Memory> {
@ -89,8 +110,13 @@ impl Context {
&*self.fonts
}
pub fn set_fonts(&mut self, fonts: Fonts) {
self.fonts = Arc::new(fonts);
pub fn texture(&self) -> &Texture {
self.fonts().texture()
}
/// Will become active next frame
pub fn set_fonts(&self, fonts: Fonts) {
*self.new_fonts.lock() = Some(Arc::new(fonts));
}
pub fn style(&self) -> Style {
@ -118,13 +144,25 @@ impl Context {
vec2(self.round_to_pixel(vec.x), self.round_to_pixel(vec.y))
}
pub fn begin_frame(&mut self, new_input: RawInput) {
// ---------------------------------------------------------------------
pub fn begin_frame(self: &mut Arc<Self>, new_input: RawInput) {
let mut self_: Self = (**self).clone();
self_.begin_frame_mut(new_input);
*self = Arc::new(self_);
}
fn begin_frame_mut(&mut self, new_input: RawInput) {
if !self.last_raw_input.mouse_down || self.last_raw_input.mouse_pos.is_none() {
self.memory().active_id = None;
}
self.used_ids.lock().clear();
if let Some(new_fonts) = self.new_fonts.lock().take() {
self.fonts = new_fonts;
}
if let Some(mouse_pos) = new_input.mouse_pos {
self.mouse_tracker.add(new_input.time, mouse_pos);
} else {
@ -135,15 +173,48 @@ impl Context {
self.last_raw_input = new_input;
}
pub fn end_frame(&self) -> Output {
std::mem::take(&mut self.output())
pub fn end_frame(&self) -> (Output, PaintBatches) {
let output: Output = std::mem::take(&mut self.output());
let paint_batches = self.paint();
(output, paint_batches)
}
pub fn drain_paint_lists(&self) -> Vec<(Rect, PaintCmd)> {
fn drain_paint_lists(&self) -> Vec<(Rect, PaintCmd)> {
let memory = self.memory();
self.graphics().drain(&memory.floating_order).collect()
}
fn paint(&self) -> PaintBatches {
let mut mesher_options = *self.mesher_options.lock();
mesher_options.aa_size = 1.0 / self.pixels_per_point();
let paint_commands = self.drain_paint_lists();
let num_primitives = paint_commands.len();
let batches = mesher::mesh_paint_commands(mesher_options, self.fonts(), paint_commands);
{
let mut stats = PaintStats::default();
stats.num_batches = batches.len();
stats.num_primitives = num_primitives;
for (_, mesh) in &batches {
stats.num_vertices += mesh.vertices.len();
stats.num_triangles += mesh.indices.len() / 3;
}
*self.paint_stats.lock() = stats;
}
batches
}
// ---------------------------------------------------------------------
/// A region for the entire screen, behind any windows.
pub fn background_region(self: &Arc<Self>) -> Region {
let rect = Rect::from_min_size(Default::default(), self.input().screen_size);
Region::new(self.clone(), Layer::Background, Id::background(), rect)
}
// ---------------------------------------------------------------------
/// Is the user interacting with anything?
pub fn any_active(&self) -> bool {
self.memory().active_id.is_some()
@ -257,6 +328,8 @@ impl Context {
}
}
// ---------------------------------------------------------------------
pub fn show_error(&self, pos: Pos2, text: &str) {
let align = (Align::Min, Align::Min);
let layer = Layer::Debug;
@ -354,6 +427,72 @@ impl Context {
}
}
impl Context {
pub fn ui(&self, region: &mut Region) {
use crate::containers::*;
region.collapsing("Style", |region| {
self.mesher_options.lock().ui(region);
self.style_ui(region);
});
region.collapsing("Fonts", |region| {
let old_font_definitions = self.fonts().definitions();
let mut new_font_definitions = old_font_definitions.clone();
font_definitions_ui(&mut new_font_definitions, region);
self.fonts().texture().ui(region);
if *old_font_definitions != new_font_definitions {
let fonts =
Fonts::from_definitions(new_font_definitions, self.input().pixels_per_point);
self.set_fonts(fonts);
}
});
region.collapsing("Input", |region| {
CollapsingHeader::new("Raw Input")
.default_open()
.show(region, |region| {
region.ctx().last_raw_input().clone().ui(region)
});
CollapsingHeader::new("Input")
.default_open()
.show(region, |region| region.input().clone().ui(region));
});
region.collapsing("Stats", |region| {
region.add(label!(
"Screen size: {} x {} points, pixels_per_point: {}",
region.input().screen_size.x,
region.input().screen_size.y,
region.input().pixels_per_point,
));
if let Some(mouse_pos) = region.input().mouse_pos {
region.add(label!("mouse_pos: {:.2} x {:.2}", mouse_pos.x, mouse_pos.y,));
} else {
region.add_label("mouse_pos: None");
}
region.add(label!("Painting:").text_style(TextStyle::Heading));
self.paint_stats.lock().ui(region);
});
}
}
fn font_definitions_ui(font_definitions: &mut FontDefinitions, region: &mut Region) {
use crate::widgets::*;
for (text_style, (_family, size)) in font_definitions.iter_mut() {
// TODO: radiobutton for family
region.add(
Slider::f32(size, 4.0..=40.0)
.precision(0)
.text(format!("{:?}", text_style)),
);
}
if region.add(Button::new("Reset fonts")).clicked {
*font_definitions = crate::fonts::default_font_definitions();
}
}
impl Context {
pub fn style_ui(&self, region: &mut Region) {
let mut style = self.style();
@ -361,3 +500,27 @@ impl Context {
self.set_style(style);
}
}
impl mesher::MesherOptions {
pub fn ui(&mut self, region: &mut Region) {
use crate::widgets::*;
region.add(Checkbox::new(&mut self.anti_alias, "Antialias"));
region.add(Checkbox::new(
&mut self.debug_paint_clip_rects,
"Paint Clip Rects (debug)",
));
}
}
impl PaintStats {
pub fn ui(&self, region: &mut Region) {
region
.add(label!("Batches: {}", self.num_batches))
.tooltip_text("Number of separate clip rectanlges");
region
.add(label!("Primitives: {}", self.num_primitives))
.tooltip_text("Boxes, circles, text areas etc");
region.add(label!("Vertices: {}", self.num_vertices));
region.add(label!("Triangles: {}", self.num_triangles));
}
}

View file

@ -1,152 +0,0 @@
use std::sync::Arc;
use crate::{containers::*, mesher::*, widgets::*, *};
#[derive(Clone, Copy, Default)]
struct Stats {
num_batches: usize,
num_primitives: usize,
num_vertices: usize,
num_triangles: usize,
}
/// Encapsulates input, layout and painting for ease of use.
/// TODO: merge into Context, and have generations of Context instead.
pub struct Emigui {
ctx: Arc<Context>,
stats: Stats,
mesher_options: MesherOptions,
}
impl Emigui {
pub fn new(pixels_per_point: f32) -> Emigui {
Emigui {
ctx: Arc::new(Context::new(pixels_per_point)),
stats: Default::default(),
mesher_options: MesherOptions::default(),
}
}
pub fn ctx(&self) -> &Arc<Context> {
&self.ctx
}
pub fn texture(&self) -> &Texture {
self.ctx.fonts().texture()
}
pub fn begin_frame(&mut self, new_input: RawInput) {
// TODO: avoid this clone
let mut new_ctx = (*self.ctx).clone();
new_ctx.begin_frame(new_input);
self.ctx = Arc::new(new_ctx);
}
pub fn end_frame(&mut self) -> (Output, PaintBatches) {
let output = self.ctx.end_frame();
let paint_batches = self.paint();
(output, paint_batches)
}
fn paint(&mut self) -> PaintBatches {
self.mesher_options.aa_size = 1.0 / self.ctx().pixels_per_point();
let paint_commands = self.ctx.drain_paint_lists();
let num_primitives = paint_commands.len();
let batches = mesh_paint_commands(&self.mesher_options, self.ctx.fonts(), paint_commands);
self.stats = Default::default();
self.stats.num_batches = batches.len();
self.stats.num_primitives = num_primitives;
for (_, mesh) in &batches {
self.stats.num_vertices += mesh.vertices.len();
self.stats.num_triangles += mesh.indices.len() / 3;
}
batches
}
/// A region for the entire screen, behind any windows.
pub fn background_region(&mut self) -> Region {
let rect = Rect::from_min_size(Default::default(), self.ctx.input().screen_size);
Region::new(self.ctx.clone(), Layer::Background, Id::background(), rect)
}
}
impl Emigui {
pub fn ui(&mut self, region: &mut Region) {
region.collapsing("Style", |region| {
region.add(Checkbox::new(
&mut self.mesher_options.anti_alias,
"Antialias",
));
region.add(Checkbox::new(
&mut self.mesher_options.debug_paint_clip_rects,
"Paint Clip Rects (debug)",
));
self.ctx.style_ui(region);
});
region.collapsing("Fonts", |region| {
let old_font_definitions = self.ctx.fonts().definitions();
let mut new_font_definitions = old_font_definitions.clone();
font_definitions_ui(&mut new_font_definitions, region);
self.ctx.fonts().texture().ui(region);
if *old_font_definitions != new_font_definitions {
let mut new_ctx = (*self.ctx).clone();
let fonts = Fonts::from_definitions(
new_font_definitions,
self.ctx.input().pixels_per_point,
);
new_ctx.set_fonts(fonts);
self.ctx = Arc::new(new_ctx);
}
});
region.collapsing("Input", |region| {
CollapsingHeader::new("Raw Input")
.default_open()
.show(region, |region| {
region.ctx().last_raw_input().clone().ui(region)
});
CollapsingHeader::new("Input")
.default_open()
.show(region, |region| region.input().clone().ui(region));
});
region.collapsing("Stats", |region| {
region.add(label!(
"Screen size: {} x {} points, pixels_per_point: {}",
region.input().screen_size.x,
region.input().screen_size.y,
region.input().pixels_per_point,
));
if let Some(mouse_pos) = region.input().mouse_pos {
region.add(label!("mouse_pos: {:.2} x {:.2}", mouse_pos.x, mouse_pos.y,));
} else {
region.add_label("mouse_pos: None");
}
region.add(label!("Rendering:").text_style(TextStyle::Heading));
region
.add(label!("Batches: {}", self.stats.num_batches))
.tooltip_text("Number of separate clip rectanlges");
region
.add(label!("Primitives: {}", self.stats.num_primitives))
.tooltip_text("Boxes, circles, text areas etc");
region.add(label!("Vertices: {}", self.stats.num_vertices));
region.add(label!("Triangles: {}", self.stats.num_triangles));
});
}
}
fn font_definitions_ui(font_definitions: &mut FontDefinitions, region: &mut Region) {
for (text_style, (_family, size)) in font_definitions.iter_mut() {
// TODO: radiobutton for family
region.add(
Slider::f32(size, 4.0..=40.0)
.precision(0)
.text(format!("{:?}", text_style)),
);
}
if region.add(Button::new("Reset fonts")).clicked {
*font_definitions = crate::fonts::default_font_definitions();
}
}

View file

@ -25,7 +25,6 @@
pub mod color;
pub mod containers;
mod context;
mod emigui;
pub mod example_app;
mod font;
mod fonts;
@ -44,7 +43,6 @@ mod types;
pub mod widgets;
pub use {
crate::emigui::Emigui,
color::Color,
context::Context,
fonts::{FontDefinitions, Fonts, TextStyle},

View file

@ -240,6 +240,7 @@ pub enum PathType {
}
use self::PathType::{Closed, Open};
#[derive(Clone, Copy)]
pub struct MesherOptions {
pub anti_alias: bool,
pub aa_size: f32,
@ -256,12 +257,7 @@ impl Default for MesherOptions {
}
}
pub fn fill_closed_path(
mesh: &mut Mesh,
options: &MesherOptions,
path: &[PathPoint],
color: Color,
) {
pub fn fill_closed_path(mesh: &mut Mesh, options: MesherOptions, path: &[PathPoint], color: Color) {
let n = path.len() as u32;
let vert = |pos, color| Vertex {
pos,
@ -297,7 +293,7 @@ pub fn fill_closed_path(
pub fn paint_path(
mesh: &mut Mesh,
options: &MesherOptions,
options: MesherOptions,
path_type: PathType,
path: &[PathPoint],
color: Color,
@ -391,12 +387,7 @@ pub fn paint_path(
// ----------------------------------------------------------------------------
pub fn mesh_command(
options: &MesherOptions,
fonts: &Fonts,
command: PaintCmd,
out_mesh: &mut Mesh,
) {
pub fn mesh_command(options: MesherOptions, fonts: &Fonts, command: PaintCmd, out_mesh: &mut Mesh) {
match command {
PaintCmd::Circle {
center,
@ -512,7 +503,7 @@ pub fn mesh_command(
}
pub fn mesh_paint_commands(
options: &MesherOptions,
options: MesherOptions,
fonts: &Fonts,
commands: Vec<(Rect, PaintCmd)>,
) -> Vec<(Rect, Mesh)> {

View file

@ -22,7 +22,7 @@ fn main() {
let pixels_per_point = display.gl_window().get_hidpi_factor() as f32;
let mut emigui = Emigui::new(pixels_per_point);
let mut ctx = Context::new(pixels_per_point);
let mut painter = emigui_glium::Painter::new(&display);
let mut raw_input = emigui::RawInput {
@ -43,7 +43,7 @@ fn main() {
let mut clipboard = emigui_glium::init_clipboard();
let memory_path = "emigui.json";
emigui_glium::read_memory(&emigui.ctx(), memory_path);
emigui_glium::read_memory(&ctx, memory_path);
while running {
{
@ -67,8 +67,8 @@ fn main() {
}
let emigui_start = Instant::now();
emigui.begin_frame(raw_input.clone()); // TODO: avoid clone
let mut region = emigui.background_region();
ctx.begin_frame(raw_input.clone()); // TODO: avoid clone
let mut region = ctx.background_region();
let mut region = region.centered_column(region.available_width().min(480.0));
region.set_align(Align::Min);
region.add(label!("Emigui running inside of Glium").text_style(emigui::TextStyle::Heading));
@ -106,21 +106,21 @@ fn main() {
.default_pos(pos2(450.0, 100.0))
.default_size(vec2(450.0, 500.0))
.show(region.ctx(), |region| {
emigui.ui(region);
ctx.ui(region);
});
let (output, paint_batches) = emigui.end_frame();
let (output, paint_batches) = ctx.end_frame();
frame_times.add(
raw_input.time,
(Instant::now() - emigui_start).as_secs_f64() as f32,
);
painter.paint_batches(&display, paint_batches, emigui.texture());
painter.paint_batches(&display, paint_batches, ctx.texture());
emigui_glium::handle_output(output, &display, clipboard.as_mut());
}
if let Err(err) = emigui_glium::write_memory(&emigui.ctx(), memory_path) {
if let Err(err) = emigui_glium::write_memory(&ctx, memory_path) {
eprintln!("ERROR: Failed to save emigui state: {}", err);
}
}

View file

@ -6,20 +6,22 @@ extern crate wasm_bindgen;
extern crate emigui;
extern crate emigui_wasm;
use std::sync::Arc;
use {
emigui::{
color::srgba, containers::*, example_app::ExampleWindow, label, widgets::Separator, Align,
Emigui, RawInput, TextStyle, *,
RawInput, TextStyle, *,
},
emigui_wasm::now_sec,
};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
#[wasm_bindgen]
pub struct State {
example_app: ExampleWindow,
emigui: Emigui,
ctx: Arc<Context>,
webgl_painter: emigui_wasm::webgl::Painter,
frame_times: emigui::MovementTracker<f32>,
@ -27,11 +29,11 @@ pub struct State {
impl State {
fn new(canvas_id: &str, pixels_per_point: f32) -> Result<State, JsValue> {
let emigui = Emigui::new(pixels_per_point);
emigui_wasm::load_memory(emigui.ctx());
let ctx = Context::new(pixels_per_point);
emigui_wasm::load_memory(&ctx);
Ok(State {
example_app: Default::default(),
emigui,
ctx,
webgl_painter: emigui_wasm::webgl::Painter::new(canvas_id)?,
frame_times: emigui::MovementTracker::new(1000, 1.0),
})
@ -41,9 +43,9 @@ impl State {
let everything_start = now_sec();
let pixels_per_point = raw_input.pixels_per_point;
self.emigui.begin_frame(raw_input);
self.ctx.begin_frame(raw_input);
let mut region = self.emigui.background_region();
let mut region = self.ctx.background_region();
let mut region = region.centered_column(region.available_width().min(480.0));
region.set_align(Align::Min);
region.add(label!("Emigui!").text_style(TextStyle::Heading));
@ -93,11 +95,11 @@ impl State {
.default_pos(pos2(400.0, 300.0))
.default_size(vec2(400.0, 400.0))
.show(region.ctx(), |region| {
self.emigui.ui(region);
self.ctx.ui(region);
});
let bg_color = srgba(0, 0, 0, 0); // Use background css color.
let (output, batches) = self.emigui.end_frame();
let (output, batches) = self.ctx.end_frame();
let now = now_sec();
self.frame_times.add(now, (now - everything_start) as f32);
@ -105,11 +107,11 @@ impl State {
self.webgl_painter.paint_batches(
bg_color,
batches,
self.emigui.texture(),
self.ctx.texture(),
pixels_per_point,
)?;
emigui_wasm::save_memory(self.emigui.ctx()); // TODO: don't save every frame
emigui_wasm::save_memory(&self.ctx); // TODO: don't save every frame
Ok(output)
}