game-of-life-gui/src/widgets/runner.rs

91 lines
2.6 KiB
Rust

use std::{time::Duration, thread::ThreadId};
use adw::prelude::ButtonExt;
use gtk::prelude::WidgetExt;
use relm4::*;
/// A message that can be provided to the runner
#[derive(Debug)]
pub enum RunMsg {
Toggle,
Pass(ThreadId),
}
/// A message that the runner can forward to the app
#[derive(Debug)]
pub enum RunOutput {
Advance,
}
/// The runner's internal state
pub struct RunModel {
running: bool,
id: Option<ThreadId>,
}
#[relm4::component(pub)]
impl SimpleComponent for RunModel {
type Widgets = RunWidgets;
type Init = ();
type Input = RunMsg;
type Output = RunOutput;
view! {
#[name = "button"]
&gtk::Button {
#[watch]
set_tooltip_text: Some(if model.running { "Pause" } else { "Play" }),
#[watch]
set_icon_name: if model.running { "media-playback-stop-symbolic" } else { "media-playback-start-symbolic" },
connect_clicked[sender] => move |_| {
sender.input(RunMsg::Toggle);
},
}
}
fn init(
_init: Self::Init,
root: &Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let model = RunModel {
running: false,
id: None,
};
let widgets = view_output!();
ComponentParts { model, widgets }
}
fn update(
&mut self,
input: RunMsg,
sender: ComponentSender<Self>,
) {
match input {
// Toggle the play-pause state of the runner
RunMsg::Toggle => {
self.running = !self.running;
}
// Called every 500ms when running
RunMsg::Pass(id) => {
if self.id != Some(id) {return}
}
}
/* Here, to prevent the possibility of a user pressing the play button twice in quick
* succession, spawning two threads that are both advancing the game, the runner keeps
* track of which thread id is the most recently spawned. If a thread detects that it is
* not the most recent, it will close.
*/
if self.running {
sender.output(RunOutput::Advance).unwrap();
self.id = Some(std::thread::spawn(move || {
// Note that timing is handled by the application, not the library. Another
// developer could change it
std::thread::sleep(Duration::from_millis(500));
sender.input(RunMsg::Pass(std::thread::current().id()));
}).thread().id());
}
}
}