shared/domain/module/body/
find_answer.rs1use crate::domain::module::{
2 body::{
3 Body, BodyConvert, BodyExt, ModeExt, StepExt, ThemeId,
4 _groups::design::{BaseContent, Trace},
5 },
6 ModuleKind,
7};
8use serde::{Deserialize, Serialize};
9use std::collections::HashSet;
10
11mod play_settings;
12pub use play_settings::*;
13
14use super::{
15 Audio, Transform,
16 _groups::design::{Sticker, Text},
17};
18
19#[derive(Default, Clone, Serialize, Deserialize, Debug)]
21pub struct ModuleData {
22 pub content: Option<Content>,
24}
25
26impl BodyExt<Mode, Step> for ModuleData {
27 fn as_body(&self) -> Body {
28 Body::FindAnswer(self.clone())
29 }
30
31 fn is_complete(&self) -> bool {
32 self.content
33 .as_ref()
34 .map_or(false, |content| content.is_valid())
35 }
36
37 fn kind() -> ModuleKind {
38 ModuleKind::FindAnswer
39 }
40
41 fn new_with_mode_and_theme(mode: Mode, theme: ThemeId) -> Self {
42 let mut base = BaseContent::default();
43 base.theme = theme;
44
45 let mut sticker_content = Text::from_value(format!(
46 r#"{{"version":"0.1.0","content":[{{"children":[{{"text":"{}","element":"P2"}}]}}]}}"#,
47 "Questions appear here / שאלות מופיעות כאן"
48 ));
49 sticker_content.transform.translation.0[1] = -0.35; base.stickers.push(Sticker::Text(sticker_content));
51 let question_field_index = base.stickers.len() - 1;
52
53 Self {
54 content: Some(Content {
55 mode,
56 base,
57 question_field: QuestionField::Text(question_field_index),
58 ..Default::default()
59 }),
60 }
61 }
62
63 fn mode(&self) -> Option<Mode> {
64 self.content.as_ref().map(|c| c.mode.clone())
65 }
66
67 fn requires_choose_mode(&self) -> bool {
68 self.content.is_none()
69 }
70
71 fn set_editor_state_step(&mut self, step: Step) {
72 if let Some(content) = self.content.as_mut() {
73 content.editor_state.step = step;
74 }
75 }
76 fn set_editor_state_steps_completed(&mut self, steps_completed: HashSet<Step>) {
77 if let Some(content) = self.content.as_mut() {
78 content.editor_state.steps_completed = steps_completed;
79 }
80 }
81
82 fn get_editor_state_step(&self) -> Option<Step> {
83 self.content
84 .as_ref()
85 .map(|content| content.editor_state.step)
86 }
87
88 fn get_editor_state_steps_completed(&self) -> Option<HashSet<Step>> {
89 self.content
90 .as_ref()
91 .map(|content| content.editor_state.steps_completed.clone())
92 }
93
94 fn set_theme(&mut self, theme_id: ThemeId) {
95 if let Some(content) = self.content.as_mut() {
96 content.base.theme = theme_id;
97 }
98 }
99
100 fn get_theme(&self) -> Option<ThemeId> {
101 self.content.as_ref().map(|content| content.base.theme)
102 }
103}
104
105impl BodyConvert for ModuleData {}
106
107impl TryFrom<Body> for ModuleData {
108 type Error = &'static str;
109
110 fn try_from(body: Body) -> Result<Self, Self::Error> {
111 match body {
112 Body::FindAnswer(data) => Ok(data),
113 _ => Err("cannot convert body to Answer This!"),
114 }
115 }
116}
117
118#[derive(Default, Clone, Serialize, Deserialize, Debug)]
120pub struct Content {
121 pub base: BaseContent,
123
124 pub editor_state: EditorState,
126
127 pub mode: Mode,
129
130 pub questions: Vec<Question>,
132
133 pub question_field: QuestionField,
135
136 pub play_settings: PlaySettings,
138}
139
140impl Content {
141 pub fn is_valid(&self) -> bool {
143 !self.questions.is_empty()
144 && self
145 .questions
146 .iter()
147 .find(|question| !question.is_valid())
148 .is_none()
149 }
150}
151
152#[derive(Clone, Serialize, Deserialize, Debug)]
154pub enum QuestionField {
155 Text(usize),
157 Dynamic(Option<Transform>),
163}
164
165impl QuestionField {
166 pub fn is_dynamic(&self) -> bool {
168 matches!(self, Self::Dynamic(_))
169 }
170
171 pub fn is_text(&self) -> bool {
173 matches!(self, Self::Text(_))
174 }
175}
176
177impl Default for QuestionField {
178 fn default() -> Self {
179 QuestionField::Dynamic(None)
180 }
181}
182
183#[derive(Default, Clone, Serialize, Deserialize, Debug)]
185pub struct Question {
186 pub title: String,
188
189 pub question_text: String,
191
192 pub question_audio: Option<Audio>,
194
195 pub incorrect_audio: Option<Audio>,
197
198 #[serde(default)]
199 pub correct_audio: Option<Audio>,
201
202 pub traces: Vec<Trace>,
204}
205
206impl Question {
207 pub fn is_valid(&self) -> bool {
209 !self.traces.is_empty()
210 }
211}
212
213#[derive(Default, Clone, Serialize, Deserialize, Debug)]
215pub struct EditorState {
216 pub step: Step,
218
219 pub steps_completed: HashSet<Step>,
221}
222
223#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
224pub enum Mode {
226 Family,
228 Map,
230 MultipleChoice,
232 Scene,
234 Text,
236 Differences,
238}
239
240impl Default for Mode {
241 fn default() -> Self {
242 Self::MultipleChoice
243 }
244}
245
246impl ModeExt for Mode {
247 fn get_list() -> Vec<Self> {
248 vec![
249 Self::Scene,
250 Self::MultipleChoice,
251 Self::Text,
252 Self::Differences,
253 Self::Map,
254 Self::Family,
255 ]
256 }
257
258 fn as_str_id(&self) -> &'static str {
259 match self {
260 Self::Family => "family",
261 Self::Map => "map",
262 Self::MultipleChoice => "multiple-choice",
263 Self::Scene => "scene",
264 Self::Text => "text",
265 Self::Differences => "differences",
266 }
267 }
268
269 fn label(&self) -> &'static str {
270 const STR_FAMILY_LABEL: &'static str = "Family Tree";
271 const STR_MAP_LABEL: &'static str = "Where on the Map?";
272 const STR_MULTIPLE_CHOICE_LABEL: &'static str = "Find the Answers";
273 const STR_SCENE_LABEL: &'static str = "Spot It";
274 const STR_TEXT_LABEL: &'static str = "Find the Text";
275 const STR_DIFFERENCES_LABEL: &'static str = "Find the Differences";
276
277 match self {
278 Self::Family => STR_FAMILY_LABEL,
279 Self::Map => STR_MAP_LABEL,
280 Self::MultipleChoice => STR_MULTIPLE_CHOICE_LABEL,
281 Self::Scene => STR_SCENE_LABEL,
282 Self::Text => STR_TEXT_LABEL,
283 Self::Differences => STR_DIFFERENCES_LABEL,
284 }
285 }
286}
287
288#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
290pub enum Step {
291 One,
293 Two,
295 Three,
297 Four,
299 Five,
301}
302
303impl Default for Step {
304 fn default() -> Self {
305 Self::One
306 }
307}
308
309impl StepExt for Step {
310 fn next(&self) -> Option<Self> {
311 match self {
312 Self::One => Some(Self::Two),
313 Self::Two => Some(Self::Three),
314 Self::Three => Some(Self::Four),
315 Self::Four => Some(Self::Five),
316 Self::Five => None,
317 }
318 }
319
320 fn as_number(&self) -> usize {
321 match self {
322 Self::One => 1,
323 Self::Two => 2,
324 Self::Three => 3,
325 Self::Four => 4,
326 Self::Five => 5,
327 }
328 }
329
330 fn label(&self) -> &'static str {
331 const STR_BACKGROUND: &'static str = "Design";
332 const STR_CONTENT: &'static str = "Content";
333 const STR_INTERACTION: &'static str = "Interaction";
334 const STR_SETTINGS: &'static str = "Settings";
335 const STR_PREVIEW: &'static str = "Preview";
336 match self {
337 Self::One => STR_BACKGROUND,
338 Self::Two => STR_CONTENT,
339 Self::Three => STR_INTERACTION,
340 Self::Four => STR_SETTINGS,
341 Self::Five => STR_PREVIEW,
342 }
343 }
344
345 fn get_list() -> Vec<Self> {
346 vec![Self::One, Self::Two, Self::Three, Self::Four, Self::Five]
347 }
348 fn get_preview() -> Self {
349 Self::Five
350 }
351}