mirror of https://github.com/emilk/egui.git
Emil Ernerfeldt
4 years ago
3 changed files with 215 additions and 3 deletions
@ -0,0 +1,172 @@ |
|||
use std::collections::VecDeque; |
|||
|
|||
#[derive(Clone, Debug, PartialEq)] |
|||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] |
|||
pub struct Settings { |
|||
/// Maximum number of undos.
|
|||
/// If your state is resource intensive, you should keep this low.
|
|||
///
|
|||
/// Default: `100`
|
|||
pub max_undos: usize, |
|||
|
|||
/// When that state hasn't changed for this many seconds,
|
|||
/// create a new undo point (if one is needed).
|
|||
///
|
|||
/// Default value: `1.0` seconds.
|
|||
pub stable_time: f32, |
|||
|
|||
/// If the state is changing so often that we never get to `stable_time`,
|
|||
/// then still create a save point every `auto_save_interval` seconds,
|
|||
/// so we have something to undo to.
|
|||
///
|
|||
/// Default value: `30` seconds.
|
|||
pub auto_save_interval: f32, |
|||
} |
|||
|
|||
impl Default for Settings { |
|||
fn default() -> Self { |
|||
Self { |
|||
max_undos: 100, |
|||
stable_time: 1.0, |
|||
auto_save_interval: 30.0, |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// Automatic undo system.
|
|||
///
|
|||
/// Every frame you feed it the most recent state.
|
|||
/// The `Undoer` compares it with the latest undo point
|
|||
/// and if there is a change it may create a new undo point.
|
|||
///
|
|||
/// `Undoer` follows two simple rules:
|
|||
///
|
|||
/// 1) If the state has changed since the latest undo point, but has
|
|||
/// remained stable for `stable_time` seconds, an new undo point is created.
|
|||
/// 2) If the state does not stabilize within `auto_save_interval` seconds, an undo point is created.
|
|||
///
|
|||
/// Rule 1) will make sure an undo point is not created until you _stop_ dragging that slider.
|
|||
/// Rule 2) will make sure that you will get some undo points even if you are constantly changing the state.
|
|||
#[derive(Clone, Default)] |
|||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] |
|||
pub struct Undoer<State> { |
|||
settings: Settings, |
|||
|
|||
/// New undoes are added to the back.
|
|||
/// Two adjacent undo points are never equal.
|
|||
/// The latest undo point may (often) be the current state.
|
|||
undos: VecDeque<State>, |
|||
|
|||
#[cfg_attr(feature = "serde", serde(skip))] |
|||
flux: Option<Flux<State>>, |
|||
} |
|||
|
|||
impl<State> std::fmt::Debug for Undoer<State> { |
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
|||
let Self { undos, .. } = self; |
|||
f.debug_struct("Undoer") |
|||
.field("undo count", &undos.len()) |
|||
.finish() |
|||
} |
|||
} |
|||
|
|||
/// Represents how the current state is changing
|
|||
#[derive(Clone)] |
|||
struct Flux<State> { |
|||
start_time: f64, |
|||
latest_change_time: f64, |
|||
latest_state: State, |
|||
} |
|||
|
|||
impl<State> Undoer<State> |
|||
where |
|||
State: Clone + PartialEq, |
|||
{ |
|||
/// Do we have an undo point different from the given state?
|
|||
pub fn has_undo(&self, current_state: &State) -> bool { |
|||
match self.undos.len() { |
|||
0 => false, |
|||
1 => self.undos.back() != Some(current_state), |
|||
_ => true, |
|||
} |
|||
} |
|||
|
|||
/// Return true if the state is currently changing
|
|||
pub fn is_in_flux(&self) -> bool { |
|||
self.flux.is_some() |
|||
} |
|||
|
|||
pub fn undo(&mut self, current_state: &State) -> Option<&State> { |
|||
if self.has_undo(current_state) { |
|||
self.flux = None; |
|||
|
|||
if self.undos.back() == Some(current_state) { |
|||
self.undos.pop_back(); |
|||
} |
|||
|
|||
// Note: we keep the undo point intact.
|
|||
self.undos.back() |
|||
} else { |
|||
None |
|||
} |
|||
} |
|||
|
|||
/// Add an undo point if, and only if, there has been a change since the latest undo point.
|
|||
///
|
|||
/// * `time`: current time in seconds.
|
|||
pub fn add_undo(&mut self, current_state: &State) { |
|||
if self.undos.back() != Some(current_state) { |
|||
self.undos.push_back(current_state.clone()); |
|||
} |
|||
while self.undos.len() > self.settings.max_undos { |
|||
self.undos.pop_front(); |
|||
} |
|||
self.flux = None; |
|||
} |
|||
|
|||
/// Call this as often as you want (e.g. every frame)
|
|||
/// and `Undoer` will determine if a new undo point should be created.
|
|||
///
|
|||
/// * `current_time`: current time in seconds.
|
|||
pub fn feed_state(&mut self, current_time: f64, current_state: &State) { |
|||
match self.undos.back() { |
|||
None => { |
|||
// First time feed_state is called.
|
|||
// always create an undo point:
|
|||
self.add_undo(current_state); |
|||
} |
|||
Some(latest_undo) => { |
|||
if latest_undo == current_state { |
|||
self.flux = None; |
|||
} else { |
|||
match self.flux.as_mut() { |
|||
None => { |
|||
self.flux = Some(Flux { |
|||
start_time: current_time, |
|||
latest_change_time: current_time, |
|||
latest_state: current_state.clone(), |
|||
}); |
|||
} |
|||
Some(flux) => { |
|||
if &flux.latest_state == current_state { |
|||
let time_since_latest_change = |
|||
(current_time - flux.latest_change_time) as f32; |
|||
if time_since_latest_change >= self.settings.stable_time { |
|||
self.add_undo(current_state); |
|||
} |
|||
} else { |
|||
let time_since_flux_start = (current_time - flux.start_time) as f32; |
|||
if time_since_flux_start >= self.settings.auto_save_interval { |
|||
self.add_undo(current_state); |
|||
} else { |
|||
flux.latest_change_time = current_time; |
|||
flux.latest_state = current_state.clone(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
Loading…
Reference in new issue