Kinetic windows
This commit is contained in:
parent
bfbb669d02
commit
45564f952b
12 changed files with 398 additions and 229 deletions
|
@ -12,7 +12,7 @@ This is the core library crate Emigui. It is fully platform independent without
|
|||
* [x] Collapsing header region
|
||||
* [x] Tooltip
|
||||
* [x] Movable/resizable windows
|
||||
* [ ] Kinetic windows
|
||||
* [x] Kinetic windows
|
||||
* [ ] BUG FIX: Don't catch clicks on closed windows
|
||||
* [ ] Scroll areas
|
||||
* [x] Vertical scrolling
|
||||
|
|
|
@ -14,6 +14,10 @@ pub(crate) struct State {
|
|||
|
||||
/// Last know size. Used for catching clicks.
|
||||
pub size: Vec2,
|
||||
|
||||
/// You can throw a Floating thing. It's fun.
|
||||
#[serde(skip)]
|
||||
pub vel: Vec2,
|
||||
}
|
||||
|
||||
// TODO: rename Floating to something else. Area?
|
||||
|
@ -56,6 +60,7 @@ impl Floating {
|
|||
let state = State {
|
||||
pos: default_pos,
|
||||
size: Vec2::zero(),
|
||||
vel: Vec2::zero(),
|
||||
};
|
||||
(state, true)
|
||||
}
|
||||
|
@ -75,8 +80,21 @@ impl Floating {
|
|||
let clip_rect = Rect::everything();
|
||||
let move_interact = ctx.interact(layer, &clip_rect, &rect, Some(id.with("move")));
|
||||
|
||||
let input = ctx.input();
|
||||
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:
|
||||
|
|
|
@ -10,8 +10,9 @@ pub struct Context {
|
|||
pub(crate) style: Mutex<Style>,
|
||||
pub(crate) fonts: Arc<Fonts>,
|
||||
/// Raw input from last frame. Use `input()` instead.
|
||||
pub(crate) last_raw_input: RawInput,
|
||||
last_raw_input: RawInput,
|
||||
pub(crate) input: GuiInput,
|
||||
mouse_tracker: MovementTracker<Pos2>,
|
||||
memory: Mutex<Memory>,
|
||||
pub(crate) graphics: Mutex<GraphicLayers>,
|
||||
|
||||
|
@ -29,6 +30,7 @@ impl Clone for Context {
|
|||
fonts: self.fonts.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()),
|
||||
|
@ -44,6 +46,7 @@ impl Context {
|
|||
fonts: Arc::new(Fonts::new(pixels_per_point)),
|
||||
last_raw_input: Default::default(),
|
||||
input: Default::default(),
|
||||
mouse_tracker: MovementTracker::new(1000, 0.1),
|
||||
memory: Default::default(),
|
||||
graphics: Default::default(),
|
||||
output: Default::default(),
|
||||
|
@ -63,6 +66,11 @@ impl Context {
|
|||
&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.
|
||||
pub fn last_raw_input(&self) -> &RawInput {
|
||||
&self.last_raw_input
|
||||
|
@ -76,6 +84,10 @@ impl Context {
|
|||
*self.style.lock() = style;
|
||||
}
|
||||
|
||||
pub fn pixels_per_point(&self) -> f32 {
|
||||
self.input.pixels_per_point
|
||||
}
|
||||
|
||||
/// Useful for pixel-perfect rendering
|
||||
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
||||
(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))
|
||||
}
|
||||
|
||||
// TODO: move
|
||||
pub fn begin_frame(&mut self, gui_input: GuiInput) {
|
||||
pub fn begin_frame(&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();
|
||||
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 {
|
||||
|
|
|
@ -12,7 +12,6 @@ struct Stats {
|
|||
/// Encapsulates input, layout and painting for ease of use.
|
||||
/// TODO: merge into Context
|
||||
pub struct Emigui {
|
||||
last_input: RawInput,
|
||||
ctx: Arc<Context>,
|
||||
stats: Stats,
|
||||
mesher_options: MesherOptions,
|
||||
|
@ -21,7 +20,6 @@ pub struct Emigui {
|
|||
impl Emigui {
|
||||
pub fn new(pixels_per_point: f32) -> Emigui {
|
||||
Emigui {
|
||||
last_input: Default::default(),
|
||||
ctx: Arc::new(Context::new(pixels_per_point)),
|
||||
stats: Default::default(),
|
||||
mesher_options: MesherOptions::default(),
|
||||
|
@ -37,18 +35,10 @@ impl Emigui {
|
|||
}
|
||||
|
||||
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
|
||||
let mut new_ctx = (*self.ctx).clone();
|
||||
|
||||
new_ctx.last_raw_input = new_input;
|
||||
new_ctx.begin_frame(gui_input);
|
||||
new_ctx.begin_frame(new_input);
|
||||
self.ctx = Arc::new(new_ctx);
|
||||
}
|
||||
|
||||
|
@ -59,7 +49,7 @@ impl Emigui {
|
|||
}
|
||||
|
||||
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 batches = mesh_paint_commands(&self.mesher_options, &self.ctx.fonts, paint_commands);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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
193
emigui/src/input.rs
Normal 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));
|
||||
}
|
||||
}
|
|
@ -14,11 +14,13 @@ pub mod example_app;
|
|||
mod font;
|
||||
mod fonts;
|
||||
mod id;
|
||||
mod input;
|
||||
mod layers;
|
||||
mod layout;
|
||||
pub mod math;
|
||||
mod memory;
|
||||
pub mod mesher;
|
||||
mod movement_tracker;
|
||||
mod region;
|
||||
mod style;
|
||||
mod texture_atlas;
|
||||
|
@ -31,11 +33,13 @@ pub use {
|
|||
context::Context,
|
||||
fonts::{FontDefinitions, Fonts, TextStyle},
|
||||
id::Id,
|
||||
input::*,
|
||||
layers::*,
|
||||
layout::{Align, GuiResponse},
|
||||
math::*,
|
||||
memory::Memory,
|
||||
mesher::{Mesh, PaintBatches, Vertex},
|
||||
movement_tracker::MovementTracker,
|
||||
region::Region,
|
||||
style::Style,
|
||||
texture_atlas::Texture,
|
||||
|
|
120
emigui/src/movement_tracker.rs
Normal file
120
emigui/src/movement_tracker.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -92,10 +92,7 @@ impl TextureAtlas {
|
|||
|
||||
impl Texture {
|
||||
pub fn ui(&self, region: &mut crate::Region) {
|
||||
use crate::{
|
||||
color::WHITE, label, layout::show_popup, math::*, widgets::Label, Mesh, PaintCmd,
|
||||
Vertex,
|
||||
};
|
||||
use crate::{color::WHITE, label, layout::show_popup, math::*, Mesh, PaintCmd, Vertex};
|
||||
|
||||
region.add(label!(
|
||||
"Texture size: {} x {} (hover to zoom)",
|
||||
|
|
|
@ -1,151 +1,12 @@
|
|||
use crate::{
|
||||
color::Color,
|
||||
fonts::TextStyle,
|
||||
math::{Pos2, Rect, Vec2},
|
||||
math::{Pos2, Rect},
|
||||
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)]
|
||||
pub struct Output {
|
||||
pub cursor_icon: CursorIcon,
|
||||
|
|
|
@ -55,8 +55,8 @@ impl Label {
|
|||
/// Usage: label!("Foo: {}", bar)
|
||||
#[macro_export]
|
||||
macro_rules! label {
|
||||
($fmt:expr) => (Label::new($fmt));
|
||||
($fmt:expr, $($arg:tt)*) => (Label::new(format!($fmt, $($arg)*)));
|
||||
($fmt:expr) => ($crate::widgets::Label::new($fmt));
|
||||
($fmt:expr, $($arg:tt)*) => ($crate::widgets::Label::new(format!($fmt, $($arg)*)));
|
||||
}
|
||||
|
||||
impl Widget for Label {
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
#![deny(warnings)]
|
||||
#[allow(clippy::single_match)]
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use {
|
||||
emigui::{containers::*, example_app::ExampleWindow, widgets::*, *},
|
||||
|
@ -42,7 +39,7 @@ fn main() {
|
|||
let start_time = Instant::now();
|
||||
let mut running = true;
|
||||
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 clipboard = emigui_glium::init_clipboard();
|
||||
|
||||
|
@ -82,8 +79,15 @@ fn main() {
|
|||
|
||||
region.add(
|
||||
label!(
|
||||
"Frame time: {:.1} ms (excludes painting)",
|
||||
1e3 * mean_frame_time(&frame_times)
|
||||
"CPU usage: {:.2} ms (excludes painting)",
|
||||
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),
|
||||
);
|
||||
|
@ -108,10 +112,10 @@ fn main() {
|
|||
|
||||
let (output, paint_batches) = emigui.end_frame();
|
||||
|
||||
frame_times.push_back((Instant::now() - emigui_start).as_secs_f64());
|
||||
while frame_times.len() > 30 {
|
||||
frame_times.pop_front();
|
||||
}
|
||||
frame_times.add(
|
||||
raw_input.time,
|
||||
(Instant::now() - emigui_start).as_secs_f64() as f32,
|
||||
);
|
||||
|
||||
painter.paint_batches(&display, paint_batches, emigui.texture());
|
||||
emigui_glium::handle_output(output, &display, clipboard.as_mut());
|
||||
|
@ -121,11 +125,3 @@ fn main() {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ pub struct State {
|
|||
emigui: Emigui,
|
||||
webgl_painter: emigui_wasm::webgl::Painter,
|
||||
|
||||
frame_times: std::collections::VecDeque<f64>,
|
||||
frame_times: emigui::MovementTracker<f32>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
|
@ -37,7 +37,7 @@ impl State {
|
|||
example_app: Default::default(),
|
||||
emigui,
|
||||
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());
|
||||
});
|
||||
|
||||
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(
|
||||
label!(
|
||||
"Frame time: {:.1} ms (excludes painting)",
|
||||
1e3 * mean_frame_time
|
||||
"CPU usage: {:.2} ms (excludes painting)",
|
||||
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),
|
||||
);
|
||||
|
@ -101,10 +103,8 @@ impl State {
|
|||
let bg_color = srgba(0, 0, 0, 0); // Use background css color.
|
||||
let (output, batches) = self.emigui.end_frame();
|
||||
|
||||
self.frame_times.push_back(now_sec() - everything_start);
|
||||
while self.frame_times.len() > 30 {
|
||||
self.frame_times.pop_front();
|
||||
}
|
||||
let now = now_sec();
|
||||
self.frame_times.add(now, (now - everything_start) as f32);
|
||||
|
||||
self.webgl_painter.paint_batches(
|
||||
bg_color,
|
||||
|
|
Loading…
Reference in a new issue