Expose Timer API to rust API

And use it in the puzzle demo to implement the auto play mode
This commit is contained in:
Olivier Goffart 2020-11-16 12:14:27 +01:00
parent eadfdbe5fb
commit 0672f4b3cd
5 changed files with 66 additions and 4 deletions

View file

@ -156,6 +156,7 @@ pub use sixtyfps_corelib::model::{
}; };
pub use sixtyfps_corelib::sharedarray::SharedArray; pub use sixtyfps_corelib::sharedarray::SharedArray;
pub use sixtyfps_corelib::string::SharedString; pub use sixtyfps_corelib::string::SharedString;
pub use sixtyfps_corelib::timers::{Timer, TimerMode};
pub use sixtyfps_corelib::{ARGBColor, Color}; pub use sixtyfps_corelib::{ARGBColor, Color};
/// internal re_exports used by the macro generated /// internal re_exports used by the macro generated

View file

@ -13,8 +13,9 @@ Remaining feature to implement to have parity:
Seatle). Note that this feature is kind of broken in the flutter example as it is only applied Seatle). Note that this feature is kind of broken in the flutter example as it is only applied
when changing themes when changing themes
* Expanding cirle animation when pressing a tile. * Expanding cirle animation when pressing a tile.
* Auto-play mode. Including animation of the auto-play checkbox * Animation of the auto-play checkbox.
* When the puzzle is finished, the last tile is added, and the tiles are growing in the Seatle theme, * When the puzzle is finished, the last tile is added, and the tiles are growing in the Seatle theme,
or a hand apears, and the puzzle cannot be moved. or a hand apears, and the puzzle cannot be moved.
* The different styles are well separated in different files. * The different styles are well separated in different files.
* Shadow on the tiles

View file

@ -48,6 +48,7 @@ struct AppState {
/// An array of 16 values wixh represent a 4x4 matrix containing the piece number in that /// An array of 16 values wixh represent a 4x4 matrix containing the piece number in that
/// position. -1 is no piece. /// position. -1 is no piece.
positions: Vec<i8>, positions: Vec<i8>,
auto_play_timer: sixtyfps::Timer,
} }
impl AppState { impl AppState {
@ -99,6 +100,24 @@ impl AppState {
self.set_pieces_pos(self.positions[swap as usize] as _, swap); self.set_pieces_pos(self.positions[swap as usize] as _, swap);
} }
} }
fn random_move(&mut self) {
let mut rng = rand::thread_rng();
let hole = self.positions.iter().position(|x| *x == -1).unwrap() as i8;
let mut p;
loop {
p = rand::Rng::gen_range(&mut rng, 0, 16);
if hole == p {
continue;
} else if hole % 4 == p % 4 {
break;
} else if hole / 4 == p / 4 {
break;
}
}
let p = self.positions[p as usize];
self.piece_clicked(p)
}
} }
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))] #[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))]
@ -113,12 +132,34 @@ pub fn main() {
pieces: Rc::new(sixtyfps::VecModel::<Piece>::from(vec![Piece::default(); 15])), pieces: Rc::new(sixtyfps::VecModel::<Piece>::from(vec![Piece::default(); 15])),
main_window: main_window.as_weak(), main_window: main_window.as_weak(),
positions: vec![], positions: vec![],
auto_play_timer: Default::default(),
})); }));
state.borrow_mut().randomize(); state.borrow_mut().randomize();
main_window.as_ref().set_pieces(sixtyfps::ModelHandle::new(state.borrow().pieces.clone())); main_window.as_ref().set_pieces(sixtyfps::ModelHandle::new(state.borrow().pieces.clone()));
let state_copy = state.clone(); let state_copy = state.clone();
main_window.as_ref().on_piece_cliked(move |p| state_copy.borrow_mut().piece_clicked(p as i8)); main_window.as_ref().on_piece_cliked(move |p| {
state_copy.borrow().auto_play_timer.stop();
state_copy.borrow().main_window.upgrade().map(|x| x.as_ref().set_auto_play(false));
state_copy.borrow_mut().piece_clicked(p as i8);
});
let state_copy = state.clone(); let state_copy = state.clone();
main_window.as_ref().on_reset(move || state_copy.borrow_mut().randomize()); main_window.as_ref().on_reset(move || state_copy.borrow_mut().randomize());
let state_copy = state.clone();
main_window.as_ref().on_enable_auto_mode(move |enabled| {
if enabled {
let state_weak = Rc::downgrade(&state_copy);
state_copy.borrow().auto_play_timer.start(
sixtyfps::TimerMode::Repeated,
std::time::Duration::from_millis(200),
Box::new(move || {
if let Some(state) = state_weak.upgrade() {
state.borrow_mut().random_move();
}
}),
);
} else {
state_copy.borrow().auto_play_timer.stop();
}
});
main_window.run(); main_window.run();
} }

