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}