Kinetic windows

This commit is contained in:
Emil Ernerfeldt 2020-05-03 13:28:47 +02:00
parent bfbb669d02
commit 45564f952b
12 changed files with 398 additions and 229 deletions

View file

@ -12,7 +12,7 @@ This is the core library crate Emigui. It is fully platform independent without
* [x] Collapsing header region * [x] Collapsing header region
* [x] Tooltip * [x] Tooltip
* [x] Movable/resizable windows * [x] Movable/resizable windows
* [ ] Kinetic windows * [x] Kinetic windows
* [ ] BUG FIX: Don't catch clicks on closed windows * [ ] BUG FIX: Don't catch clicks on closed windows
* [ ] Scroll areas * [ ] Scroll areas
* [x] Vertical scrolling * [x] Vertical scrolling

View file

@ -14,6 +14,10 @@ pub(crate) struct State {
/// Last know size. Used for catching clicks. /// Last know size. Used for catching clicks.
pub size: Vec2, pub size: Vec2,
/// You can throw a Floating thing. It's fun.
#[serde(skip)]
pub vel: Vec2,
} }
// TODO: rename Floating to something else. Area? // TODO: rename Floating to something else. Area?
@ -56,6 +60,7 @@ impl Floating {
let state = State { let state = State {
pos: default_pos, pos: default_pos,
size: Vec2::zero(), size: Vec2::zero(),
vel: Vec2::zero(),
}; };
(state, true) (state, true)
} }
@ -75,8 +80,21 @@ impl Floating {
let clip_rect = Rect::everything(); let clip_rect = Rect::everything();
let move_interact = ctx.interact(layer, &clip_rect, &rect, Some(id.with("move"))); let move_interact = ctx.interact(layer, &clip_rect, &rect, Some(id.with("move")));
let input = ctx.input();
if move_interact.active { if move_interact.active {
state.pos += ctx.input().mouse_move; state.pos += input.mouse_move;
state.vel = input.mouse_velocity;
} else {
let stop_speed = 20.0; // Pixels per second.
let friction_coeff = 1000.0; // Pixels per second squared.
let friction = friction_coeff * input.dt;
if friction > state.vel.length() || state.vel.length() < stop_speed {
state.vel = Vec2::zero();
} else {
state.vel -= friction * state.vel.normalized();
state.pos += state.vel * input.dt;
}
} }
// Constrain to screen: // Constrain to screen:

View file

@ -10,8 +10,9 @@ pub struct Context {
pub(crate) style: Mutex<Style>, pub(crate) style: Mutex<Style>,
pub(crate) fonts: Arc<Fonts>, pub(crate) fonts: Arc<Fonts>,
/// Raw input from last frame. Use `input()` instead. /// Raw input from last frame. Use `input()` instead.
pub(crate) last_raw_input: RawInput, last_raw_input: RawInput,
pub(crate) input: GuiInput, pub(crate) input: GuiInput,
mouse_tracker: MovementTracker<Pos2>,
memory: Mutex<Memory>, memory: Mutex<Memory>,
pub(crate) graphics: Mutex<GraphicLayers>, pub(crate) graphics: Mutex<GraphicLayers>,
@ -29,6 +30,7 @@ impl Clone for Context {
fonts: self.fonts.clone(), fonts: self.fonts.clone(),
last_raw_input: self.last_raw_input.clone(), last_raw_input: self.last_raw_input.clone(),
input: self.input.clone(), input: self.input.clone(),
mouse_tracker: self.mouse_tracker.clone(),
memory: Mutex::new(self.memory.lock().clone()), memory: Mutex::new(self.memory.lock().clone()),
graphics: Mutex::new(self.graphics.lock().clone()), graphics: Mutex::new(self.graphics.lock().clone()),
output: Mutex::new(self.output.lock().clone()), output: Mutex::new(self.output.lock().clone()),
@ -44,6 +46,7 @@ impl Context {
fonts: Arc::new(Fonts::new(pixels_per_point)), fonts: Arc::new(Fonts::new(pixels_per_point)),
last_raw_input: Default::default(), last_raw_input: Default::default(),
input: Default::default(), input: Default::default(),
mouse_tracker: MovementTracker::new(1000, 0.1),
memory: Default::default(), memory: Default::default(),
graphics: Default::default(), graphics: Default::default(),
output: Default::default(), output: Default::default(),
@ -63,6 +66,11 @@ impl Context {
&self.input &self.input
} }
/// Smoothed mouse velocity, in points per second
pub fn mouse_vel(&self) -> Vec2 {
self.mouse_tracker.velocity().unwrap_or_default()
}
/// Raw input from last frame. Use `input()` instead. /// Raw input from last frame. Use `input()` instead.
pub fn last_raw_input(&self) -> &RawInput { pub fn last_raw_input(&self) -> &RawInput {
&self.last_raw_input &self.last_raw_input
@ -76,6 +84,10 @@ impl Context {
*self.style.lock() = style; *self.style.lock() = style;
} }
pub fn pixels_per_point(&self) -> f32 {
self.input.pixels_per_point
}
/// Useful for pixel-perfect rendering /// Useful for pixel-perfect rendering
pub fn round_to_pixel(&self, point: f32) -> f32 { pub fn round_to_pixel(&self, point: f32) -> f32 {
(point * self.input.pixels_per_point).round() / self.input.pixels_per_point (point * self.input.pixels_per_point).round() / self.input.pixels_per_point
@ -89,10 +101,21 @@ impl Context {
vec2(self.round_to_pixel(vec.x), self.round_to_pixel(vec.y)) vec2(self.round_to_pixel(vec.x), self.round_to_pixel(vec.y))
} }
// TODO: move pub fn begin_frame(&mut self, new_input: RawInput) {
pub fn begin_frame(&mut self, gui_input: GuiInput) { 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(); self.used_ids.lock().clear();
self.input = gui_input;
if let Some(mouse_pos) = new_input.mouse_pos {
self.mouse_tracker.add(new_input.time, mouse_pos);
} else {
self.mouse_tracker.clear();
}
self.input = GuiInput::from_last_and_new(&self.last_raw_input, &new_input);
self.input.mouse_velocity = self.mouse_vel();
self.last_raw_input = new_input;
} }
pub fn end_frame(&self) -> Output { pub fn end_frame(&self) -> Output {

View file

@ -12,7 +12,6 @@ struct Stats {
/// Encapsulates input, layout and painting for ease of use. /// Encapsulates input, layout and painting for ease of use.
/// TODO: merge into Context /// TODO: merge into Context
pub struct Emigui { pub struct Emigui {
last_input: RawInput,
ctx: Arc<Context>, ctx: Arc<Context>,
stats: Stats, stats: Stats,
mesher_options: MesherOptions, mesher_options: MesherOptions,
@ -21,7 +20,6 @@ pub struct Emigui {
impl Emigui { impl Emigui {
pub fn new(pixels_per_point: f32) -> Emigui { pub fn new(pixels_per_point: f32) -> Emigui {
Emigui { Emigui {
last_input: Default::default(),
ctx: Arc::new(Context::new(pixels_per_point)), ctx: Arc::new(Context::new(pixels_per_point)),
stats: Default::default(), stats: Default::default(),
mesher_options: MesherOptions::default(), mesher_options: MesherOptions::default(),
@ -37,18 +35,10 @@ impl Emigui {
} }
pub fn begin_frame(&mut self, new_input: RawInput) { pub fn begin_frame(&mut self, new_input: RawInput) {
if !self.last_input.mouse_down || self.last_input.mouse_pos.is_none() {
self.ctx.memory().active_id = None;
}
let gui_input = GuiInput::from_last_and_new(&self.last_input, &new_input);
self.last_input = new_input.clone(); // TODO: also stored in Context. Remove this one
// TODO: avoid this clone // TODO: avoid this clone
let mut new_ctx = (*self.ctx).clone(); let mut new_ctx = (*self.ctx).clone();
new_ctx.last_raw_input = new_input; new_ctx.begin_frame(new_input);
new_ctx.begin_frame(gui_input);
self.ctx = Arc::new(new_ctx); self.ctx = Arc::new(new_ctx);
} }
@ -59,7 +49,7 @@ impl Emigui {
} }
fn paint(&mut self) -> PaintBatches { fn paint(&mut self) -> PaintBatches {
self.mesher_options.aa_size = 1.0 / self.last_input.pixels_per_point; self.mesher_options.aa_size = 1.0 / self.ctx().pixels_per_point();
let paint_commands = self.ctx.drain_paint_lists(); let paint_commands = self.ctx.drain_paint_lists();
let batches = mesh_paint_commands(&self.mesher_options, &self.ctx.fonts, paint_commands); let batches = mesh_paint_commands(&self.mesher_options, &self.ctx.fonts, paint_commands);
self.stats = Default::default(); self.stats = Default::default();
@ -149,36 +139,3 @@ fn font_definitions_ui(font_definitions: &mut FontDefinitions, region: &mut Regi
*font_definitions = crate::fonts::default_font_definitions(); *font_definitions = crate::fonts::default_font_definitions();
} }
} }
impl RawInput {
pub fn ui(&self, region: &mut Region) {
// TODO: simpler way to show values, e.g. `region.value("Mouse Pos:", self.mouse_pos);
// TODO: easily change default font!
region.add(label!("mouse_down: {}", self.mouse_down));
region.add(label!("mouse_pos: {:.1?}", self.mouse_pos));
region.add(label!("scroll_delta: {:?}", self.scroll_delta));
region.add(label!("screen_size: {:?}", self.screen_size));
region.add(label!("pixels_per_point: {}", self.pixels_per_point));
region.add(label!("time: {:.3} s", self.time));
region.add(label!("events: {:?}", self.events));
region.add(label!("dropped_files: {:?}", self.dropped_files));
region.add(label!("hovered_files: {:?}", self.hovered_files));
}
}
impl GuiInput {
pub fn ui(&self, region: &mut Region) {
region.add(label!("mouse_down: {}", self.mouse_down));
region.add(label!("mouse_pressed: {}", self.mouse_pressed));
region.add(label!("mouse_released: {}", self.mouse_released));
region.add(label!("mouse_pos: {:?}", self.mouse_pos));
region.add(label!("mouse_move: {:?}", self.mouse_move));
region.add(label!("scroll_delta: {:?}", self.scroll_delta));
region.add(label!("screen_size: {:?}", self.screen_size));
region.add(label!("pixels_per_point: {}", self.pixels_per_point));
region.add(label!("time: {}", self.time));
region.add(label!("events: {:?}", self.events));
region.add(label!("dropped_files: {:?}", self.dropped_files));
region.add(label!("hovered_files: {:?}", self.hovered_files));
}
}

193
emigui/src/input.rs Normal file
View file

@ -0,0 +1,193 @@
use crate::math::*;
/// What the integration gives to the gui.
/// All coordinates in emigui is in point/logical coordinates.
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(default)]
pub struct RawInput {
/// Is the button currently down?
pub mouse_down: bool,
/// Current position of the mouse in points.
pub mouse_pos: Option<Pos2>,
/// How many pixels the user scrolled
pub scroll_delta: Vec2,
/// Size of the screen in points.
pub screen_size: Vec2,
/// Also known as device pixel ratio, > 1 for HDPI screens.
pub pixels_per_point: f32,
/// Time in seconds. Relative to whatever. Used for animation.
pub time: f64,
/// Files has been dropped into the window.
pub dropped_files: Vec<std::path::PathBuf>,
/// Someone is threatening to drop these on us.
pub hovered_files: Vec<std::path::PathBuf>,
/// In-order events received this frame
pub events: Vec<Event>,
}
/// What emigui maintains
#[derive(Clone, Debug, Default)]
pub struct GuiInput {
// TODO: mouse: Mouse as separate
//
/// Is the button currently down?
/// true the frame when it is pressed,
/// false the frame it is released.
pub mouse_down: bool,
/// The mouse went from !down to down
pub mouse_pressed: bool,
/// The mouse went from down to !down
pub mouse_released: bool,
/// Current position of the mouse in points.
/// None for touch screens when finger is not down.
pub mouse_pos: Option<Pos2>,
/// How much the mouse moved compared to last frame, in points.
pub mouse_move: Vec2,
/// Current velocity of mouse cursor, if any.
pub mouse_velocity: Vec2,
/// How many pixels the user scrolled
pub scroll_delta: Vec2,
/// Size of the screen in points.
pub screen_size: Vec2,
/// Also known as device pixel ratio, > 1 for HDPI screens.
pub pixels_per_point: f32,
/// Time in seconds. Relative to whatever. Used for animation.
pub time: f64,
/// Time since last frame, in seconds.
pub dt: f32,
/// Files has been dropped into the window.
pub dropped_files: Vec<std::path::PathBuf>,
/// Someone is threatening to drop these on us.
pub hovered_files: Vec<std::path::PathBuf>,
/// In-order events received this frame
pub events: Vec<Event>,
}
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Event {
Copy,
Cut,
/// Text input, e.g. via keyboard or paste action
Text(String),
Key {
key: Key,
pressed: bool,
},
}
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Key {
Alt,
Backspace,
Control,
Delete,
Down,
End,
Escape,
Home,
Insert,
Left,
/// Windows key or Mac Command key
Logo,
PageDown,
PageUp,
Return,
Right,
Shift,
// Space,
Tab,
Up,
}
impl GuiInput {
pub fn from_last_and_new(last: &RawInput, new: &RawInput) -> GuiInput {
let mouse_move = new
.mouse_pos
.and_then(|new| last.mouse_pos.map(|last| new - last))
.unwrap_or_default();
let dt = (new.time - last.time) as f32;
let mut mouse_velocity = mouse_move / dt;
if !mouse_velocity.is_finite() {
mouse_velocity = Vec2::zero();
}
GuiInput {
mouse_down: new.mouse_down && new.mouse_pos.is_some(),
mouse_pressed: !last.mouse_down && new.mouse_down,
mouse_released: last.mouse_down && !new.mouse_down,
mouse_pos: new.mouse_pos,
mouse_move,
mouse_velocity,
scroll_delta: new.scroll_delta,
screen_size: new.screen_size,
pixels_per_point: new.pixels_per_point,
time: new.time,
dt,
dropped_files: new.dropped_files.clone(),
hovered_files: new.hovered_files.clone(),
events: new.events.clone(),
}
}
}
impl RawInput {
pub fn ui(&self, region: &mut crate::Region) {
use crate::label;
// TODO: simpler way to show values, e.g. `region.value("Mouse Pos:", self.mouse_pos);
// TODO: easily change default font!
region.add(label!("mouse_down: {}", self.mouse_down));
region.add(label!("mouse_pos: {:.1?}", self.mouse_pos));
region.add(label!("scroll_delta: {:?}", self.scroll_delta));
region.add(label!("screen_size: {:?}", self.screen_size));
region.add(label!("pixels_per_point: {}", self.pixels_per_point));
region.add(label!("time: {:.3} s", self.time));
region.add(label!("events: {:?}", self.events));
region.add(label!("dropped_files: {:?}", self.dropped_files));
region.add(label!("hovered_files: {:?}", self.hovered_files));
}
}
impl GuiInput {
pub fn ui(&self, region: &mut crate::Region) {
use crate::label;
region.add(label!("mouse_down: {}", self.mouse_down));
region.add(label!("mouse_pressed: {}", self.mouse_pressed));
region.add(label!("mouse_released: {}", self.mouse_released));
region.add(label!("mouse_pos: {:?}", self.mouse_pos));
region.add(label!("mouse_move: {:?}", self.mouse_move));
region.add(label!(
"mouse_velocity: [{:3.0} {:3.0}] points/sec",
self.mouse_velocity.x,
self.mouse_velocity.y
));
region.add(label!("scroll_delta: {:?}", self.scroll_delta));
region.add(label!("screen_size: {:?}", self.screen_size));
region.add(label!("pixels_per_point: {}", self.pixels_per_point));
region.add(label!("time: {}", self.time));
region.add(label!("events: {:?}", self.events));
region.add(label!("dropped_files: {:?}", self.dropped_files));
region.add(label!("hovered_files: {:?}", self.hovered_files));
}
}

View file

@ -14,11 +14,13 @@ pub mod example_app;
mod font; mod font;
mod fonts; mod fonts;
mod id; mod id;
mod input;
mod layers; mod layers;
mod layout; mod layout;
pub mod math; pub mod math;
mod memory; mod memory;
pub mod mesher; pub mod mesher;
mod movement_tracker;
mod region; mod region;
mod style; mod style;
mod texture_atlas; mod texture_atlas;
@ -31,11 +33,13 @@ pub use {
context::Context, context::Context,
fonts::{FontDefinitions, Fonts, TextStyle}, fonts::{FontDefinitions, Fonts, TextStyle},
id::Id, id::Id,
input::*,
layers::*, layers::*,
layout::{Align, GuiResponse}, layout::{Align, GuiResponse},
math::*, math::*,
memory::Memory, memory::Memory,
mesher::{Mesh, PaintBatches, Vertex}, mesher::{Mesh, PaintBatches, Vertex},
movement_tracker::MovementTracker,
region::Region, region::Region,
style::Style, style::Style,
texture_atlas::Texture, texture_atlas::Texture,

View file

@ -0,0 +1,120 @@
use std::collections::VecDeque;
/// This struct tracks recent values of some time series.
/// This can be used for things like smoothed averages (for e.g. FPS)
/// or for smoothed velocity (e.g. mouse pointer speed).
/// All times are in seconds.
#[derive(Clone, Debug)]
pub struct MovementTracker<T> {
max_len: usize,
max_age: f64,
/// (time, value) pais
values: VecDeque<(f64, T)>,
}
impl<T> MovementTracker<T>
where
T: Copy,
{
pub fn new(max_len: usize, max_age: f64) -> Self {
Self {
max_len,
max_age,
values: Default::default(),
}
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
pub fn len(&self) -> usize {
self.values.len()
}
pub fn values<'a>(&'a self) -> impl Iterator<Item = T> + 'a {
self.values.iter().map(|(_time, value)| *value)
}
pub fn clear(&mut self) {
self.values.clear()
}
/// Values must be added with a monotonically increasing time, or at least not decreasing.
pub fn add(&mut self, now: f64, value: T) {
if let Some((last_time, _)) = self.values.back() {
debug_assert!(now >= *last_time, "Time shouldn't go backwards");
}
self.values.push_back((now, value));
self.flush(now);
}
/// Mean time difference between values in this MovementTracker.
pub fn mean_time_interval(&self) -> Option<f32> {
if let (Some(first), Some(last)) = (self.values.front(), self.values.back()) {
let n = self.len();
if n >= 2 {
Some((last.0 - first.0) as f32 / ((n - 1) as f32))
} else {
None
}
} else {
None
}
}
fn flush(&mut self, now: f64) {
while self.values.len() > self.max_len {
self.values.pop_front();
}
while let Some((front_time, _)) = self.values.front() {
if *front_time < now - self.max_age {
self.values.pop_front();
} else {
break;
}
}
}
}
impl<T> MovementTracker<T>
where
T: Copy,
T: std::iter::Sum,
T: std::ops::Div<f32, Output = T>,
{
pub fn sum(&self) -> T {
self.values().sum()
}
pub fn average(&self) -> Option<T> {
let num = self.len();
if num > 0 {
Some(self.sum() / (num as f32))
} else {
None
}
}
}
impl<T, Vel> MovementTracker<T>
where
T: Copy,
T: std::ops::Sub<Output = Vel>,
Vel: std::ops::Div<f32, Output = Vel>,
{
/// Calculate a smooth velocity (per second) over the entire time span
pub fn velocity(&self) -> Option<Vel> {
if let (Some(first), Some(last)) = (self.values.front(), self.values.back()) {
let dt = (last.0 - first.0) as f32;
if dt > 0.0 {
Some((last.1 - first.1) / dt)
} else {
None
}
} else {
None
}
}
}

View file

@ -92,10 +92,7 @@ impl TextureAtlas {
impl Texture { impl Texture {
pub fn ui(&self, region: &mut crate::Region) { pub fn ui(&self, region: &mut crate::Region) {
use crate::{ use crate::{color::WHITE, label, layout::show_popup, math::*, Mesh, PaintCmd, Vertex};
color::WHITE, label, layout::show_popup, math::*, widgets::Label, Mesh, PaintCmd,
Vertex,
};
region.add(label!( region.add(label!(
"Texture size: {} x {} (hover to zoom)", "Texture size: {} x {} (hover to zoom)",

View file

@ -1,151 +1,12 @@
use crate::{ use crate::{
color::Color, color::Color,
fonts::TextStyle, fonts::TextStyle,
math::{Pos2, Rect, Vec2}, math::{Pos2, Rect},
mesher::{Mesh, Path}, mesher::{Mesh, Path},
}; };
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
/// What the integration gives to the gui.
/// All coordinates in emigui is in point/logical coordinates.
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(default)]
pub struct RawInput {
/// Is the button currently down?
pub mouse_down: bool,
/// Current position of the mouse in points.
pub mouse_pos: Option<Pos2>,
/// How many pixels the user scrolled
pub scroll_delta: Vec2,
/// Size of the screen in points.
pub screen_size: Vec2,
/// Also known as device pixel ratio, > 1 for HDPI screens.
pub pixels_per_point: f32,
/// Time in seconds. Relative to whatever. Used for animation.
pub time: f64,
/// Files has been dropped into the window.
pub dropped_files: Vec<std::path::PathBuf>,
/// Someone is threatening to drop these on us.
pub hovered_files: Vec<std::path::PathBuf>,
/// In-order events received this frame
pub events: Vec<Event>,
}
/// What emigui maintains
#[derive(Clone, Debug, Default)]
pub struct GuiInput {
// TODO: mouse: Mouse as separate
//
/// Is the button currently down?
/// true the frame when it is pressed,
/// false the frame it is released.
pub mouse_down: bool,
/// The mouse went from !down to down
pub mouse_pressed: bool,
/// The mouse went from down to !down
pub mouse_released: bool,
/// Current position of the mouse in points.
/// None for touch screens when finger is not down.
pub mouse_pos: Option<Pos2>,
/// How much the mouse moved compared to last frame, in points.
pub mouse_move: Vec2,
/// How many pixels the user scrolled
pub scroll_delta: Vec2,
/// Size of the screen in points.
pub screen_size: Vec2,
/// Also known as device pixel ratio, > 1 for HDPI screens.
pub pixels_per_point: f32,
/// Time in seconds. Relative to whatever. Used for animation.
pub time: f64,
/// Files has been dropped into the window.
pub dropped_files: Vec<std::path::PathBuf>,
/// Someone is threatening to drop these on us.
pub hovered_files: Vec<std::path::PathBuf>,
/// In-order events received this frame
pub events: Vec<Event>,
}
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Event {
Copy,
Cut,
/// Text input, e.g. via keyboard or paste action
Text(String),
Key {
key: Key,
pressed: bool,
},
}
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Key {
Alt,
Backspace,
Control,
Delete,
Down,
End,
Escape,
Home,
Insert,
Left,
/// Windows key or Mac Command key
Logo,
PageDown,
PageUp,
Return,
Right,
Shift,
// Space,
Tab,
Up,
}
impl GuiInput {
pub fn from_last_and_new(last: &RawInput, new: &RawInput) -> GuiInput {
let mouse_move = new
.mouse_pos
.and_then(|new| last.mouse_pos.map(|last| new - last))
.unwrap_or_default();
GuiInput {
mouse_down: new.mouse_down && new.mouse_pos.is_some(),
mouse_pressed: !last.mouse_down && new.mouse_down,
mouse_released: last.mouse_down && !new.mouse_down,
mouse_pos: new.mouse_pos,
mouse_move,
scroll_delta: new.scroll_delta,
screen_size: new.screen_size,
pixels_per_point: new.pixels_per_point,
time: new.time,
dropped_files: new.dropped_files.clone(),
hovered_files: new.hovered_files.clone(),
events: new.events.clone(),
}
}
}
#[derive(Clone, Default, Serialize)] #[derive(Clone, Default, Serialize)]
pub struct Output { pub struct Output {
pub cursor_icon: CursorIcon, pub cursor_icon: CursorIcon,

View file

@ -55,8 +55,8 @@ impl Label {
/// Usage: label!("Foo: {}", bar) /// Usage: label!("Foo: {}", bar)
#[macro_export] #[macro_export]
macro_rules! label { macro_rules! label {
($fmt:expr) => (Label::new($fmt)); ($fmt:expr) => ($crate::widgets::Label::new($fmt));
($fmt:expr, $($arg:tt)*) => (Label::new(format!($fmt, $($arg)*))); ($fmt:expr, $($arg:tt)*) => ($crate::widgets::Label::new(format!($fmt, $($arg)*)));
} }
impl Widget for Label { impl Widget for Label {

View file

@ -1,9 +1,6 @@
#![deny(warnings)] #![deny(warnings)]
#[allow(clippy::single_match)] #[allow(clippy::single_match)]
use std::{ use std::time::{Duration, Instant};
collections::VecDeque,
time::{Duration, Instant},
};
use { use {
emigui::{containers::*, example_app::ExampleWindow, widgets::*, *}, emigui::{containers::*, example_app::ExampleWindow, widgets::*, *},
@ -42,7 +39,7 @@ fn main() {
let start_time = Instant::now(); let start_time = Instant::now();
let mut running = true; let mut running = true;
let mut frame_start = Instant::now(); let mut frame_start = Instant::now();
let mut frame_times = VecDeque::new(); let mut frame_times = emigui::MovementTracker::new(1000, 1.0);
let mut example_app = ExampleWindow::default(); let mut example_app = ExampleWindow::default();
let mut clipboard = emigui_glium::init_clipboard(); let mut clipboard = emigui_glium::init_clipboard();
@ -82,8 +79,15 @@ fn main() {
region.add( region.add(
label!( label!(
"Frame time: {:.1} ms (excludes painting)", "CPU usage: {:.2} ms (excludes painting)",
1e3 * mean_frame_time(&frame_times) 1e3 * frame_times.average().unwrap_or_default()
)
.text_style(TextStyle::Monospace),
);
region.add(
label!(
"FPS: {:.1}",
1.0 / frame_times.mean_time_interval().unwrap_or_default()
) )
.text_style(TextStyle::Monospace), .text_style(TextStyle::Monospace),
); );
@ -108,10 +112,10 @@ fn main() {
let (output, paint_batches) = emigui.end_frame(); let (output, paint_batches) = emigui.end_frame();
frame_times.push_back((Instant::now() - emigui_start).as_secs_f64()); frame_times.add(
while frame_times.len() > 30 { raw_input.time,
frame_times.pop_front(); (Instant::now() - emigui_start).as_secs_f64() as f32,
} );
painter.paint_batches(&display, paint_batches, emigui.texture()); painter.paint_batches(&display, paint_batches, emigui.texture());
emigui_glium::handle_output(output, &display, clipboard.as_mut()); emigui_glium::handle_output(output, &display, clipboard.as_mut());
@ -121,11 +125,3 @@ fn main() {
eprintln!("ERROR: Failed to save emigui state: {}", err); eprintln!("ERROR: Failed to save emigui state: {}", err);
} }
} }
pub fn mean_frame_time(frame_times: &VecDeque<f64>) -> f64 {
if frame_times.is_empty() {
0.0
} else {
frame_times.iter().sum::<f64>() / (frame_times.len() as f64)
}
}

View file

@ -26,7 +26,7 @@ pub struct State {
emigui: Emigui, emigui: Emigui,
webgl_painter: emigui_wasm::webgl::Painter, webgl_painter: emigui_wasm::webgl::Painter,
frame_times: std::collections::VecDeque<f64>, frame_times: emigui::MovementTracker<f32>,
} }
impl State { impl State {
@ -37,7 +37,7 @@ impl State {
example_app: Default::default(), example_app: Default::default(),
emigui, emigui,
webgl_painter: emigui_wasm::webgl::Painter::new(canvas_id)?, webgl_painter: emigui_wasm::webgl::Painter::new(canvas_id)?,
frame_times: Default::default(), frame_times: emigui::MovementTracker::new(1000, 1.0),
}) })
} }
@ -69,15 +69,17 @@ impl State {
region.add_label(self.webgl_painter.debug_info()); region.add_label(self.webgl_painter.debug_info());
}); });
let mean_frame_time = if self.frame_times.is_empty() {
0.0
} else {
self.frame_times.iter().sum::<f64>() / (self.frame_times.len() as f64)
};
region.add( region.add(
label!( label!(
"Frame time: {:.1} ms (excludes painting)", "CPU usage: {:.2} ms (excludes painting)",
1e3 * mean_frame_time 1e3 * self.frame_times.average().unwrap_or_default()
)
.text_style(TextStyle::Monospace),
);
region.add(
label!(
"FPS: {:.1}",
1.0 / self.frame_times.mean_time_interval().unwrap_or_default()
) )
.text_style(TextStyle::Monospace), .text_style(TextStyle::Monospace),
); );
@ -101,10 +103,8 @@ impl State {
let bg_color = srgba(0, 0, 0, 0); // Use background css color. let bg_color = srgba(0, 0, 0, 0); // Use background css color.
let (output, batches) = self.emigui.end_frame(); let (output, batches) = self.emigui.end_frame();
self.frame_times.push_back(now_sec() - everything_start); let now = now_sec();
while self.frame_times.len() > 30 { self.frame_times.add(now, (now - everything_start) as f32);
self.frame_times.pop_front();
}
self.webgl_painter.paint_batches( self.webgl_painter.paint_batches(
bg_color, bg_color,