Skip to main content

rkg_utils/header/mii/
lips.rs

1use std::convert::Infallible;
2
3use crate::byte_handler::{ByteHandlerError, FromByteHandler};
4
5/// Represents the lip customization options of a Mii,
6/// including lip style, color, size, and vertical position.
7#[derive(Clone, Copy)]
8pub struct Lips {
9    /// Vertical position of the lips (0–18).
10    y: u8,
11    /// Lip size (0–8).
12    size: u8,
13    /// Lip shape/style.
14    lips_type: LipsType,
15    /// Lip color.
16    lips_color: LipsColor,
17}
18
19impl Lips {
20    /// Creates a new [`Lips`] from its individual components.
21    ///
22    /// # Arguments
23    ///
24    /// * `y` - Vertical position of the lips (0–18).
25    /// * `size` - Lip size (0–8).
26    /// * `lips_type` - Lip shape/style.
27    /// * `lips_color` - Lip color.
28    ///
29    /// # Errors
30    ///
31    /// Returns [`LipsError::YInvalid`] if `y` exceeds 18.
32    /// Returns [`LipsError::SizeInvalid`] if `size` exceeds 8.
33    pub fn new(
34        y: u8,
35        size: u8,
36        lips_type: LipsType,
37        lips_color: LipsColor,
38    ) -> Result<Self, LipsError> {
39        if y > 18 {
40            return Err(LipsError::YInvalid);
41        }
42        if size > 8 {
43            return Err(LipsError::SizeInvalid);
44        }
45        Ok(Self {
46            y,
47            size,
48            lips_type,
49            lips_color,
50        })
51    }
52
53    /// Returns the vertical position of the lips (0–18).
54    pub fn y(&self) -> u8 {
55        self.y
56    }
57
58    /// Returns the lip size (0–8).
59    pub fn size(&self) -> u8 {
60        self.size
61    }
62
63    /// Returns the lip shape/style.
64    pub fn lips_type(&self) -> LipsType {
65        self.lips_type
66    }
67
68    /// Returns the lip color.
69    pub fn lips_color(&self) -> LipsColor {
70        self.lips_color
71    }
72
73    /// Sets the vertical position of the lips.
74    ///
75    /// # Errors
76    ///
77    /// Returns [`LipsError::YInvalid`] if `y` exceeds 18.
78    pub fn set_y(&mut self, y: u8) -> Result<(), LipsError> {
79        if y > 18 {
80            return Err(LipsError::YInvalid);
81        }
82        self.y = y;
83        Ok(())
84    }
85
86    /// Sets the lip size.
87    ///
88    /// # Errors
89    ///
90    /// Returns [`LipsError::SizeInvalid`] if `size` exceeds 8.
91    pub fn set_size(&mut self, size: u8) -> Result<(), LipsError> {
92        if size > 8 {
93            return Err(LipsError::SizeInvalid);
94        }
95        self.size = size;
96        Ok(())
97    }
98
99    /// Sets the lip shape/style.
100    pub fn set_lips_type(&mut self, lips_type: LipsType) {
101        self.lips_type = lips_type;
102    }
103
104    /// Sets the lip color.
105    pub fn set_lips_color(&mut self, lips_color: LipsColor) {
106        self.lips_color = lips_color;
107    }
108}
109
110/// Deserializes [`Lips`] from a `ByteHandler`.
111///
112/// Extracts and unpacks the lip type, vertical position, color, and size
113/// from the packed Mii binary format using bit shifts and masks.
114impl FromByteHandler for Lips {
115    type Err = LipsError;
116    fn from_byte_handler<T>(handler: T) -> Result<Self, Self::Err>
117    where
118        T: TryInto<crate::byte_handler::ByteHandler>,
119        Self::Err: From<T::Error>,
120    {
121        let mut handler = handler.try_into()?;
122
123        let lips_type =
124            LipsType::try_from(handler.copy_byte(0) >> 3).map_err(|_| LipsError::TypeInvalid)?;
125        let y = handler.copy_byte(1) & 0x1F;
126        handler.shift_right(1);
127        let lips_color = LipsColor::try_from(handler.copy_byte(0) & 0x03)
128            .map_err(|_| LipsError::ColorInvalid)?;
129        let size = handler.copy_byte(1) >> 4;
130
131        Self::new(y, size, lips_type, lips_color)
132    }
133}
134
135/// Errors that can occur while constructing or deserializing [`Lips`].
136#[derive(thiserror::Error, Debug)]
137pub enum LipsError {
138    /// The lip type byte did not map to a known [`LipsType`] variant.
139    #[error("Type is invalid")]
140    TypeInvalid,
141    /// The lip color byte did not map to a known [`LipsColor`] variant.
142    #[error("Color is invalid")]
143    ColorInvalid,
144    /// The size value exceeds the maximum of 8.
145    #[error("Size is invalid")]
146    SizeInvalid,
147    /// The vertical position exceeds the maximum of 18.
148    #[error("Y position is invalid")]
149    YInvalid,
150    /// A `ByteHandler` operation failed.
151    #[error("ByteHandler Error: {0}")]
152    ByteHandlerError(#[from] ByteHandlerError),
153    /// Infallible conversion error; cannot occur at runtime.
154    #[error("")]
155    Infallible(#[from] Infallible),
156}
157
158/// Lip color options available in the Mii editor.
159#[derive(Clone, Copy, PartialEq, Debug)]
160pub enum LipsColor {
161    Orange,
162    Red,
163    Pink,
164}
165
166/// Converts a raw byte value from the Mii data format into a [`LipsColor`].
167///
168/// Returns `Err(())` if the byte does not correspond to any known lip color.
169impl TryFrom<u8> for LipsColor {
170    type Error = ();
171    fn try_from(value: u8) -> Result<Self, Self::Error> {
172        match value {
173            0 => Ok(Self::Orange),
174            1 => Ok(Self::Red),
175            2 => Ok(Self::Pink),
176            _ => Err(()),
177        }
178    }
179}
180
181/// Converts a [`LipsColor`] into its raw byte representation for the Mii data format.
182impl From<LipsColor> for u8 {
183    fn from(value: LipsColor) -> Self {
184        match value {
185            LipsColor::Orange => 0,
186            LipsColor::Red => 1,
187            LipsColor::Pink => 2,
188        }
189    }
190}
191
192/// Lip shape/style options available in the Mii editor.
193#[derive(Clone, Copy, PartialEq, Debug)]
194pub enum LipsType {
195    Neutral,
196    NeutralLips,
197    Smile,
198    SmileStroke,
199    SmileTeeth,
200    LipsSmall,
201    LipsLarge,
202    Wave,
203    WaveAngrySmall,
204    NeutralStrokeLarge,
205    TeethSurprised,
206    LipsExtraLarge,
207    LipsUp,
208    NeutralDown,
209    Surprised,
210    TeethMiddle,
211    NeutralStroke,
212    LipsExtraSmall,
213    Malicious,
214    LipsDual,
215    NeutralComma,
216    NeutralUp,
217    TeethLarge,
218    WaveAngry,
219}
220
221/// Converts a raw byte value from the Mii data format into a [`LipsType`].
222///
223/// Returns `Err(())` if the byte does not correspond to any known lip type.
224impl TryFrom<u8> for LipsType {
225    type Error = ();
226    fn try_from(value: u8) -> Result<Self, Self::Error> {
227        match value {
228            0x17 => Ok(Self::Neutral),
229            0x01 => Ok(Self::NeutralLips),
230            0x13 => Ok(Self::Smile),
231            0x15 => Ok(Self::SmileStroke),
232            0x16 => Ok(Self::SmileTeeth),
233            0x05 => Ok(Self::LipsSmall),
234            0x00 => Ok(Self::LipsLarge),
235            0x08 => Ok(Self::Wave),
236            0x0A => Ok(Self::WaveAngrySmall),
237            0x10 => Ok(Self::NeutralStrokeLarge),
238            0x06 => Ok(Self::TeethSurprised),
239            0x0D => Ok(Self::LipsExtraLarge),
240            0x07 => Ok(Self::LipsUp),
241            0x09 => Ok(Self::NeutralDown),
242            0x02 => Ok(Self::Surprised),
243            0x11 => Ok(Self::TeethMiddle),
244            0x03 => Ok(Self::NeutralStroke),
245            0x04 => Ok(Self::LipsExtraSmall),
246            0x0F => Ok(Self::Malicious),
247            0x0B => Ok(Self::LipsDual),
248            0x14 => Ok(Self::NeutralComma),
249            0x12 => Ok(Self::NeutralUp),
250            0x0E => Ok(Self::TeethLarge),
251            0x0C => Ok(Self::WaveAngry),
252            _ => Err(()),
253        }
254    }
255}
256
257/// Converts a [`LipsType`] into its raw byte representation for the Mii data format.
258impl From<LipsType> for u8 {
259    fn from(value: LipsType) -> Self {
260        match value {
261            LipsType::Neutral => 0x17,
262            LipsType::NeutralLips => 0x01,
263            LipsType::Smile => 0x13,
264            LipsType::SmileStroke => 0x15,
265            LipsType::SmileTeeth => 0x16,
266            LipsType::LipsSmall => 0x05,
267            LipsType::LipsLarge => 0x00,
268            LipsType::Wave => 0x08,
269            LipsType::WaveAngrySmall => 0x0A,
270            LipsType::NeutralStrokeLarge => 0x10,
271            LipsType::TeethSurprised => 0x06,
272            LipsType::LipsExtraLarge => 0x0D,
273            LipsType::LipsUp => 0x07,
274            LipsType::NeutralDown => 0x09,
275            LipsType::Surprised => 0x02,
276            LipsType::TeethMiddle => 0x11,
277            LipsType::NeutralStroke => 0x03,
278            LipsType::LipsExtraSmall => 0x04,
279            LipsType::Malicious => 0x0F,
280            LipsType::LipsDual => 0x0B,
281            LipsType::NeutralComma => 0x14,
282            LipsType::NeutralUp => 0x12,
283            LipsType::TeethLarge => 0x0E,
284            LipsType::WaveAngry => 0x0C,
285        }
286    }
287}