View file

@ -33,6 +33,8 @@ struct Theme := {
export MainWindow := Window { export MainWindow := Window {
signal piece_cliked(int); signal piece_cliked(int);
signal reset(); signal reset();
signal enable_auto_mode(bool);
property <bool> auto_play;
property <int> moves; property <int> moves;
property <int> tiles-left; property <int> tiles-left;
property <[Piece]> pieces: [ property <[Piece]> pieces: [
@ -206,7 +208,7 @@ export MainWindow := Window {
+ (parent.width - (4*pieces_size + 3*pieces_spacing))/2; + (parent.width - (4*pieces_size + 3*pieces_spacing))/2;
y: px * (pieces_size + pieces_spacing) y: px * (pieces_size + pieces_spacing)
+ (parent.height - (4*pieces_size + 3*pieces_spacing))/2; + (parent.height - (4*pieces_size + 3*pieces_spacing))/2;
animate px , py { duration: 200ms; easing: cubic-bezier(0.17,0.76,0.4,1.9); } animate px , py { duration: 170ms; easing: cubic-bezier(0.17,0.76,0.4,1.75); }
animate border-width, border-radius { duration: 500ms; easing: ease-out; } animate border-width, border-radius { duration: 500ms; easing: ease-out; }
if (current-theme-index == 1) : Rectangle { if (current-theme-index == 1) : Rectangle {
@ -259,6 +261,21 @@ export MainWindow := Window {
clicked => { root.reset(); } clicked => { root.reset(); }
} }
} }
Text {
// FIXME: this should be a rectangle with an animated ✓
text: auto_play ? " ☑ " : " ☐ ";
color: auto_play ? current-theme.game-highlight-color : current-theme.game-text-color;
animate color { duration: 200ms; }
vertical-alignment: align-center;
TouchArea {
width: 100%;
height: 100%;
clicked => {
auto_play = !auto_play;
root.enable_auto_mode(auto_play);
}
}
}
Rectangle {} // stretch Rectangle {} // stretch
Text { Text {
text: root.moves; text: root.moves;

View file

@ -19,6 +19,8 @@ use std::cell::{Cell, RefCell};
type TimerCallback = Box<dyn Fn()>; type TimerCallback = Box<dyn Fn()>;
/// The TimerMode specifies what should happen after the timer fired. /// The TimerMode specifies what should happen after the timer fired.
///
/// Used by the [`Timer::start`] function.
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub enum TimerMode { pub enum TimerMode {
/// A SingleShot timer is fired only once. /// A SingleShot timer is fired only once.
@ -40,7 +42,7 @@ impl Timer {
/// ///
/// Arguments: /// Arguments:
/// * `mode`: The timer mode to apply, i.e. whether to repeatedly fire the timer or just once. /// * `mode`: The timer mode to apply, i.e. whether to repeatedly fire the timer or just once.
/// * `duration`: The duration from now until when the fire should fire. /// * `duration`: The duration from now until when the timer should fire.
/// * `callback`: The function to call when the time has been reached or exceeded. /// * `callback`: The function to call when the time has been reached or exceeded.
pub fn start(&self, mode: TimerMode, duration: std::time::Duration, callback: TimerCallback) { pub fn start(&self, mode: TimerMode, duration: std::time::Duration, callback: TimerCallback) {
CURRENT_TIMERS.with(|timers| { CURRENT_TIMERS.with(|timers| {