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] 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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
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 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,
|
||||||
|
|
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 {
|
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)",
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue