Skip to main content

rkg_utils/header/mii/
eyebrows.rs

1use std::convert::Infallible;
2
3use crate::{
4    byte_handler::{ByteHandlerError, FromByteHandler},
5    header::mii::hair::HairColor,
6};
7
8/// Represents the eyebrow customization options of a Mii.
9///
10/// All positional and size values are validated against the ranges permitted
11/// by the Mii data format on construction.
12#[derive(Clone, Copy)]
13pub struct Eyebrows {
14    /// Eyebrow rotation (0–11).
15    rotation: u8,
16    /// Eyebrow size (0–8).
17    size: u8,
18    /// Horizontal position of the eyebrows (0–12).
19    x: u8,
20    /// Vertical position of the eyebrows (3–18).
21    y: u8,
22    /// Eyebrow color, shared with the hair color palette.
23    eyebrow_color: HairColor,
24    /// Eyebrow shape/style.
25    eyebrow_type: EyebrowType,
26}
27
28impl Eyebrows {
29    /// Creates a new [`Eyebrows`] from its individual components.
30    ///
31    /// # Arguments
32    ///
33    /// * `rotation` - Eyebrow rotation (0–11).
34    /// * `size` - Eyebrow size (0–8).
35    /// * `x` - Horizontal position (0–12).
36    /// * `y` - Vertical position (3–18).
37    /// * `eyebrow_color` - Eyebrow color from the [`HairColor`] palette.
38    /// * `eyebrow_type` - Eyebrow shape/style.
39    ///
40    /// # Errors
41    ///
42    /// Returns [`EyebrowsError::RotationInvalid`] if `rotation` exceeds 11.
43    /// Returns [`EyebrowsError::SizeInvalid`] if `size` exceeds 8.
44    /// Returns [`EyebrowsError::XInvalid`] if `x` exceeds 12.
45    /// Returns [`EyebrowsError::YInvalid`] if `y` is outside the range 3–18.
46    pub fn new(
47        rotation: u8,
48        size: u8,
49        x: u8,
50        y: u8,
51        eyebrow_color: HairColor,
52        eyebrow_type: EyebrowType,
53    ) -> Result<Self, EyebrowsError> {
54        if rotation > 11 {
55            return Err(EyebrowsError::RotationInvalid);
56        }
57        if size > 8 {
58            return Err(EyebrowsError::SizeInvalid);
59        }
60        if x > 12 {
61            return Err(EyebrowsError::XInvalid);
62        }
63        if !(3..=18).contains(&y) {
64            return Err(EyebrowsError::YInvalid);
65        }
66
67        Ok(Self {
68            rotation,
69            size,
70            x,
71            y,
72            eyebrow_color,
73            eyebrow_type,
74        })
75    }
76
77    /// Returns the eyebrow rotation (0–11).
78    pub fn rotation(&self) -> u8 {
79        self.rotation
80    }
81
82    /// Returns the eyebrow size (0–8).
83    pub fn size(&self) -> u8 {
84        self.size
85    }
86
87    /// Returns the horizontal position of the eyebrows (0–12).
88    pub fn x(&self) -> u8 {
89        self.x
90    }
91
92    /// Returns the vertical position of the eyebrows (3–18).
93    pub fn y(&self) -> u8 {
94        self.y
95    }
96
97    /// Returns the eyebrow color.
98    pub fn eyebrow_color(&self) -> HairColor {
99        self.eyebrow_color
100    }
101
102    /// Returns the eyebrow shape/style.
103    pub fn eyebrow_type(&self) -> EyebrowType {
104        self.eyebrow_type
105    }
106
107    /// Sets the eyebrow rotation.
108    ///
109    /// # Errors
110    ///
111    /// Returns [`EyebrowsError::RotationInvalid`] if `rotation` exceeds 11.
112    pub fn set_rotation(&mut self, rotation: u8) -> Result<(), EyebrowsError> {
113        if rotation > 11 {
114            return Err(EyebrowsError::RotationInvalid);
115        }
116        self.rotation = rotation;
117        Ok(())
118    }
119
120    /// Sets the eyebrow size.
121    ///
122    /// # Errors
123    ///
124    /// Returns [`EyebrowsError::SizeInvalid`] if `size` exceeds 8.
125    pub fn set_size(&mut self, size: u8) -> Result<(), EyebrowsError> {
126        if size > 8 {
127            return Err(EyebrowsError::SizeInvalid);
128        }
129        self.size = size;
130        Ok(())
131    }
132
133    /// Sets the horizontal position of the eyebrows.
134    ///
135    /// # Errors
136    ///
137    /// Returns [`EyebrowsError::XInvalid`] if `x` exceeds 12.
138    pub fn set_x(&mut self, x: u8) -> Result<(), EyebrowsError> {
139        if x > 12 {
140            return Err(EyebrowsError::XInvalid);
141        }
142        self.x = x;
143        Ok(())
144    }
145
146    /// Sets the vertical position of the eyebrows.
147    ///
148    /// # Errors
149    ///
150    /// Returns [`EyebrowsError::YInvalid`] if `y` is outside the range 3–18.
151    pub fn set_y(&mut self, y: u8) -> Result<(), EyebrowsError> {
152        if !(3..=18).contains(&y) {
153            return Err(EyebrowsError::YInvalid);
154        }
155        self.y = y;
156        Ok(())
157    }
158
159    /// Sets the eyebrow color.
160    pub fn set_eyebrow_color(&mut self, eyebrow_color: HairColor) {
161        self.eyebrow_color = eyebrow_color;
162    }
163
164    /// Sets the eyebrow shape/style.
165    pub fn set_eyebrow_type(&mut self, eyebrow_type: EyebrowType) {
166        self.eyebrow_type = eyebrow_type;
167    }
168}
169
170/// Errors that can occur while constructing or deserializing [`Eyebrows`].
171#[derive(thiserror::Error, Debug)]
172pub enum EyebrowsError {
173    /// The eyebrow type byte did not map to a known [`EyebrowType`] variant.
174    #[error("Type is invalid")]
175    TypeInvalid,
176    /// The eyebrow color byte did not map to a known [`HairColor`] variant.
177    #[error("Color is invalid")]
178    ColorInvalid,
179    /// The rotation value exceeds the maximum of 11.
180    #[error("Rotation is invalid")]
181    RotationInvalid,
182    /// The size value exceeds the maximum of 8.
183    #[error("Size is invalid")]
184    SizeInvalid,
185    /// The vertical position is outside the valid range (3–18).
186    #[error("Y position is invalid")]
187    YInvalid,
188    /// The horizontal position exceeds the maximum of 12.
189    #[error("X position is invalid")]
190    XInvalid,
191    /// A `ByteHandler` operation failed.
192    #[error("ByteHandler Error: {0}")]
193    ByteHandlerError(#[from] ByteHandlerError),
194    /// Infallible conversion error; cannot occur at runtime.
195    #[error("")]
196    Infallible(#[from] Infallible),
197}
198
199/// Deserializes [`Eyebrows`] from a `ByteHandler`.
200///
201/// Extracts and unpacks the eyebrow type, color, horizontal position, vertical position,
202/// size, and rotation from the packed Mii binary format using a series of bit shifts and masks.
203impl FromByteHandler for Eyebrows {
204    type Err = EyebrowsError;
205    fn from_byte_handler<T>(handler: T) -> Result<Self, Self::Err>
206    where
207        T: TryInto<crate::byte_handler::ByteHandler>,
208        Self::Err: From<T::Error>,
209    {
210        let mut handler = handler.try_into()?;
211
212        let x = handler.copy_byte(3) & 0x0F;
213        let eyebrow_type = EyebrowType::try_from(handler.copy_byte(0) >> 3)
214            .map_err(|_| EyebrowsError::TypeInvalid)?;
215        let eyebrow_color = HairColor::try_from(handler.copy_byte(2) >> 5)
216            .map_err(|_| EyebrowsError::ColorInvalid)?;
217        handler.shift_right(1);
218        let y = handler.copy_byte(3) >> 3;
219        let size = handler.copy_byte(2) & 0x0F;
220        handler.shift_right(2);
221        let rotation = handler.copy_byte(1) >> 3;
222        Self::new(rotation, size, x, y, eyebrow_color, eyebrow_type)
223    }
224}
225
226/// All eyebrow shapes available in the Mii editor.
227#[derive(Clone, Copy, Debug, PartialEq)]
228pub enum EyebrowType {
229    FlatAngledLarge,
230    LowArchRoundedThin,
231    SoftAngledLarge,
232    MediumArchRoundedThin,
233    RoundedMedium,
234    LowArchMedium,
235    RoundedThin,
236    UpThin,
237    MediumArchRoundedMedium,
238    RoundedLarge,
239    UpLarge,
240    FlatAngledLargeInverted,
241    MediumArchFlat,
242    AngledThin,
243    HorizontalLarge,
244    HighArchFlat,
245    Flat,
246    MediumArchLarge,
247    LowArchThin,
248    RoundedThinInverted,
249    HighArchLarge,
250    Hairy,
251    Dotted,
252    None,
253}
254
255/// Converts a raw byte value from the Mii data format into an [`EyebrowType`].
256///
257/// Returns `Err(())` if the byte does not correspond to any known eyebrow type.
258impl TryFrom<u8> for EyebrowType {
259    type Error = ();
260    fn try_from(value: u8) -> Result<Self, Self::Error> {
261        match value {
262            0x06 => Ok(Self::FlatAngledLarge),
263            0x00 => Ok(Self::LowArchRoundedThin),
264            0x0C => Ok(Self::SoftAngledLarge),
265            0x01 => Ok(Self::MediumArchRoundedThin),
266            0x09 => Ok(Self::RoundedMedium),
267            0x13 => Ok(Self::LowArchMedium),
268            0x07 => Ok(Self::RoundedThin),
269            0x15 => Ok(Self::UpThin),
270            0x08 => Ok(Self::MediumArchRoundedMedium),
271            0x11 => Ok(Self::RoundedLarge),
272            0x05 => Ok(Self::UpLarge),
273            0x04 => Ok(Self::FlatAngledLargeInverted),
274            0x0B => Ok(Self::MediumArchFlat),
275            0x0A => Ok(Self::AngledThin),
276            0x02 => Ok(Self::HorizontalLarge),
277            0x03 => Ok(Self::HighArchFlat),
278            0x0E => Ok(Self::Flat),
279            0x14 => Ok(Self::MediumArchLarge),
280            0x0F => Ok(Self::LowArchThin),
281            0x0D => Ok(Self::RoundedThinInverted),
282            0x16 => Ok(Self::HighArchLarge),
283            0x12 => Ok(Self::Hairy),
284            0x10 => Ok(Self::Dotted),
285            0x17 => Ok(Self::None),
286            _ => Err(()),
287        }
288    }
289}
290
291/// Converts an [`EyebrowType`] into its raw byte representation for the Mii data format.
292impl From<EyebrowType> for u8 {
293    fn from(value: EyebrowType) -> Self {
294        match value {
295            EyebrowType::FlatAngledLarge => 0x06,
296            EyebrowType::LowArchRoundedThin => 0x00,
297            EyebrowType::SoftAngledLarge => 0x0C,
298            EyebrowType::MediumArchRoundedThin => 0x01,
299            EyebrowType::RoundedMedium => 0x09,
300            EyebrowType::LowArchMedium => 0x13,
301            EyebrowType::RoundedThin => 0x07,
302            EyebrowType::UpThin => 0x15,
303            EyebrowType::MediumArchRoundedMedium => 0x08,
304            EyebrowType::RoundedLarge => 0x11,
305            EyebrowType::UpLarge => 0x05,
306            EyebrowType::FlatAngledLargeInverted => 0x04,
307            EyebrowType::MediumArchFlat => 0x0B,
308            EyebrowType::AngledThin => 0x0A,
309            EyebrowType::HorizontalLarge => 0x02,
310            EyebrowType::HighArchFlat => 0x03,
311            EyebrowType::Flat => 0x0E,
312            EyebrowType::MediumArchLarge => 0x14,
313            EyebrowType::LowArchThin => 0x0F,
314            EyebrowType::RoundedThinInverted => 0x0D,
315            EyebrowType::HighArchLarge => 0x16,
316            EyebrowType::Hairy => 0x12,
317            EyebrowType::Dotted => 0x10,
318            EyebrowType::None => 0x17,
319        }
320    }
321}