spectrusty_peripherals/mouse/
kempston.rs

1/*
2    Copyright (C) 2020-2022  Rafal Michalski
3
4    This file is part of SPECTRUSTY, a Rust library for building emulators.
5
6    For the full copyright notice, see the lib.rs file.
7*/
8//! A Kempston Mouse device implementation.
9#[cfg(feature = "snapshot")]
10use serde::{Serialize, Deserialize};
11
12use super::{MouseInterface, MouseDevice, MouseMovement, MouseButtons};
13/*
14    Horizontal position: IN 64479
15    Vertical postition: IN 65503
16    Buttons: IN 64223 [255 = None], [254 = Right], [253 = Left], [252 = Both]
17*/
18const RIGHT_BTN_MASK:  u8 = 0b0000_0001;
19const LEFT_BTN_MASK:   u8 = 0b0000_0010;
20const MIDDLE_BTN_MASK: u8 = 0b0000_0100; // extension
21const UNUSED_BTN_MASK: u8 = !(MIDDLE_BTN_MASK|LEFT_BTN_MASK|RIGHT_BTN_MASK);
22
23const PORT_BTN_MASK: u16 = 0b0000_0001_0000_0000;
24const PORT_BTN_BITS: u16 = 0b0000_0000_0000_0000;
25const PORT_POS_MASK: u16 = 0b0000_0101_0000_0000;
26const PORT_X_BITS:   u16 = 0b0000_0001_0000_0000;
27const PORT_Y_BITS:   u16 = 0b0000_0101_0000_0000;
28
29/// The Kempston Mouse device implements [MouseDevice] and [MouseInterface] traits.
30///
31/// The horizontal position increases when moving to the right and decreases when moving to the left.
32/// The vertical position increases when moving from bottom to the top or forward (away from the user)
33/// and decreases then moving backward (towards the user).
34///
35/// A horizontal position is being provided when bits of the port address: A8 is 1 and A10 is 0.
36/// A vertical position is being provided when bits of the port address: A8 and A10 is 1.
37/// A button state is being provided when A8 bit of the port address is 0:
38///
39/// * bit 0 is 0 when the left button is being pressed and 1 when the left button is released.
40/// * bit 1 is 0 when the right button is being pressed and 1 when the right button is released.
41#[derive(Clone, Copy, Debug)]
42#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
43#[cfg_attr(feature = "snapshot", serde(rename_all = "camelCase"))]
44pub struct KempstonMouseDevice {
45    #[cfg_attr(feature = "snapshot", serde(skip, default = "u8::max_value"))]
46    data_btn: u8,
47    data_x: u8,
48    data_y: u8,
49    #[cfg_attr(feature = "snapshot", serde(skip))]
50    buttons: MouseButtons,
51}
52
53impl Default for KempstonMouseDevice {
54    fn default() -> Self {
55        KempstonMouseDevice {
56            data_btn: !0,
57            data_x: !0,
58            data_y: !0,
59            buttons: Default::default(),
60        }
61    }
62}
63
64impl MouseDevice for KempstonMouseDevice {
65    #[inline]
66    fn port_read(&self, port: u16) -> u8 {
67        if port & PORT_BTN_MASK == PORT_BTN_BITS {
68            self.data_btn
69        }
70        else {
71            match port & PORT_POS_MASK {
72                PORT_X_BITS => self.data_x,
73                PORT_Y_BITS => self.data_y,
74                _ => unsafe { core::hint::unreachable_unchecked() }
75            }
76        }
77    }
78}
79
80impl MouseInterface for KempstonMouseDevice {
81    #[inline]
82    fn set_buttons(&mut self, buttons: MouseButtons) {
83        self.buttons = buttons;
84        self.data_btn = (self.data_btn & UNUSED_BTN_MASK) |
85            if buttons.intersects(MouseButtons::RIGHT)  { 0 } else { RIGHT_BTN_MASK } |
86            if buttons.intersects(MouseButtons::LEFT)   { 0 } else { LEFT_BTN_MASK  } |
87            if buttons.intersects(MouseButtons::MIDDLE) { 0 } else { MIDDLE_BTN_MASK };
88    }
89    #[inline]
90    fn get_buttons(&self) -> MouseButtons {
91        self.buttons
92    }
93    #[inline]
94    fn move_mouse(&mut self, movement: MouseMovement) {
95        self.data_x = clamped_move(self.data_x, movement.horizontal);
96        self.data_y = clamped_move(self.data_y, -movement.vertical);
97    }
98}
99
100#[inline(always)]
101fn clamped_move(prev: u8, mut delta: i16) -> u8 {
102    delta >>= 1;
103    if delta < i8::min_value().into() {
104        delta = i8::min_value().into();
105    }
106    else if delta > i8::max_value().into() {
107        delta = i8::max_value().into();
108    }
109    prev.wrapping_add(delta as u8)
110}