1use std::time::Duration;
2
3use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt, OrientableExt, WidgetExt};
4use relm4::{
5 Component, ComponentParts, ComponentSender, RelmApp, RelmWidgetExt, SharedState,
6 factory::{DynamicIndex, FactoryComponent, FactorySender, FactoryVecDeque},
7};
8
9#[derive(Default)]
10enum GameState {
11 #[default]
12 Start,
13 Countdown(u8),
14 Running,
15 Guessing,
16 End(bool),
17}
18
19static GAME_STATE: SharedState<GameState> = SharedState::new();
20
21#[derive(Debug)]
22struct GamePage {
23 id: u8,
24}
25
26#[derive(Debug)]
27enum CounterMsg {
28 Update,
29}
30
31#[derive(Debug)]
32enum CounterOutput {
33 StartGame(DynamicIndex),
34 SelectedGuess(DynamicIndex),
35}
36
37#[relm4::factory]
38impl FactoryComponent for GamePage {
39 type Init = u8;
40 type Input = CounterMsg;
41 type Output = CounterOutput;
42 type CommandOutput = ();
43 type ParentWidget = adw::TabView;
44
45 view! {
46 #[root]
47 root = gtk::Box {
48 set_orientation: gtk::Orientation::Horizontal,
49 set_spacing: 10,
50
51 gtk::CenterBox {
52 set_hexpand: true,
53 set_vexpand: true,
54
55 #[wrap(Some)]
56 set_center_widget = match *state {
57 GameState::Countdown(value) => {
58 gtk::Label {
59 set_valign: gtk::Align::Center,
60
61 #[watch]
62 set_label: &value.to_string(),
63 }
64 }
65 GameState::Running => {
66 gtk::Label {
67 set_valign: gtk::Align::Center,
68 set_label: "???",
69 }
70 }
71 GameState::Start => {
72 gtk::Box {
73 set_orientation: gtk::Orientation::Vertical,
74 set_valign: gtk::Align::Center,
75 set_margin_all: 10,
76 set_spacing: 10,
77
78 gtk::Label {
79 set_label: "Can you still find this tab after is was shuffled?",
80 },
81 gtk::Button {
82 set_label: "Start!",
83 connect_clicked[sender, index] => move |_| {
84 sender.output(CounterOutput::StartGame(index.clone())).unwrap()
85 }
86 },
87 }
88 }
89 GameState::Guessing => {
90 gtk::Button {
91 set_label: "This was my tab!",
92 set_valign: gtk::Align::Center,
93
94 connect_clicked[sender, index] => move |_| {
95 sender.output(CounterOutput::SelectedGuess(index.clone())).unwrap()
96 }
97 }
98 }
99 GameState::End(won) => {
100 gtk::Box {
101 set_orientation: gtk::Orientation::Vertical,
102 set_valign: gtk::Align::Center,
103 set_margin_all: 10,
104 set_spacing: 10,
105
106 gtk::Label {
107 #[watch]
108 set_label: if won {
109 "That's correct, you win!"
110 } else {
111 "You lose, this wasn't your tab..."
112 },
113 },
114 gtk::Button {
115 set_label: "Start again",
116 connect_clicked => move |_| {
117 *GAME_STATE.write() = GameState::Start;
118 }
119 },
120 }
121
122 }
123 }
124 }
125 },
126 #[local_ref]
127 returned_widget -> adw::TabPage {
128 #[watch]
129 set_title: &match *state {
130 GameState::Running | GameState::Guessing => {
131 "???".to_string()
132 }
133 _ => format!("Tab {}", self.id),
134 },
135 #[watch]
136 set_loading: matches!(*state, GameState::Running),
137 }
138 }
139
140 fn init_model(value: Self::Init, _index: &DynamicIndex, sender: FactorySender<Self>) -> Self {
141 GAME_STATE.subscribe(sender.input_sender(), |_| CounterMsg::Update);
142 Self { id: value }
143 }
144
145 fn init_widgets(
146 &mut self,
147 index: &DynamicIndex,
148 root: Self::Root,
149 returned_widget: &adw::TabPage,
150 sender: FactorySender<Self>,
151 ) -> Self::Widgets {
152 let state = GAME_STATE.read();
153 let widgets = view_output!();
154 widgets
155 }
156
157 fn pre_view() {
158 let state = GAME_STATE.read();
159 }
160
161 fn update(&mut self, msg: Self::Input, _sender: FactorySender<Self>) {
162 match msg {
163 CounterMsg::Update => (),
164 }
165 }
166}
167
168struct App {
169 counters: FactoryVecDeque<GamePage>,
170 start_index: Option<DynamicIndex>,
171}
172
173#[derive(Debug)]
174enum AppMsg {
175 SelectedGuess(DynamicIndex),
176 StartGame(DynamicIndex),
177 StopGame,
178}
179
180#[relm4::component]
181impl Component for App {
182 type Init = ();
183 type Input = AppMsg;
184 type Output = ();
185 type CommandOutput = bool;
186
187 view! {
188 adw::Window {
189 set_title: Some("Tab game!"),
190 set_default_size: (400, 200),
191
192 gtk::Box {
193 set_orientation: gtk::Orientation::Vertical,
194 set_spacing: 5,
195
196 adw::HeaderBar {},
197
198 adw::TabBar {
199 set_view: Some(tab_view),
200 set_autohide: false,
201 },
202
203 #[local_ref]
204 tab_view -> adw::TabView {
205 connect_close_page => |_, _| {
206 gtk::glib::signal::Propagation::Stop
207 }
208 }
209 }
210 }
211 }
212
213 fn init(
214 _: Self::Init,
215 root: Self::Root,
216 sender: ComponentSender<Self>,
217 ) -> ComponentParts<Self> {
218 let counters = FactoryVecDeque::builder()
219 .launch(adw::TabView::default())
220 .forward(sender.input_sender(), |output| match output {
221 CounterOutput::StartGame(index) => AppMsg::StartGame(index),
222 CounterOutput::SelectedGuess(guess) => AppMsg::SelectedGuess(guess),
223 });
224
225 let mut model = App {
226 counters,
227 start_index: None,
228 };
229
230 let tab_view = model.counters.widget();
231 let widgets = view_output!();
232
233 let mut counters_guard = model.counters.guard();
234 for i in 0..3 {
235 counters_guard.push_back(i);
236 }
237
238 counters_guard.drop();
242
243 ComponentParts { model, widgets }
244 }
245
246 fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>, _root: &Self::Root) {
247 match msg {
248 AppMsg::StartGame(index) => {
249 self.start_index = Some(index);
250 sender.command(|sender, _| async move {
251 for i in (1..4).rev() {
252 *GAME_STATE.write() = GameState::Countdown(i);
253 relm4::tokio::time::sleep(Duration::from_millis(1000)).await;
254 }
255 *GAME_STATE.write() = GameState::Running;
256 for _ in 0..20 {
257 relm4::tokio::time::sleep(Duration::from_millis(500)).await;
258 sender.send(false).unwrap();
259 }
260 relm4::tokio::time::sleep(Duration::from_millis(1000)).await;
261 sender.send(true).unwrap();
262 });
263 }
264 AppMsg::StopGame => {
265 *GAME_STATE.write() = GameState::Guessing;
266 }
267 AppMsg::SelectedGuess(index) => {
268 *GAME_STATE.write() = GameState::End(index == self.start_index.take().unwrap());
269 }
270 }
271 }
272
273 fn update_cmd(
274 &mut self,
275 msg: Self::CommandOutput,
276 sender: ComponentSender<Self>,
277 _root: &Self::Root,
278 ) {
279 if msg {
280 sender.input(AppMsg::StopGame);
281 } else {
282 let mut counters_guard = self.counters.guard();
283 match rand::random::<u8>() % 3 {
284 0 => {
285 counters_guard.swap(1, 2);
286 }
287 1 => {
288 counters_guard.swap(0, 1);
289 }
290 _ => {
291 let widget = counters_guard.widget();
292 if !widget.select_next_page() {
293 widget.select_previous_page();
294 }
295 }
296 }
297 }
298 }
299}
300
301fn main() {
302 let app = RelmApp::new("relm4.example.tab_game");
303 app.run::<App>(());
304}