rs_sb3/target.rs
1//! Module to deal with Scratch target
2
3use crate::asset::{Costume, Sound};
4use crate::prelude::*;
5use crate::string_hashmap::StringHashMap;
6use crate::{block::Block, broadcast::Broadcast, comment::Comment, list::List, variable::Variable};
7use utils::json_to_unexpected;
8
9/// A target is the stage or a sprite.
10#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct Target {
13 /// The name of the sprite. Always "Stage" for the stage.
14 /// If not provided, the target will not be loaded.
15 pub name: Text,
16
17 /// An object associating IDs with arrays representing variables.
18 /// The first element of the array is the variable name,
19 /// the second is the value and the third is `true` if the variable is a cloud variable,
20 /// or otherwise not present.
21 pub variables: StringHashMap<Variable>,
22
23 /// An object associating IDs with arrays representing lists.
24 /// The first element of the array is the list name and the second is the list as an array.
25 pub lists: StringHashMap<List>,
26
27 /// An object associating IDs with broadcast names.
28 /// Normally only present in the stage.
29 pub broadcasts: StringHashMap<Broadcast>,
30
31 /// An object associating IDs with blocks.
32 pub blocks: StringHashMap<Block>,
33
34 /// An object associating IDs with comments.
35 pub comments: StringHashMap<Comment>,
36
37 /// The costume number.
38 pub current_costume: Int,
39
40 /// An array of costumes.
41 pub costumes: Vec<Costume>,
42
43 /// An array of sounds.
44 pub sounds: Vec<Sound>,
45
46 /// The layer number.
47 pub layer_order: Int,
48
49 /// The volume
50 pub volume: Number,
51}
52
53/// Scratch's Stage.
54/// Costume is considered backdrop.
55#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
56#[serde(rename_all = "camelCase")]
57pub struct Stage {
58 /// See [`Target`]
59 #[serde(flatten)]
60 pub target: Target,
61
62 /// The tempo in BPM.
63 pub tempo: Number,
64
65 /// See [`VideoState`]
66 pub video_state: VideoState,
67
68 /// The video transparency.
69 /// Defaults to 50. Has no effect if video_stage is "off"
70 /// or if the project does not use an extension with video input.
71 pub video_transparency: Number,
72
73 /// The language of the Text to Speech extension. Defaults to the editor language.
74 // TODO: Create TextToSpeechLangage struct
75 pub text_to_speech_language: Option<Json>,
76
77 /// Always true for stage when serialize
78 /// I wonder what happend if i insert false in here
79 pub is_stage: bool,
80}
81
82/// Scratch Sprite
83#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
84#[serde(rename_all = "camelCase")]
85pub struct Sprite {
86 /// See [`Target`]
87 #[serde(flatten)]
88 pub target: Target,
89
90 /// True if the sprite is visible and false otherwise. Defaults to true.
91 pub visible: bool,
92
93 /// The x-coordinate. Defaults to 0.
94 pub x: Number,
95
96 /// The y-coordinate. Defaults to 0.
97 pub y: Number,
98
99 /// The sprite's size as a percentage. Defaults to 100.
100 pub size: Number,
101
102 /// The sprite's direction in degrees clockwise from up. Defaults to 90.
103 pub direction: Number,
104
105 /// True if the sprite is draggable and false otherwise. Defaults to false.
106 pub draggable: bool,
107
108 /// See [`RotationStyle`]
109 pub rotation_style: RotationStyle,
110
111 /// Always false for sprite
112 /// I wonder what happend if i insert true in here
113 pub is_stage: bool,
114}
115
116#[derive(Debug, Clone, PartialEq, Serialize)]
117#[serde(untagged)]
118pub enum SpriteOrStage {
119 Stage(Stage),
120 Sprite(Sprite),
121}
122
123#[test]
124fn sizeoftest() {
125 dbg!(std::mem::size_of::<Stage>());
126 dbg!(std::mem::size_of::<Sprite>());
127}
128
129/// Determines if video is visible on the stage and if it is flipped.
130/// Has no effect if the project does not use an extension with video input.
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
132pub enum VideoState {
133 /// Video is on
134 #[serde(rename = "on")]
135 On,
136
137 /// Video is off
138 #[serde(rename = "off")]
139 Off,
140
141 /// Video is on and flipped
142 #[serde(rename = "on-flipped")]
143 OnFlipped,
144}
145
146/// A [`Sprite`]'s rotation style controls which directions a sprite can face in.
147/// These directions are all in accordance with the analogous rotation system used by Scratch.
148/// Depending on the rotation style of a sprite, the sprite may appear to be facing a different way than the direction it has been set to.
149#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
150pub enum RotationStyle {
151 /// Visually points the sprite in the direction it is facing.
152 /// However, this will make the sprite appear upside-down if it is facing left.
153 #[serde(rename = "all around")]
154 AllAround,
155
156 /// Flips the sprite right or left.
157 /// If the sprite's direction is between 0° and 180°, the costume will not appear rotated.
158 /// If the sprite's direction is between 0° and -180°, the costume will be mirrored around the y axis.
159 #[serde(rename = "left right")]
160 LeftRight,
161
162 /// Don't rotate
163 ///
164 /// Note: The sprite's visual direction will not change,
165 /// but the direction it moves with the Move () Steps block can still be modified.
166 #[serde(rename = "don't rotate")]
167 DontRotate,
168}
169
170// Serde impl ==================================================================
171
172impl<'de> Deserialize<'de> for SpriteOrStage {
173 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
174 where
175 D: Deserializer<'de>,
176 {
177 use serde::de::Error;
178 let object_j = Json::deserialize(deserializer)?;
179 let object = match &object_j {
180 Json::Object(o) => o,
181 o => {
182 return Err(<D::Error as Error>::invalid_type(
183 json_to_unexpected(o),
184 &"sprite or stage map, tagged with `isStage` key",
185 ))
186 }
187 };
188 let Some(is_stage) = object.get("isStage") else {
189 return Err(
190 <D::Error as Error>::missing_field("isStage")
191 )
192 };
193 let is_stage = match is_stage {
194 &Json::Bool(b) => b,
195 o => {
196 return Err(<D::Error as Error>::invalid_value(
197 json_to_unexpected(o),
198 &"`isStage` key must be the type `bool`",
199 ))
200 }
201 };
202 if is_stage {
203 Ok(SpriteOrStage::Stage(
204 serde_json::from_value(object_j).map_err(<D::Error as Error>::custom)?,
205 ))
206 } else {
207 Ok(SpriteOrStage::Sprite(
208 serde_json::from_value(object_j).map_err(<D::Error as Error>::custom)?,
209 ))
210 }
211 }
212}