1use core::fmt;
9use core::convert::TryFrom;
10use core::str::FromStr;
11
12#[cfg(feature = "snapshot")]
13use serde::{Serialize, Deserialize};
14
15use bitflags::bitflags;
16
17#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum ReadEarMode {
21 Issue3,
23 Issue2,
25 Clear,
27 Set
29}
30
31#[derive(Clone, Debug)]
32pub struct ParseReadEarModeError;
33
34bitflags! {
35 #[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
37 #[cfg_attr(feature = "snapshot", serde(try_from = "u8", into = "u8"))]
38 #[derive(Default)]
39 pub struct EarMic: u8 {
40 const MIC = 0b01;
41 const EAR = 0b10;
42 const EARMIC = 0b11;
43 }
44}
45
46#[derive(Clone, Debug, PartialEq, Eq)]
47pub struct TryFromU8EarMicError(pub u8);
48
49bitflags! {
50 #[derive(Default)]
52 pub struct UlaPortFlags: u8 {
53 const BORDER_MASK = 0b0000_0111;
54 const EAR_MIC_MASK = 0b0001_1000;
55 const MIC_OUT = 0b0000_1000;
56 const EAR_OUT = 0b0001_0000;
57 const KEYBOARD_MASK = 0b0001_1111;
58 const EAR_IN = 0b0100_0000;
59 const UNUSED_MASK = 0b1010_0000;
60 }
61}
62
63bitflags! {
64 #[derive(Default)]
66 pub struct Ula128MemFlags: u8 {
67 const RAM_BANK_MASK = 0b00_0111;
68 const SCREEN_BANK = 0b00_1000;
69 const ROM_BANK = 0b01_0000;
70 const LOCK_MMU = 0b10_0000;
71 }
72}
73
74bitflags! {
75 #[derive(Default)]
77 pub struct Ula3CtrlFlags: u8 {
78 const EXT_PAGING = 0b0_0001;
79 const PAGE_LAYOUT_MASK = 0b0_0110;
80 const ROM_BANK_HI = 0b0_0100;
81 const DISC_MOTOR = 0b0_1000;
82 const PRINTER_STROBE = 0b1_0000;
83 const PAGE_LAYOUT0 = 0b0_0000;
84 const PAGE_LAYOUT1 = 0b0_0010;
85 const PAGE_LAYOUT2 = 0b0_0100;
86 const PAGE_LAYOUT3 = 0b0_0110;
87 }
88}
89
90#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
91#[cfg_attr(feature = "snapshot", serde(try_from = "u8", into = "u8"))]
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93#[repr(u8)]
94pub enum Ula3Paging {
95 Banks0123 = 0,
96 Banks4567 = 1,
97 Banks4563 = 2,
98 Banks4763 = 3,
99}
100
101#[derive(Clone, Debug, PartialEq, Eq)]
102pub struct TryFromU8Ula3PagingError(pub u8);
103
104bitflags! {
105 #[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
107 #[cfg_attr(feature = "snapshot", serde(from = "u8", into = "u8"))]
108 #[derive(Default)]
109 pub struct ScldCtrlFlags: u8 {
110 const SCREEN_MODE_MASK = 0b0000_0111;
111 const SCREEN_SOURCE_MASK = 0b0000_0011;
112 const SCREEN_SECONDARY = 0b0000_0001;
113 const SCREEN_HI_ATTRS = 0b0000_0010;
114 const SCREEN_HI_RES = 0b0000_0100;
115 const HIRES_COLOR_MASK = 0b0011_1000;
116 const INTR_DISABLED = 0b0100_0000;
117 const MAP_EX_ROM = 0b1000_0000;
118 }
119}
120
121bitflags! {
122 #[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
124 #[cfg_attr(feature = "snapshot", serde(from = "u8", into = "u8"))]
125 #[derive(Default)]
126 pub struct UlaPlusRegFlags: u8 {
127 const SCREEN_MODE_MASK = 0b0000_0111;
128 const SCREEN_HI_ATTRS = 0b0000_0010;
129 const SCREEN_HI_RES = 0b0000_0100;
130 const HIRES_COLOR_MASK = 0b0011_1000;
131 const PALETTE_MASK = 0b0011_1111;
132 const GROUP_MASK = 0b1100_0000;
133 const MODE_GROUP = 0b0100_0000;
134 const PALETTE_GROUP = 0b0000_0000;
135 }
136}
137
138bitflags! {
139 #[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
141 #[cfg_attr(feature = "snapshot", serde(try_from = "u8", into = "u8"))]
142 #[derive(Default)]
143 pub struct ColorMode: u8 {
144 const PALETTE = 0b001;
145 const GRAYSCALE = 0b010;
146 }
147}
148
149#[derive(Clone, Debug, PartialEq, Eq)]
150pub struct TryFromU8ColorModeError(pub u8);
151
152impl From<ReadEarMode> for &str {
155 fn from(mode: ReadEarMode) -> Self {
156 match mode {
157 ReadEarMode::Issue3 => "Issue 3",
158 ReadEarMode::Issue2 => "Issue 2",
159 ReadEarMode::Clear => "Clear",
160 ReadEarMode::Set => "Set"
161 }
162 }
163}
164
165impl fmt::Display for ReadEarMode {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 <&str>::from(*self).fmt(f)
168 }
169}
170
171impl std::error::Error for ParseReadEarModeError {}
172
173impl fmt::Display for ParseReadEarModeError {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 write!(f, "cannot parse `ReadEarMode`: unrecognized string")
176 }
177}
178
179impl FromStr for ReadEarMode {
180 type Err = ParseReadEarModeError;
181 fn from_str(mode: &str) -> core::result::Result<Self, Self::Err> {
182 if mode.eq_ignore_ascii_case("issue 3") {
183 Ok(ReadEarMode::Issue3)
184 }
185 else if mode.eq_ignore_ascii_case("issue 2") {
186 Ok(ReadEarMode::Issue2)
187 }
188 else if mode.eq_ignore_ascii_case("clear") {
189 Ok(ReadEarMode::Clear)
190 }
191 else if mode.eq_ignore_ascii_case("set") {
192 Ok(ReadEarMode::Set)
193 }
194 else {
195 Err(ParseReadEarModeError)
196 }
197 }
198}
199
200impl std::error::Error for TryFromU8EarMicError {}
203
204impl fmt::Display for TryFromU8EarMicError {
205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206 write!(f, "converted integer (0x{:x}) contains extraneous bits for `EarMic`", self.0)
207 }
208}
209
210impl TryFrom<u8> for EarMic {
211 type Error = TryFromU8EarMicError;
212 fn try_from(earmic: u8) -> core::result::Result<Self, Self::Error> {
213 EarMic::from_bits(earmic).ok_or(TryFromU8EarMicError(earmic))
214 }
215}
216
217impl From<EarMic> for u8 {
218 fn from(earmic: EarMic) -> u8 {
219 earmic.bits()
220 }
221}
222
223impl From<UlaPortFlags> for EarMic {
226 #[inline]
227 fn from(flags: UlaPortFlags) -> Self {
228 EarMic::from_bits_truncate((flags & UlaPortFlags::EAR_MIC_MASK).bits() >> 3)
229 }
230}
231
232impl Ula128MemFlags {
235 pub fn with_last_ram_page_bank(self, bank: usize) -> Self {
237 (self & !Ula128MemFlags::RAM_BANK_MASK) |
238 (Ula128MemFlags::from_bits_truncate(bank as u8) & Ula128MemFlags::RAM_BANK_MASK)
239 }
240 pub fn last_ram_page_bank(self) -> usize {
242 (self & Ula128MemFlags::RAM_BANK_MASK).bits().into()
243 }
244 pub fn rom_page_bank(self) -> usize {
246 self.intersects(Ula128MemFlags::ROM_BANK).into()
247 }
248 pub fn is_shadow_screen(self) -> bool {
250 self.intersects(Ula128MemFlags::SCREEN_BANK)
251 }
252 pub fn is_mmu_locked(self) -> bool {
254 self.intersects(Ula128MemFlags::LOCK_MMU)
255 }
256}
257
258impl Ula3CtrlFlags {
261 pub fn with_special_paging(self, paging: Ula3Paging) -> Self {
263 ((self | Ula3CtrlFlags::EXT_PAGING) & !Ula3CtrlFlags::PAGE_LAYOUT_MASK)
264 | match paging {
265 Ula3Paging::Banks0123 => Ula3CtrlFlags::PAGE_LAYOUT0,
266 Ula3Paging::Banks4567 => Ula3CtrlFlags::PAGE_LAYOUT1,
267 Ula3Paging::Banks4563 => Ula3CtrlFlags::PAGE_LAYOUT2,
268 Ula3Paging::Banks4763 => Ula3CtrlFlags::PAGE_LAYOUT3,
269 }
270 }
271 pub fn with_rom_page_bank_hi(mut self, rom_bank: usize) -> Self {
274 self.remove(Ula3CtrlFlags::EXT_PAGING);
275 self.set(Ula3CtrlFlags::ROM_BANK_HI, rom_bank & 2 != 0);
276 self
277 }
278 pub fn has_special_paging(self) -> bool {
280 self.intersects(Ula3CtrlFlags::EXT_PAGING)
281 }
282 pub fn special_paging(self) -> Option<Ula3Paging> {
284 if self.has_special_paging() {
285 Some(match self & Ula3CtrlFlags::PAGE_LAYOUT_MASK {
286 Ula3CtrlFlags::PAGE_LAYOUT0 => Ula3Paging::Banks0123,
287 Ula3CtrlFlags::PAGE_LAYOUT1 => Ula3Paging::Banks4567,
288 Ula3CtrlFlags::PAGE_LAYOUT2 => Ula3Paging::Banks4563,
289 Ula3CtrlFlags::PAGE_LAYOUT3 => Ula3Paging::Banks4763,
290 _ => unreachable!()
291 })
292 }
293 else {
294 None
295 }
296 }
297 pub fn rom_page_bank_hi(self) -> usize {
302 ((self & Ula3CtrlFlags::ROM_BANK_HI).bits() >> 1).into()
303 }
304 pub fn is_disc_motor_on(self) -> bool {
306 self.intersects(Ula3CtrlFlags::DISC_MOTOR)
307 }
308 pub fn is_printer_strobe_on(self) -> bool {
310 self.intersects(Ula3CtrlFlags::PRINTER_STROBE)
311 }
312}
313
314impl From<Ula3Paging> for u8 {
317 fn from(paging: Ula3Paging) -> u8 {
318 paging as u8
319 }
320}
321
322impl std::error::Error for TryFromU8Ula3PagingError {}
323
324impl fmt::Display for TryFromU8Ula3PagingError {
325 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326 write!(f, "converted integer ({}) out of range for `Ula3Paging`", self.0)
327 }
328}
329
330impl TryFrom<u8> for Ula3Paging {
331 type Error = TryFromU8Ula3PagingError;
332 fn try_from(bank: u8) -> core::result::Result<Self, Self::Error> {
333 Ok(match bank {
334 0 => Ula3Paging::Banks0123,
335 1 => Ula3Paging::Banks4567,
336 2 => Ula3Paging::Banks4563,
337 3 => Ula3Paging::Banks4763,
338 _ => return Err(TryFromU8Ula3PagingError(bank))
339 })
340 }
341}
342
343impl Ula3Paging {
344 const RAM_BANKS0: [u8;4] = [0, 1, 2, 3];
345 const RAM_BANKS1: [u8;4] = [4, 5, 6, 7];
346 const RAM_BANKS2: [u8;4] = [4, 5, 6, 3];
347 const RAM_BANKS3: [u8;4] = [4, 7, 6, 3];
348 pub fn ram_banks(self) -> &'static [u8] {
350 match self {
351 Ula3Paging::Banks0123 => &Self::RAM_BANKS0,
352 Ula3Paging::Banks4567 => &Self::RAM_BANKS1,
353 Ula3Paging::Banks4563 => &Self::RAM_BANKS2,
354 Ula3Paging::Banks4763 => &Self::RAM_BANKS3,
355 }
356 }
357 pub fn ram_banks_with_pages_iter(self) -> impl Iterator<Item=(usize, u8)> {
360 self.ram_banks().iter().map(|&bank| bank as usize).zip(0..4)
361 }
362}
363
364impl ScldCtrlFlags {
367 #[inline]
369 pub fn into_plus_mode(self) -> UlaPlusRegFlags {
370 UlaPlusRegFlags::from_bits_truncate(
371 (self & (ScldCtrlFlags::SCREEN_MODE_MASK
372 |ScldCtrlFlags::HIRES_COLOR_MASK)
373 ).bits()
374 )|UlaPlusRegFlags::MODE_GROUP
375 }
376 #[inline]
378 pub fn hires_color_index(self) -> u8 {
379 (self & ScldCtrlFlags::HIRES_COLOR_MASK).bits() >> 3
380 }
381 #[inline]
383 pub fn is_map_ex_rom(self) -> bool {
384 self.intersects(ScldCtrlFlags::MAP_EX_ROM)
385 }
386 #[inline]
388 pub fn is_intr_disabled(self) -> bool {
389 self.intersects(ScldCtrlFlags::INTR_DISABLED)
390 }
391 #[inline]
393 pub fn is_screen_hi_res(self) -> bool {
394 self.intersects(ScldCtrlFlags::SCREEN_HI_RES)
395 }
396 #[inline]
398 pub fn is_screen_hi_attrs(self) -> bool {
399 self.intersects(ScldCtrlFlags::SCREEN_HI_ATTRS)
400 }
401 #[inline]
403 pub fn is_screen_secondary(self) -> bool {
404 self.intersects(ScldCtrlFlags::SCREEN_SECONDARY)
405 }
406}
407
408impl From<ScldCtrlFlags> for u8 {
409 #[inline]
410 fn from(flags: ScldCtrlFlags) -> u8 {
411 flags.bits()
412 }
413}
414
415impl From<u8> for ScldCtrlFlags {
416 #[inline]
417 fn from(flags: u8) -> ScldCtrlFlags {
418 ScldCtrlFlags::from_bits_truncate(flags)
419 }
420}
421
422impl UlaPlusRegFlags {
425 pub fn palette_group(self) -> Option<u8> {
427 if (self & UlaPlusRegFlags::GROUP_MASK) == UlaPlusRegFlags::PALETTE_GROUP {
428 return Some((self & UlaPlusRegFlags::PALETTE_MASK).bits())
429 }
430 None
431 }
432
433 pub fn is_mode_group(self) -> bool {
435 (self & UlaPlusRegFlags::GROUP_MASK) == UlaPlusRegFlags::MODE_GROUP
436 }
437 #[inline]
439 pub fn hires_color_index(self) -> u8 {
440 (self & UlaPlusRegFlags::HIRES_COLOR_MASK).bits() >> 3
441 }
442 #[inline]
444 pub fn is_screen_hi_res(self) -> bool {
445 self.intersects(UlaPlusRegFlags::SCREEN_HI_RES)
446 }
447}
448
449impl From<UlaPlusRegFlags> for u8 {
450 #[inline]
451 fn from(flags: UlaPlusRegFlags) -> u8 {
452 flags.bits()
453 }
454}
455
456impl From<u8> for UlaPlusRegFlags {
457 #[inline]
458 fn from(flags: u8) -> UlaPlusRegFlags {
459 UlaPlusRegFlags::from_bits_truncate(flags)
460 }
461}
462
463impl std::error::Error for TryFromU8ColorModeError {}
466
467impl fmt::Display for TryFromU8ColorModeError {
468 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
469 write!(f, "converted integer (0x{:x}) contains extraneous bits for `ColorMode`", self.0)
470 }
471}
472
473impl TryFrom<u8> for ColorMode {
474 type Error = TryFromU8ColorModeError;
475 fn try_from(color_mode: u8) -> core::result::Result<Self, Self::Error> {
476 ColorMode::from_bits(color_mode).ok_or(TryFromU8ColorModeError(color_mode))
477 }
478}
479
480impl From<ColorMode> for u8 {
481 fn from(color_mode: ColorMode) -> u8 {
482 color_mode.bits()
483 }
484}