resource-fork-types 0.1.2

Support for reading common resource fork types in rust
Documentation
use binrw::{binread, BinRead, BinReaderExt};
use macintosh_utils::PascalString;
use num_enum::{FromPrimitive, IntoPrimitive};
use resource_fork::Resource;

use crate::{
    common::{long_list_plus_one, LengthPrefixedBuffer, Rect},
    Clut,
};

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct UnknownDialogItem {
    pub bounds: Rect,
    pub unknown: [u8; 4],
    pub enabled: bool,
    pub item_type: u8,
    pub payload: Vec<u8>,
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct UserItem {
    pub bounds: Rect,
    pub unknown: [u8; 4],
    pub enabled: bool,
    pub payload: Vec<u8>,
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HelpItem {
    pub bounds: Rect,
    pub unknown: [u8; 4],
    pub enabled: bool,
    pub payload: Vec<u8>,
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Button {
    pub bounds: Rect,
    pub unknown: [u8; 4],
    pub enabled: bool,
    pub label: String,
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CheckBox {
    pub bounds: Rect,
    pub unknown: [u8; 4],
    pub enabled: bool,
    pub label: String,
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RadioButton {
    pub bounds: Rect,
    pub unknown: [u8; 4],
    pub enabled: bool,
    pub label: String,
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct StaticText {
    pub bounds: Rect,
    pub unknown: [u8; 4],
    pub enabled: bool,
    pub label: String,
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TextField {
    pub bounds: Rect,
    pub unknown: [u8; 4],
    pub enabled: bool,
    pub label: String,
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Control {
    pub bounds: Rect,
    pub unknown: [u8; 4],
    pub enabled: bool,
    pub payload: Vec<u8>,
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Icon {
    pub bounds: Rect,
    pub unknown: [u8; 4],
    pub enabled: bool,
    pub resource: u16,
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Picture {
    pub bounds: Rect,
    pub unknown: [u8; 4],
    pub enabled: bool,
    pub resource: u16,
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DialogItem {
    Unknown(UnknownDialogItem),
    UserItem(UserItem),
    HelpItem(HelpItem),
    Button(Button),
    CheckBox(CheckBox),
    RadioButton(RadioButton),
    Control(Control),
    StaticText(StaticText),
    TextField(TextField),
    Icon(Icon),
    Picture(Picture),
}

impl DialogItem {
    pub fn enabled(&self) -> bool {
        match self {
            DialogItem::Unknown(item) => item.enabled,
            DialogItem::UserItem(item) => item.enabled,
            DialogItem::HelpItem(item) => item.enabled,
            DialogItem::Button(item) => item.enabled,
            DialogItem::CheckBox(item) => item.enabled,
            DialogItem::RadioButton(item) => item.enabled,
            DialogItem::Control(item) => item.enabled,
            DialogItem::StaticText(item) => item.enabled,
            DialogItem::TextField(item) => item.enabled,
            DialogItem::Icon(item) => item.enabled,
            DialogItem::Picture(item) => item.enabled,
        }
    }

    pub fn bounds(&self) -> Rect {
        match self {
            DialogItem::Unknown(item) => item.bounds,
            DialogItem::UserItem(item) => item.bounds,
            DialogItem::HelpItem(item) => item.bounds,
            DialogItem::Button(item) => item.bounds,
            DialogItem::CheckBox(item) => item.bounds,
            DialogItem::RadioButton(item) => item.bounds,
            DialogItem::Control(item) => item.bounds,
            DialogItem::StaticText(item) => item.bounds,
            DialogItem::TextField(item) => item.bounds,
            DialogItem::Icon(item) => item.bounds,
            DialogItem::Picture(item) => item.bounds,
        }
    }
}

#[derive(IntoPrimitive, FromPrimitive)]
#[repr(u8)]
enum ItemType {
    UserItem = 0,
    HelpItem = 1,
    Button = 4,
    CheckBox = 5,
    RadioButton = 6,
    Control = 7,
    StaticText = 8,
    TextField = 16,
    Icon = 32,
    Picture = 64,
    #[num_enum(catch_all)]
    Unknown(u8),
}

impl BinRead for DialogItem {
    type Args<'a> = ();

    fn read_options<R: std::io::Read + std::io::Seek>(
        reader: &mut R,
        _: binrw::Endian,
        _: Self::Args<'_>,
    ) -> binrw::BinResult<Self> {
        let unknown: [u8; 4] = reader.read_be()?;
        let bounds: Rect = reader.read_be()?;
        let enabled_and_type: u8 = reader.read_be()?;
        let enabled = enabled_and_type & 0x80 != 0;
        let item_type = enabled_and_type & !0x80;
        let item_type = ItemType::from(item_type);

        let result = match item_type {
            ItemType::Button => Ok(Self::Button(Button {
                bounds,
                enabled,
                unknown,
                label: reader.read_be::<PascalString>()?.into(),
            })),
            ItemType::StaticText => Ok(Self::StaticText(StaticText {
                bounds,
                enabled,
                unknown,
                label: reader.read_be::<PascalString>()?.into(),
            })),
            ItemType::TextField => Ok(Self::TextField(TextField {
                bounds,
                enabled,
                unknown,
                label: reader.read_be::<PascalString>()?.into(),
            })),
            ItemType::Control => Ok(Self::Control(Control {
                bounds,
                enabled,
                unknown,
                payload: reader.read_be::<LengthPrefixedBuffer>()?.into(),
            })),
            ItemType::UserItem => Ok(Self::UserItem(UserItem {
                bounds,
                enabled,
                unknown,
                payload: reader.read_be::<LengthPrefixedBuffer>()?.into(),
            })),
            ItemType::HelpItem => Ok(Self::HelpItem(HelpItem {
                bounds,
                enabled,
                unknown,
                payload: reader.read_be::<LengthPrefixedBuffer>()?.into(),
            })),
            ItemType::CheckBox => Ok(Self::CheckBox(CheckBox {
                bounds,
                enabled,
                unknown,
                label: reader.read_be::<PascalString>()?.into(),
            })),
            ItemType::RadioButton => Ok(Self::RadioButton(RadioButton {
                bounds,
                enabled,
                unknown,
                label: reader.read_be::<PascalString>()?.into(),
            })),
            ItemType::Icon => Ok(Self::Icon(Icon {
                bounds,
                enabled,
                unknown,
                resource: reader.read_be::<ResourceRef>()?.into(),
            })),
            ItemType::Picture => Ok(Self::Picture(Picture {
                bounds,
                enabled,
                unknown,
                resource: reader.read_be::<ResourceRef>()?.into(),
            })),
            ItemType::Unknown(item_type) => Ok(Self::Unknown(UnknownDialogItem {
                bounds,
                enabled,
                item_type,
                unknown,
                payload: reader.read_be::<LengthPrefixedBuffer>()?.into(),
            })),
        };

        if reader.stream_position()? % 2 != 0 {
            let _ = reader.seek(std::io::SeekFrom::Current(1))?;
        }

        result
    }
}

#[binread]
#[derive(Resource, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[resource(code = "DITL")]
#[br(big)]
pub struct DialogItemList {
    #[br(map(long_list_plus_one))]
    items: Vec<DialogItem>,
}

impl From<DialogItemList> for Vec<DialogItem> {
    fn from(val: DialogItemList) -> Self {
        val.items
    }
}

#[binread]
struct ResourceRef {
    #[br(temp, assert(size==2))]
    size: u8,
    reference: u16,
}

impl From<ResourceRef> for u16 {
    fn from(val: ResourceRef) -> Self {
        val.reference
    }
}

#[derive(Resource, BinRead)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[resource(code = "ictb")]
#[br(big)]
/// See <https://dev.os9.ca/techpubs/new/DialogMgr8Ref/DialogMgrRef.9.html#pgfId=196774>
pub struct ItemColorTable(Clut);