diff --git a/api/sixtyfps-rs/lib.rs b/api/sixtyfps-rs/lib.rs index 4f76568de..9d994e85c 100644 --- a/api/sixtyfps-rs/lib.rs +++ b/api/sixtyfps-rs/lib.rs @@ -156,6 +156,7 @@ pub use sixtyfps_corelib::model::{ }; pub use sixtyfps_corelib::sharedarray::SharedArray; pub use sixtyfps_corelib::string::SharedString; +pub use sixtyfps_corelib::timers::{Timer, TimerMode}; pub use sixtyfps_corelib::{ARGBColor, Color}; /// internal re_exports used by the macro generated diff --git a/examples/slide_puzzle/README.md b/examples/slide_puzzle/README.md index e94795153..3dcb6ce63 100644 --- a/examples/slide_puzzle/README.md +++ b/examples/slide_puzzle/README.md @@ -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 when changing themes * 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, or a hand apears, and the puzzle cannot be moved. * The different styles are well separated in different files. + * Shadow on the tiles diff --git a/examples/slide_puzzle/main.rs b/examples/slide_puzzle/main.rs index 16b69d41b..eac3eef94 100644 --- a/examples/slide_puzzle/main.rs +++ b/examples/slide_puzzle/main.rs @@ -48,6 +48,7 @@ struct AppState { /// An array of 16 values wixh represent a 4x4 matrix containing the piece number in that /// position. -1 is no piece. positions: Vec, + auto_play_timer: sixtyfps::Timer, } impl AppState { @@ -99,6 +100,24 @@ impl AppState { 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))] @@ -113,12 +132,34 @@ pub fn main() { pieces: Rc::new(sixtyfps::VecModel::::from(vec![Piece::default(); 15])), main_window: main_window.as_weak(), positions: vec![], + auto_play_timer: Default::default(), })); state.borrow_mut().randomize(); main_window.as_ref().set_pieces(sixtyfps::ModelHandle::new(state.borrow().pieces.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(); 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(); } diff --git a/examples/slide_puzzle/slide_puzzle.60 b/examples/slide_puzzle/slide_puzzle.60 index a1e8d10f9..0efeed186 100644 --- a/examples/slide_puzzle/slide_puzzle.60 +++ b/examples/slide_puzzle/slide_puzzle.60 @@ -33,6 +33,8 @@ struct Theme := { export MainWindow := Window { signal piece_cliked(int); signal reset(); + signal enable_auto_mode(bool); + property auto_play; property moves; property tiles-left; property <[Piece]> pieces: [ @@ -206,7 +208,7 @@ export MainWindow := Window { + (parent.width - (4*pieces_size + 3*pieces_spacing))/2; y: px * (pieces_size + pieces_spacing) + (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; } if (current-theme-index == 1) : Rectangle { @@ -259,6 +261,21 @@ export MainWindow := Window { 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 Text { text: root.moves; diff --git a/sixtyfps_runtime/corelib/timers.rs b/sixtyfps_runtime/corelib/timers.rs index 042ff1609..966ac93b9 100644 --- a/sixtyfps_runtime/corelib/timers.rs +++ b/sixtyfps_runtime/corelib/timers.rs @@ -19,6 +19,8 @@ use std::cell::{Cell, RefCell}; type TimerCallback = Box; /// The TimerMode specifies what should happen after the timer fired. +/// +/// Used by the [`Timer::start`] function. #[derive(Copy, Clone)] pub enum TimerMode { /// A SingleShot timer is fired only once. @@ -40,7 +42,7 @@ impl Timer { /// /// Arguments: /// * `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. pub fn start(&self, mode: TimerMode, duration: std::time::Duration, callback: TimerCallback) { CURRENT_TIMERS.with(|timers| {