[demo] add frame time graph to demo app
This commit is contained in:
parent
1156525ce9
commit
6fcfb52aa0
18 changed files with 371 additions and 130 deletions
51
egui/src/align.rs
Normal file
51
egui/src/align.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
use crate::math::{pos2, Rect};
|
||||||
|
|
||||||
|
/// left/center/right or top/center/bottom alignment for e.g. anchors and `Layout`s.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
|
||||||
|
pub enum Align {
|
||||||
|
/// Left/Top
|
||||||
|
Min,
|
||||||
|
|
||||||
|
/// Note: requires a bounded/known available_width.
|
||||||
|
Center,
|
||||||
|
|
||||||
|
/// Right/Bottom
|
||||||
|
/// Note: requires a bounded/known available_width.
|
||||||
|
Max,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Align {
|
||||||
|
fn default() -> Align {
|
||||||
|
Align::Min
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Align2 = (Align, Align);
|
||||||
|
|
||||||
|
pub const LEFT_BOTTOM: Align2 = (Align::Min, Align::Max);
|
||||||
|
pub const LEFT_CENTER: Align2 = (Align::Min, Align::Center);
|
||||||
|
pub const LEFT_TOP: Align2 = (Align::Min, Align::Min);
|
||||||
|
pub const CENTER_BOTTOM: Align2 = (Align::Center, Align::Max);
|
||||||
|
pub const CENTER_CENTER: Align2 = (Align::Center, Align::Center);
|
||||||
|
pub const CENTER_TOP: Align2 = (Align::Center, Align::Min);
|
||||||
|
pub const RIGHT_BOTTOM: Align2 = (Align::Max, Align::Max);
|
||||||
|
pub const RIGHT_CENTER: Align2 = (Align::Max, Align::Center);
|
||||||
|
pub const RIGHT_TOP: Align2 = (Align::Max, Align::Min);
|
||||||
|
|
||||||
|
/// Used e.g. to anchor a piece of text to a part of the rectangle.
|
||||||
|
/// Give a position within the rect, specified by the aligns
|
||||||
|
pub(crate) fn anchor_rect(rect: Rect, anchor: (Align, Align)) -> Rect {
|
||||||
|
let x = match anchor.0 {
|
||||||
|
Align::Min => rect.left(),
|
||||||
|
Align::Center => rect.left() - 0.5 * rect.width(),
|
||||||
|
Align::Max => rect.left() - rect.width(),
|
||||||
|
};
|
||||||
|
let y = match anchor.1 {
|
||||||
|
Align::Min => rect.top(),
|
||||||
|
Align::Center => rect.top() - 0.5 * rect.height(),
|
||||||
|
Align::Max => rect.top() - rect.height(),
|
||||||
|
};
|
||||||
|
Rect::from_min_size(pos2(x, y), rect.size())
|
||||||
|
}
|
|
@ -30,11 +30,9 @@ pub trait Backend {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// excludes painting
|
/// Seconds of cpu usage (in seconds) of UI code on the previous frame.
|
||||||
fn cpu_time(&self) -> f32;
|
/// Zero if this is the first frame.
|
||||||
|
fn cpu_usage(&self) -> Option<f32>;
|
||||||
/// Smoothed frames per second
|
|
||||||
fn fps(&self) -> f32;
|
|
||||||
|
|
||||||
/// Local time. Used for the clock in the demo app.
|
/// Local time. Used for the clock in the demo app.
|
||||||
fn seconds_since_midnight(&self) -> Option<f64> {
|
fn seconds_since_midnight(&self) -> Option<f64> {
|
||||||
|
|
|
@ -85,6 +85,16 @@ impl Resize {
|
||||||
self.min_size = min_size.into();
|
self.min_size = min_size.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
/// Won't shrink to smaller than this
|
||||||
|
pub fn min_width(mut self, min_width: f32) -> Self {
|
||||||
|
self.min_size.x = min_width;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Won't shrink to smaller than this
|
||||||
|
pub fn min_height(mut self, min_height: f32) -> Self {
|
||||||
|
self.min_size.y = min_height;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Won't expand to larger than this
|
/// Won't expand to larger than this
|
||||||
pub fn max_size(mut self, max_size: impl Into<Vec2>) -> Self {
|
pub fn max_size(mut self, max_size: impl Into<Vec2>) -> Self {
|
||||||
|
|
|
@ -77,6 +77,17 @@ impl<'open> Window<'open> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set minimum width of the window.
|
||||||
|
pub fn min_width(mut self, min_width: f32) -> Self {
|
||||||
|
self.resize = self.resize.min_width(min_width);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set minimum height of the window.
|
||||||
|
pub fn min_height(mut self, min_height: f32) -> Self {
|
||||||
|
self.resize = self.resize.min_height(min_height);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set initial position of the window.
|
/// Set initial position of the window.
|
||||||
pub fn default_pos(mut self, default_pos: impl Into<Pos2>) -> Self {
|
pub fn default_pos(mut self, default_pos: impl Into<Pos2>) -> Self {
|
||||||
self.area = self.area.default_pos(default_pos);
|
self.area = self.area.default_pos(default_pos);
|
||||||
|
|
|
@ -47,6 +47,120 @@ impl Default for RunMode {
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
struct FrameHistory {
|
||||||
|
frame_times: History<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FrameHistory {
|
||||||
|
fn default() -> Self {
|
||||||
|
let max_age: f64 = 1.0;
|
||||||
|
Self {
|
||||||
|
frame_times: History::from_max_len_age((max_age * 300.0).round() as usize, max_age),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameHistory {
|
||||||
|
pub fn on_new_frame(&mut self, now: f64, previus_frame_time: Option<f32>) {
|
||||||
|
let previus_frame_time = previus_frame_time.unwrap_or_default();
|
||||||
|
if let Some(latest) = self.frame_times.latest_mut() {
|
||||||
|
*latest = previus_frame_time; // rewrite history now that we know
|
||||||
|
}
|
||||||
|
self.frame_times.add(now, previus_frame_time); // projected
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fps(&self) -> f32 {
|
||||||
|
1.0 / self.frame_times.mean_time_interval().unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui(&mut self, ui: &mut Ui) {
|
||||||
|
ui.label(format!(
|
||||||
|
"Total frames painted: {}",
|
||||||
|
self.frame_times.total_count()
|
||||||
|
));
|
||||||
|
|
||||||
|
ui.label(format!(
|
||||||
|
"Mean CPU usage per frame: {:.2} ms / frame",
|
||||||
|
1e3 * self.frame_times.average().unwrap_or_default()
|
||||||
|
))
|
||||||
|
.tooltip_text(
|
||||||
|
"Includes Egui layout and tesselation time.\n\
|
||||||
|
Does not include GPU usage, nor overhead for sending data to GPU.",
|
||||||
|
);
|
||||||
|
|
||||||
|
CollapsingHeader::new("CPU usage history")
|
||||||
|
.default_open(false)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
self.graph(ui);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn graph(&mut self, ui: &mut Ui) {
|
||||||
|
let graph_top_cpu_usage = 0.010;
|
||||||
|
ui.label("Egui CPU usage history");
|
||||||
|
|
||||||
|
let history = &self.frame_times;
|
||||||
|
|
||||||
|
// TODO: we should not use `slider_width` as default graph width.
|
||||||
|
let height = ui.style().spacing.slider_width;
|
||||||
|
let rect = ui.allocate_space(vec2(ui.available_finite().width(), height));
|
||||||
|
let style = ui.style().noninteractive();
|
||||||
|
|
||||||
|
let mut cmds = vec![PaintCmd::Rect {
|
||||||
|
rect,
|
||||||
|
corner_radius: style.corner_radius,
|
||||||
|
fill: ui.style().visuals.dark_bg_color,
|
||||||
|
stroke: ui.style().noninteractive().bg_stroke,
|
||||||
|
}];
|
||||||
|
|
||||||
|
let rect = rect.shrink(4.0);
|
||||||
|
let line_stroke = Stroke::new(1.0, Srgba::additive_luminance(128));
|
||||||
|
|
||||||
|
if let Some(mouse_pos) = ui.input().mouse.pos {
|
||||||
|
if rect.contains(mouse_pos) {
|
||||||
|
let y = mouse_pos.y;
|
||||||
|
cmds.push(PaintCmd::line_segment(
|
||||||
|
[pos2(rect.left(), y), pos2(rect.right(), y)],
|
||||||
|
line_stroke,
|
||||||
|
));
|
||||||
|
let cpu_usage = remap(y, rect.bottom_up_range(), 0.0..=graph_top_cpu_usage);
|
||||||
|
let text = format!("{:.1} ms", 1e3 * cpu_usage);
|
||||||
|
cmds.push(PaintCmd::text(
|
||||||
|
ui.fonts(),
|
||||||
|
pos2(rect.left(), y),
|
||||||
|
align::LEFT_BOTTOM,
|
||||||
|
text,
|
||||||
|
TextStyle::Monospace,
|
||||||
|
color::WHITE,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let circle_color = Srgba::additive_luminance(196);
|
||||||
|
let radius = 2.0;
|
||||||
|
let right_side_time = ui.input().time; // Time at right side of screen
|
||||||
|
|
||||||
|
for (time, cpu_usage) in history.iter() {
|
||||||
|
let age = (right_side_time - time) as f32;
|
||||||
|
let x = remap(age, history.max_age()..=0.0, rect.range_x());
|
||||||
|
let y = remap_clamp(cpu_usage, 0.0..=graph_top_cpu_usage, rect.bottom_up_range());
|
||||||
|
|
||||||
|
cmds.push(PaintCmd::line_segment(
|
||||||
|
[pos2(x, rect.bottom()), pos2(x, y)],
|
||||||
|
line_stroke,
|
||||||
|
));
|
||||||
|
|
||||||
|
if cpu_usage < graph_top_cpu_usage {
|
||||||
|
cmds.push(PaintCmd::circle_filled(pos2(x, y), radius, circle_color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.painter().extend(cmds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// Special input to the demo-app.
|
/// Special input to the demo-app.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct DemoEnvironment {
|
pub struct DemoEnvironment {
|
||||||
|
@ -62,6 +176,7 @@ pub struct DemoEnvironment {
|
||||||
///
|
///
|
||||||
/// Implements `egui::app::App` so it can be used with
|
/// Implements `egui::app::App` so it can be used with
|
||||||
/// [`egui_glium`](https://crates.io/crates/egui_glium) and [`egui_web`](https://crates.io/crates/egui_web).
|
/// [`egui_glium`](https://crates.io/crates/egui_glium) and [`egui_web`](https://crates.io/crates/egui_web).
|
||||||
|
// TODO: split into `DemoWindows` and `app::DemoApp`
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
|
@ -72,7 +187,10 @@ pub struct DemoApp {
|
||||||
open_windows: OpenWindows,
|
open_windows: OpenWindows,
|
||||||
demo_window: DemoWindow,
|
demo_window: DemoWindow,
|
||||||
fractal_clock: FractalClock,
|
fractal_clock: FractalClock,
|
||||||
num_frames_painted: u64,
|
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip))]
|
||||||
|
frame_history: FrameHistory,
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", serde(skip))]
|
#[cfg_attr(feature = "serde", serde(skip))]
|
||||||
color_test: ColorTest,
|
color_test: ColorTest,
|
||||||
show_color_test: bool,
|
show_color_test: bool,
|
||||||
|
@ -195,15 +313,18 @@ impl DemoApp {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: give cpu_usage and web_info via `struct BackendInfo`
|
||||||
fn backend_ui(&mut self, ui: &mut Ui, backend: &mut dyn app::Backend) {
|
fn backend_ui(&mut self, ui: &mut Ui, backend: &mut dyn app::Backend) {
|
||||||
|
self.frame_history
|
||||||
|
.on_new_frame(ui.input().time, backend.cpu_usage());
|
||||||
|
|
||||||
let is_web = backend.web_info().is_some();
|
let is_web = backend.web_info().is_some();
|
||||||
|
|
||||||
if is_web {
|
if is_web {
|
||||||
ui.label("Egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL.");
|
ui.label("Egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL.");
|
||||||
ui.label(
|
ui.label(
|
||||||
"Everything you see is rendered as textured triangles. There is no DOM. There are no HTML elements."
|
"Everything you see is rendered as textured triangles. There is no DOM. There are no HTML elements. \
|
||||||
);
|
This is not JavaScript. This is Rust, running at 60 FPS. This is the web page, reinvented with game tech.");
|
||||||
ui.label("This is not JavaScript. This is Rust, running at 60 FPS. This is the web page, reinvented with game tech.");
|
|
||||||
ui.label("This is also work in progress, and not ready for production... yet :)");
|
ui.label("This is also work in progress, and not ready for production... yet :)");
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label("Project home page:");
|
ui.label("Project home page:");
|
||||||
|
@ -219,16 +340,28 @@ impl DemoApp {
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
ui.add(
|
self.run_mode_ui(ui);
|
||||||
label!(
|
|
||||||
"CPU usage: {:.2} ms / frame (excludes painting)",
|
if self.run_mode == RunMode::Continuous {
|
||||||
1e3 * backend.cpu_time()
|
ui.label(format!(
|
||||||
)
|
"Repainting the UI each frame. FPS: {:.1}",
|
||||||
.text_style(TextStyle::Monospace),
|
self.frame_history.fps()
|
||||||
);
|
));
|
||||||
|
} else {
|
||||||
|
ui.label("Only running UI code when there are animations or input");
|
||||||
|
}
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
self.frame_history.ui(ui);
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
ui.checkbox(
|
||||||
|
"Show color blend test (debug backend painter)",
|
||||||
|
&mut self.show_color_test,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_mode_ui(&mut self, ui: &mut Ui) {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
let run_mode = &mut self.run_mode;
|
let run_mode = &mut self.run_mode;
|
||||||
ui.label("Run mode:");
|
ui.label("Run mode:");
|
||||||
|
@ -237,32 +370,17 @@ impl DemoApp {
|
||||||
ui.radio_value("Reactive", run_mode, RunMode::Reactive)
|
ui.radio_value("Reactive", run_mode, RunMode::Reactive)
|
||||||
.tooltip_text("Repaint when there are animations or input (e.g. mouse movement)");
|
.tooltip_text("Repaint when there are animations or input (e.g. mouse movement)");
|
||||||
});
|
});
|
||||||
|
|
||||||
if self.run_mode == RunMode::Continuous {
|
|
||||||
ui.add(
|
|
||||||
label!("Repainting the UI each frame. FPS: {:.1}", backend.fps())
|
|
||||||
.text_style(TextStyle::Monospace),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ui.label("Only running UI code when there are animations or input");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.num_frames_painted += 1;
|
|
||||||
ui.label(format!("Total frames painted: {}", self.num_frames_painted));
|
|
||||||
|
|
||||||
ui.separator();
|
|
||||||
ui.checkbox(
|
|
||||||
"Show color blend test (debug backend painter)",
|
|
||||||
&mut self.show_color_test,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl app::App for DemoApp {
|
impl app::App for DemoApp {
|
||||||
fn ui(&mut self, ui: &mut Ui, backend: &mut dyn app::Backend) {
|
fn ui(&mut self, ui: &mut Ui, backend: &mut dyn app::Backend) {
|
||||||
Window::new("Backend").scroll(false).show(ui.ctx(), |ui| {
|
Window::new("Backend")
|
||||||
self.backend_ui(ui, backend);
|
.min_width(360.0)
|
||||||
});
|
.scroll(false)
|
||||||
|
.show(ui.ctx(), |ui| {
|
||||||
|
self.backend_ui(ui, backend);
|
||||||
|
});
|
||||||
|
|
||||||
let Self {
|
let Self {
|
||||||
show_color_test,
|
show_color_test,
|
||||||
|
|
|
@ -1,40 +1,84 @@
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
/// This struct tracks recent values of some time series.
|
/// This struct tracks recent values of some time series.
|
||||||
|
///
|
||||||
|
/// One use is to show a log of recent events,
|
||||||
|
/// or show a graph over recent events.
|
||||||
|
///
|
||||||
|
/// It has both a maximum length and a maximum storage time.
|
||||||
|
/// Elements are dropped when either max length or max age is reached.
|
||||||
|
///
|
||||||
|
/// Time difference between values can be zero, but never negative.
|
||||||
|
///
|
||||||
/// This can be used for things like smoothed averages (for e.g. FPS)
|
/// This can be used for things like smoothed averages (for e.g. FPS)
|
||||||
/// or for smoothed velocity (e.g. mouse pointer speed).
|
/// or for smoothed velocity (e.g. mouse pointer speed).
|
||||||
/// All times are in seconds.
|
/// All times are in seconds.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct MovementTracker<T> {
|
pub struct History<T> {
|
||||||
|
/// In elements, i.e. of `values.len()`
|
||||||
max_len: usize,
|
max_len: usize,
|
||||||
max_age: f64,
|
|
||||||
|
|
||||||
/// (time, value) pais
|
/// In seconds
|
||||||
|
max_age: f64, // TODO: f32
|
||||||
|
|
||||||
|
/// Total number of elements seen ever
|
||||||
|
total_count: u64,
|
||||||
|
|
||||||
|
/// (time, value) pairs, oldest front, newest back.
|
||||||
|
/// Time difference between values can be zero, but never negative.
|
||||||
values: VecDeque<(f64, T)>,
|
values: VecDeque<(f64, T)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> MovementTracker<T>
|
impl<T> History<T>
|
||||||
where
|
where
|
||||||
T: Copy,
|
T: Copy,
|
||||||
{
|
{
|
||||||
pub fn new(max_len: usize, max_age: f64) -> Self {
|
pub fn new(max_len: usize, max_age: f64) -> Self {
|
||||||
|
Self::from_max_len_age(max_len, max_age)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_max_len_age(max_len: usize, max_age: f64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
max_len,
|
max_len,
|
||||||
max_age,
|
max_age,
|
||||||
|
total_count: 0,
|
||||||
values: Default::default(),
|
values: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn max_len(&self) -> usize {
|
||||||
|
self.max_len
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max_age(&self) -> f32 {
|
||||||
|
self.max_age as f32
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.values.is_empty()
|
self.values.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Current number of values kept in history
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.values.len()
|
self.values.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Amount of time contained from start to end in this `MovementTracker`
|
/// Total number of values seen.
|
||||||
pub fn dt(&self) -> f32 {
|
/// Includes those that have been discarded due to `max_len` or `max_age`.
|
||||||
|
pub fn total_count(&self) -> u64 {
|
||||||
|
self.total_count
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn latest(&self) -> Option<T> {
|
||||||
|
self.values.back().map(|(_, value)| *value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn latest_mut(&mut self) -> Option<&mut T> {
|
||||||
|
self.values.back_mut().map(|(_, value)| value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Amount of time contained from start to end in this `History`.
|
||||||
|
pub fn duration(&self) -> f32 {
|
||||||
if let (Some(front), Some(back)) = (self.values.front(), self.values.back()) {
|
if let (Some(front), Some(back)) = (self.values.front(), self.values.back()) {
|
||||||
(back.0 - front.0) as f32
|
(back.0 - front.0) as f32
|
||||||
} else {
|
} else {
|
||||||
|
@ -42,6 +86,13 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `(time, value)` pairs
|
||||||
|
/// Time difference between values can be zero, but never negative.
|
||||||
|
// TODO: impl IntoIter
|
||||||
|
pub fn iter<'a>(&'a self) -> impl Iterator<Item = (f64, T)> + 'a {
|
||||||
|
self.values.iter().map(|(time, value)| (*time, *value))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn values<'a>(&'a self) -> impl Iterator<Item = T> + 'a {
|
pub fn values<'a>(&'a self) -> impl Iterator<Item = T> + 'a {
|
||||||
self.values.iter().map(|(_time, value)| *value)
|
self.values.iter().map(|(_time, value)| *value)
|
||||||
}
|
}
|
||||||
|
@ -53,13 +104,14 @@ where
|
||||||
/// Values must be added with a monotonically increasing time, or at least not decreasing.
|
/// Values must be added with a monotonically increasing time, or at least not decreasing.
|
||||||
pub fn add(&mut self, now: f64, value: T) {
|
pub fn add(&mut self, now: f64, value: T) {
|
||||||
if let Some((last_time, _)) = self.values.back() {
|
if let Some((last_time, _)) = self.values.back() {
|
||||||
debug_assert!(now >= *last_time, "Time shouldn't go backwards");
|
debug_assert!(now >= *last_time, "Time shouldn't move backwards");
|
||||||
}
|
}
|
||||||
|
self.total_count += 1;
|
||||||
self.values.push_back((now, value));
|
self.values.push_back((now, value));
|
||||||
self.flush(now);
|
self.flush(now);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mean time difference between values in this `MovementTracker`.
|
/// Mean time difference between values in this `History`.
|
||||||
pub fn mean_time_interval(&self) -> Option<f32> {
|
pub fn mean_time_interval(&self) -> Option<f32> {
|
||||||
if let (Some(first), Some(last)) = (self.values.front(), self.values.back()) {
|
if let (Some(first), Some(last)) = (self.values.front(), self.values.back()) {
|
||||||
let n = self.len();
|
let n = self.len();
|
||||||
|
@ -88,7 +140,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> MovementTracker<T>
|
impl<T> History<T>
|
||||||
where
|
where
|
||||||
T: Copy,
|
T: Copy,
|
||||||
T: std::iter::Sum,
|
T: std::iter::Sum,
|
||||||
|
@ -108,7 +160,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, Vel> MovementTracker<T>
|
impl<T, Vel> History<T>
|
||||||
where
|
where
|
||||||
T: Copy,
|
T: Copy,
|
||||||
T: std::ops::Sub<Output = Vel>,
|
T: std::ops::Sub<Output = Vel>,
|
|
@ -1,6 +1,6 @@
|
||||||
//! The input needed by Egui.
|
//! The input needed by Egui.
|
||||||
|
|
||||||
use crate::math::*;
|
use crate::{math::*, History};
|
||||||
|
|
||||||
/// If mouse moves more than this, it is no longer a click (but maybe a drag)
|
/// If mouse moves more than this, it is no longer a click (but maybe a drag)
|
||||||
const MAX_CLICK_DIST: f32 = 6.0;
|
const MAX_CLICK_DIST: f32 = 6.0;
|
||||||
|
@ -128,7 +128,7 @@ pub struct MouseInput {
|
||||||
|
|
||||||
/// Recent movement of the mouse.
|
/// Recent movement of the mouse.
|
||||||
/// Used for calculating velocity of mouse pointer.
|
/// Used for calculating velocity of mouse pointer.
|
||||||
pub pos_tracker: MovementTracker<Pos2>,
|
pos_history: History<Pos2>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MouseInput {
|
impl Default for MouseInput {
|
||||||
|
@ -145,7 +145,7 @@ impl Default for MouseInput {
|
||||||
press_origin: None,
|
press_origin: None,
|
||||||
delta: Vec2::zero(),
|
delta: Vec2::zero(),
|
||||||
velocity: Vec2::zero(),
|
velocity: Vec2::zero(),
|
||||||
pos_tracker: MovementTracker::new(1000, 0.1),
|
pos_history: History::new(1000, 0.1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -295,20 +295,20 @@ impl MouseInput {
|
||||||
if pressed {
|
if pressed {
|
||||||
// Start of a drag: we want to track the velocity for during the drag
|
// Start of a drag: we want to track the velocity for during the drag
|
||||||
// and ignore any incoming movement
|
// and ignore any incoming movement
|
||||||
self.pos_tracker.clear();
|
self.pos_history.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(mouse_pos) = new.mouse_pos {
|
if let Some(mouse_pos) = new.mouse_pos {
|
||||||
self.pos_tracker.add(new.time, mouse_pos);
|
self.pos_history.add(new.time, mouse_pos);
|
||||||
} else {
|
} else {
|
||||||
// we do not clear the `mouse_tracker` here, because it is exactly when a finger has
|
// we do not clear the `mouse_tracker` here, because it is exactly when a finger has
|
||||||
// released from the touch screen that we may want to assign a velocity to whatever
|
// released from the touch screen that we may want to assign a velocity to whatever
|
||||||
// the user tried to throw
|
// the user tried to throw
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pos_tracker.flush(new.time);
|
self.pos_history.flush(new.time);
|
||||||
let velocity = if self.pos_tracker.len() >= 3 && self.pos_tracker.dt() > 0.01 {
|
let velocity = if self.pos_history.len() >= 3 && self.pos_history.duration() > 0.01 {
|
||||||
self.pos_tracker.velocity().unwrap_or_default()
|
self.pos_history.velocity().unwrap_or_default()
|
||||||
} else {
|
} else {
|
||||||
Vec2::default()
|
Vec2::default()
|
||||||
};
|
};
|
||||||
|
@ -325,7 +325,7 @@ impl MouseInput {
|
||||||
press_origin,
|
press_origin,
|
||||||
delta,
|
delta,
|
||||||
velocity,
|
velocity,
|
||||||
pos_tracker: self.pos_tracker,
|
pos_history: self.pos_history,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -411,7 +411,7 @@ impl MouseInput {
|
||||||
press_origin,
|
press_origin,
|
||||||
delta,
|
delta,
|
||||||
velocity,
|
velocity,
|
||||||
pos_tracker: _,
|
pos_history: _,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
ui.label(format!("down: {}", down));
|
ui.label(format!("down: {}", down));
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{math::*, style::Style};
|
use crate::{math::*, style::Style, Align};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -17,44 +17,6 @@ impl Default for Direction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// left/center/right or top/center/bottom alignment for e.g. anchors and `Layout`s.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
||||||
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
|
|
||||||
pub enum Align {
|
|
||||||
/// Left/Top
|
|
||||||
Min,
|
|
||||||
|
|
||||||
/// Note: requires a bounded/known available_width.
|
|
||||||
Center,
|
|
||||||
|
|
||||||
/// Right/Bottom
|
|
||||||
/// Note: requires a bounded/known available_width.
|
|
||||||
Max,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Align {
|
|
||||||
fn default() -> Align {
|
|
||||||
Align::Min
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used e.g. to anchor a piece of text to a part of the rectangle.
|
|
||||||
/// Give a position within the rect, specified by the aligns
|
|
||||||
pub(crate) fn anchor_rect(rect: Rect, anchor: (Align, Align)) -> Rect {
|
|
||||||
let x = match anchor.0 {
|
|
||||||
Align::Min => rect.left(),
|
|
||||||
Align::Center => rect.left() - 0.5 * rect.width(),
|
|
||||||
Align::Max => rect.left() - rect.width(),
|
|
||||||
};
|
|
||||||
let y = match anchor.1 {
|
|
||||||
Align::Min => rect.top(),
|
|
||||||
Align::Center => rect.top() - 0.5 * rect.height(),
|
|
||||||
Align::Max => rect.top() - rect.height(),
|
|
||||||
};
|
|
||||||
Rect::from_min_size(pos2(x, y), rect.size())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// The layout of a `Ui`, e.g. horizontal left-aligned.
|
/// The layout of a `Ui`, e.g. horizontal left-aligned.
|
||||||
|
|
|
@ -44,12 +44,14 @@
|
||||||
rust_2018_idioms,
|
rust_2018_idioms,
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
pub mod align;
|
||||||
mod animation_manager;
|
mod animation_manager;
|
||||||
pub mod app;
|
pub mod app;
|
||||||
pub(crate) mod cache;
|
pub(crate) mod cache;
|
||||||
pub mod containers;
|
pub mod containers;
|
||||||
mod context;
|
mod context;
|
||||||
pub mod demos;
|
pub mod demos;
|
||||||
|
mod history;
|
||||||
mod id;
|
mod id;
|
||||||
mod input;
|
mod input;
|
||||||
mod introspection;
|
mod introspection;
|
||||||
|
@ -66,9 +68,11 @@ mod ui;
|
||||||
pub mod widgets;
|
pub mod widgets;
|
||||||
|
|
||||||
pub use {
|
pub use {
|
||||||
|
align::Align,
|
||||||
containers::*,
|
containers::*,
|
||||||
context::Context,
|
context::Context,
|
||||||
demos::DemoApp,
|
demos::DemoApp,
|
||||||
|
history::History,
|
||||||
id::Id,
|
id::Id,
|
||||||
input::*,
|
input::*,
|
||||||
layers::*,
|
layers::*,
|
||||||
|
|
|
@ -4,13 +4,12 @@ use std::ops::{Add, Mul, RangeInclusive};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
mod movement_tracker;
|
|
||||||
mod pos2;
|
mod pos2;
|
||||||
mod rect;
|
mod rect;
|
||||||
pub mod smart_aim;
|
pub mod smart_aim;
|
||||||
mod vec2;
|
mod vec2;
|
||||||
|
|
||||||
pub use {movement_tracker::*, pos2::*, rect::*, vec2::*};
|
pub use {pos2::*, rect::*, vec2::*};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -147,6 +147,10 @@ impl Rect {
|
||||||
self.min.y..=self.max.y
|
self.min.y..=self.max.y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bottom_up_range(&self) -> RangeInclusive<f32> {
|
||||||
|
self.max.y..=self.min.y
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.max.x < self.min.x || self.max.y < self.min.y
|
self.max.x < self.min.x || self.max.y < self.min.y
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use {
|
use {
|
||||||
super::{font::Galley, fonts::TextStyle, Srgba, Triangles},
|
super::{font::Galley, fonts::TextStyle, Fonts, Srgba, Triangles},
|
||||||
crate::math::{Pos2, Rect},
|
crate::{
|
||||||
|
align::{anchor_rect, Align},
|
||||||
|
math::{Pos2, Rect},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: rename, e.g. `paint::Cmd`?
|
// TODO: rename, e.g. `paint::Cmd`?
|
||||||
|
@ -86,6 +89,25 @@ impl PaintCmd {
|
||||||
stroke: stroke.into(),
|
stroke: stroke.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn text(
|
||||||
|
fonts: &Fonts,
|
||||||
|
pos: Pos2,
|
||||||
|
anchor: (Align, Align),
|
||||||
|
text: impl Into<String>,
|
||||||
|
text_style: TextStyle,
|
||||||
|
color: Srgba,
|
||||||
|
) -> Self {
|
||||||
|
let font = &fonts[text_style];
|
||||||
|
let galley = font.layout_multiline(text.into(), f32::INFINITY);
|
||||||
|
let rect = anchor_rect(Rect::from_min_size(pos, galley.size), anchor);
|
||||||
|
Self::Text {
|
||||||
|
pos: rect.min,
|
||||||
|
galley,
|
||||||
|
text_style,
|
||||||
|
color,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
anchor_rect, color,
|
align::{anchor_rect, Align, LEFT_TOP},
|
||||||
|
color,
|
||||||
layers::PaintCmdIdx,
|
layers::PaintCmdIdx,
|
||||||
math::{Pos2, Rect, Vec2},
|
math::{Pos2, Rect, Vec2},
|
||||||
paint::{font, Fonts, PaintCmd, Stroke, TextStyle},
|
paint::{font, Fonts, PaintCmd, Stroke, TextStyle},
|
||||||
Align, Context, Layer, Srgba,
|
Context, Layer, Srgba,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Helper to paint shapes and text to a specific region on a specific layer.
|
/// Helper to paint shapes and text to a specific region on a specific layer.
|
||||||
|
@ -116,18 +117,16 @@ impl Painter {
|
||||||
impl Painter {
|
impl Painter {
|
||||||
pub fn debug_rect(&mut self, rect: Rect, color: Srgba, text: impl Into<String>) {
|
pub fn debug_rect(&mut self, rect: Rect, color: Srgba, text: impl Into<String>) {
|
||||||
self.rect_stroke(rect, 0.0, (1.0, color));
|
self.rect_stroke(rect, 0.0, (1.0, color));
|
||||||
let anchor = (Align::Min, Align::Min);
|
|
||||||
let text_style = TextStyle::Monospace;
|
let text_style = TextStyle::Monospace;
|
||||||
self.text(rect.min, anchor, text.into(), text_style, color);
|
self.text(rect.min, LEFT_TOP, text.into(), text_style, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn error(&self, pos: Pos2, text: impl Into<String>) {
|
pub fn error(&self, pos: Pos2, text: impl Into<String>) {
|
||||||
let text = text.into();
|
let text = text.into();
|
||||||
let anchor = (Align::Min, Align::Min);
|
|
||||||
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 = anchor_rect(Rect::from_min_size(pos, galley.size), anchor);
|
let rect = anchor_rect(Rect::from_min_size(pos, galley.size), LEFT_TOP);
|
||||||
self.add(PaintCmd::Rect {
|
self.add(PaintCmd::Rect {
|
||||||
rect: rect.expand(2.0),
|
rect: rect.expand(2.0),
|
||||||
corner_radius: 0.0,
|
corner_radius: 0.0,
|
||||||
|
|
|
@ -23,11 +23,18 @@ pub struct Style {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Style {
|
impl Style {
|
||||||
// TODO: rename style.interact() to maybe... `style.response_visuals` ?
|
// TODO: rename style.interact() to maybe... `style.interactive` ?
|
||||||
/// Use this style for interactive things
|
/// Use this style for interactive things.
|
||||||
|
/// Note that you must already have a response,
|
||||||
|
/// i.e. you must allocate space and interact BEFORE painting the widget!
|
||||||
pub fn interact(&self, response: &Response) -> &WidgetVisuals {
|
pub fn interact(&self, response: &Response) -> &WidgetVisuals {
|
||||||
self.visuals.widgets.style(response)
|
self.visuals.widgets.style(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Style to use for non-interactive widgets.
|
||||||
|
pub fn noninteractive(&self) -> &WidgetVisuals {
|
||||||
|
&self.visuals.widgets.noninteractive
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -116,6 +123,10 @@ pub struct Visuals {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Visuals {
|
impl Visuals {
|
||||||
|
pub fn noninteractive(&self) -> &WidgetVisuals {
|
||||||
|
&self.widgets.noninteractive
|
||||||
|
}
|
||||||
|
|
||||||
pub fn text_color(&self) -> Srgba {
|
pub fn text_color(&self) -> Srgba {
|
||||||
self.widgets.noninteractive.text_color()
|
self.widgets.noninteractive.text_color()
|
||||||
}
|
}
|
||||||
|
|
|
@ -478,6 +478,11 @@ impl Ui {
|
||||||
self.add(label.into().heading())
|
self.add(label.into().heading())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shortcut for `add(Label::new(text).monospace())`
|
||||||
|
pub fn monospace(&mut self, label: impl Into<Label>) -> Response {
|
||||||
|
self.add(label.into().monospace())
|
||||||
|
}
|
||||||
|
|
||||||
/// Shortcut for `add(Hyperlink::new(url))`
|
/// Shortcut for `add(Hyperlink::new(url))`
|
||||||
pub fn hyperlink(&mut self, url: impl Into<String>) -> Response {
|
pub fn hyperlink(&mut self, url: impl Into<String>) -> Response {
|
||||||
self.add(Hyperlink::new(url))
|
self.add(Hyperlink::new(url))
|
||||||
|
@ -616,10 +621,10 @@ impl Ui {
|
||||||
impl Ui {
|
impl Ui {
|
||||||
pub fn collapsing<R>(
|
pub fn collapsing<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
text: impl Into<String>,
|
heading: impl Into<String>,
|
||||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||||
) -> Option<R> {
|
) -> Option<R> {
|
||||||
CollapsingHeader::new(text).show(self, add_contents)
|
CollapsingHeader::new(heading).show(self, add_contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a child ui at the current cursor.
|
/// Create a child ui at the current cursor.
|
||||||
|
|
|
@ -65,6 +65,10 @@ impl Label {
|
||||||
self.text_style(TextStyle::Heading)
|
self.text_style(TextStyle::Heading)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn monospace(self) -> Self {
|
||||||
|
self.text_style(TextStyle::Monospace)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn text_color(mut self, text_color: Srgba) -> Self {
|
pub fn text_color(mut self, text_color: Srgba) -> Self {
|
||||||
self.text_color = Some(text_color);
|
self.text_color = Some(text_color);
|
||||||
self
|
self
|
||||||
|
|
|
@ -14,7 +14,7 @@ const EGUI_MEMORY_KEY: &str = "egui";
|
||||||
const WINDOW_KEY: &str = "window";
|
const WINDOW_KEY: &str = "window";
|
||||||
|
|
||||||
pub struct GliumBackend {
|
pub struct GliumBackend {
|
||||||
frame_times: egui::MovementTracker<f32>,
|
previous_frame_time: Option<f32>,
|
||||||
quit: bool,
|
quit: bool,
|
||||||
painter: Painter,
|
painter: Painter,
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ pub struct GliumBackend {
|
||||||
impl GliumBackend {
|
impl GliumBackend {
|
||||||
pub fn new(painter: Painter) -> Self {
|
pub fn new(painter: Painter) -> Self {
|
||||||
Self {
|
Self {
|
||||||
frame_times: egui::MovementTracker::new(1000, 1.0),
|
previous_frame_time: None,
|
||||||
quit: false,
|
quit: false,
|
||||||
painter,
|
painter,
|
||||||
}
|
}
|
||||||
|
@ -30,12 +30,8 @@ impl GliumBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Backend for GliumBackend {
|
impl Backend for GliumBackend {
|
||||||
fn cpu_time(&self) -> f32 {
|
fn cpu_usage(&self) -> Option<f32> {
|
||||||
self.frame_times.average().unwrap_or_default()
|
self.previous_frame_time
|
||||||
}
|
|
||||||
|
|
||||||
fn fps(&self) -> f32 {
|
|
||||||
1.0 / self.frame_times.mean_time_interval().unwrap_or_default()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn seconds_since_midnight(&self) -> Option<f64> {
|
fn seconds_since_midnight(&self) -> Option<f64> {
|
||||||
|
@ -100,7 +96,7 @@ pub fn run(title: &str, mut storage: FileStorage, mut app: impl App + 'static) -
|
||||||
let (output, paint_jobs) = ctx.end_frame();
|
let (output, paint_jobs) = ctx.end_frame();
|
||||||
|
|
||||||
let frame_time = (Instant::now() - egui_start).as_secs_f64() as f32;
|
let frame_time = (Instant::now() - egui_start).as_secs_f64() as f32;
|
||||||
runner.frame_times.add(raw_input.time, frame_time);
|
runner.previous_frame_time = Some(frame_time);
|
||||||
|
|
||||||
runner
|
runner
|
||||||
.painter
|
.painter
|
||||||
|
@ -127,7 +123,7 @@ pub fn run(title: &str, mut storage: FileStorage, mut app: impl App + 'static) -
|
||||||
|
|
||||||
glutin::event::Event::WindowEvent { event, .. } => {
|
glutin::event::Event::WindowEvent { event, .. } => {
|
||||||
input_to_egui(event, clipboard.as_mut(), &mut raw_input, control_flow);
|
input_to_egui(event, clipboard.as_mut(), &mut raw_input, control_flow);
|
||||||
display.gl_window().window().request_redraw(); // TODO: maybe only on some events?
|
display.gl_window().window().request_redraw(); // TODO: ask Egui if the events warrants a repaint instead
|
||||||
}
|
}
|
||||||
glutin::event::Event::LoopDestroyed => {
|
glutin::event::Event::LoopDestroyed => {
|
||||||
egui::app::set_value(
|
egui::app::set_value(
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub use egui::{
|
||||||
pub struct WebBackend {
|
pub struct WebBackend {
|
||||||
ctx: Arc<egui::Context>,
|
ctx: Arc<egui::Context>,
|
||||||
painter: webgl::Painter,
|
painter: webgl::Painter,
|
||||||
frame_times: egui::MovementTracker<f32>,
|
previous_frame_time: Option<f32>,
|
||||||
frame_start: Option<f64>,
|
frame_start: Option<f64>,
|
||||||
last_save_time: Option<f64>,
|
last_save_time: Option<f64>,
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ impl WebBackend {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
ctx,
|
ctx,
|
||||||
painter: webgl::Painter::new(canvas_id)?,
|
painter: webgl::Painter::new(canvas_id)?,
|
||||||
frame_times: egui::MovementTracker::new(1000, 1.0),
|
previous_frame_time: None,
|
||||||
frame_start: None,
|
frame_start: None,
|
||||||
last_save_time: None,
|
last_save_time: None,
|
||||||
})
|
})
|
||||||
|
@ -50,7 +50,7 @@ impl WebBackend {
|
||||||
self.auto_save();
|
self.auto_save();
|
||||||
|
|
||||||
let now = now_sec();
|
let now = now_sec();
|
||||||
self.frame_times.add(now, (now - frame_start) as f32);
|
self.previous_frame_time = Some((now - frame_start) as f32);
|
||||||
|
|
||||||
Ok((output, paint_jobs))
|
Ok((output, paint_jobs))
|
||||||
}
|
}
|
||||||
|
@ -87,13 +87,8 @@ impl Backend for WebBackend {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// excludes painting
|
fn cpu_usage(&self) -> Option<f32> {
|
||||||
fn cpu_time(&self) -> f32 {
|
self.previous_frame_time
|
||||||
self.frame_times.average().unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fps(&self) -> f32 {
|
|
||||||
1.0 / self.frame_times.mean_time_interval().unwrap_or_default()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn seconds_since_midnight(&self) -> Option<f64> {
|
fn seconds_since_midnight(&self) -> Option<f64> {
|
||||||
|
|
Loading…
Reference in a new issue