openMSX
Trackball.cc
Go to the documentation of this file.
1 #include "Trackball.hh"
2 #include "MSXEventDistributor.hh"
4 #include "InputEvents.hh"
5 #include "StateChange.hh"
6 #include "checked_cast.hh"
7 #include "serialize.hh"
8 #include "serialize_meta.hh"
9 #include <algorithm>
10 
11 // * Implementation based on information we received from 'n_n'.
12 // It might not be 100% accurate. But games like 'Hole in one' already work.
13 // * Initially the 'trackball detection' code didn't work properly in openMSX
14 // while it did work in meisei. Meisei had some special cases implemented for
15 // the first read after reset. After some investigation I figured out some
16 // code without special cases that also works as expected. Most software
17 // seems to work now, though the detailed behaviour is still not tested
18 // against the real hardware.
19 
20 using std::string;
21 using std::shared_ptr;
22 
23 namespace openmsx {
24 
26 {
27 public:
28  TrackballState() {} // for serialize
29  TrackballState(EmuTime::param time, int deltaX_, int deltaY_,
30  byte press_, byte release_)
31  : StateChange(time)
32  , deltaX(deltaX_), deltaY(deltaY_)
33  , press(press_), release(release_) {}
34  int getDeltaX() const { return deltaX; }
35  int getDeltaY() const { return deltaY; }
36  byte getPress() const { return press; }
37  byte getRelease() const { return release; }
38 
39  template<typename Archive> void serialize(Archive& ar, unsigned /*version*/)
40  {
41  ar.template serializeBase<StateChange>(*this);
42  ar.serialize("deltaX", deltaX);
43  ar.serialize("deltaY", deltaY);
44  ar.serialize("press", press);
45  ar.serialize("release", release);
46  }
47 private:
48  int deltaX, deltaY;
49  byte press, release;
50 };
51 REGISTER_POLYMORPHIC_CLASS(StateChange, TrackballState, "TrackballState");
52 
53 
55  StateChangeDistributor& stateChangeDistributor_)
56  : eventDistributor(eventDistributor_)
57  , stateChangeDistributor(stateChangeDistributor_)
58  , deltaX(0), deltaY(0)
59  , lastValue(0)
60  , status(JOY_BUTTONA | JOY_BUTTONB)
61 {
62 }
63 
65 {
66  if (isPluggedIn()) {
67  Trackball::unplugHelper(EmuTime::dummy());
68  }
69 }
70 
71 
72 // Pluggable
73 const string& Trackball::getName() const
74 {
75  static const string name("trackball");
76  return name;
77 }
78 
79 string_ref Trackball::getDescription() const
80 {
81  return "MSX Trackball";
82 }
83 
84 void Trackball::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/)
85 {
86  eventDistributor.registerEventListener(*this);
87  stateChangeDistributor.registerListener(*this);
88  deltaX = deltaY = 0;
89 }
90 
91 void Trackball::unplugHelper(EmuTime::param /*time*/)
92 {
93  stateChangeDistributor.unregisterListener(*this);
94  eventDistributor.unregisterEventListener(*this);
95 }
96 
97 // JoystickDevice
98 byte Trackball::read(EmuTime::param /*time*/)
99 {
100  // From the Sony GB-7 Service manual:
101  // http://cdn.preterhuman.net/texts/computing/msx/sonygb7sm.pdf
102  // * The counter seems to be 8-bit wide, though only 4 bits (bit 7 and
103  // 2-0) are connected to the MSX. Looking at the M60226 block diagram
104  // in more detail shows that the (up/down-)counters have a 'HP'
105  // input, in the GB7 this input is hardwired to GND. My *guess* is
106  // that HP stands for either 'Half-' or 'High-precision' and that it
107  // selects between either 4 or 8 bits saturation.
108  // The bug report '#477 Trackball emulation overflow values too
109  // easily' contains a small movie. Some very rough calculations
110  // indicate that when you move a cursor 50 times per second, 8 pixels
111  // per step, you get about the same speed as in that move.
112  // So even though both 4 and 8 bit clipping *seem* to be possible,
113  // this code only implements 4 bit clipping.
114  // * It also contains a test program to read the trackball position.
115  // This program first reads the (X or Y) value and only then toggles
116  // pin 8. This seems to suggest the actual (X or Y) value is always
117  // present on reads and toggling pin 8 resets this value and switches
118  // to the other axis.
119  auto delta = (lastValue & 4) ? deltaY : deltaX;
120  return (status & ~0x0F) | ((delta + 8) & 0x0F);
121 }
122 
123 void Trackball::write(byte value, EmuTime::param /*time*/)
124 {
125  byte diff = lastValue ^ value;
126  lastValue = value;
127  if (diff & 0x4) {
128  // pin 8 flipped
129  if (value & 4) {
130  deltaX = 0;
131  } else {
132  deltaY = 0;
133  }
134  }
135 }
136 
137 // MSXEventListener
138 void Trackball::signalEvent(const shared_ptr<const Event>& event,
139  EmuTime::param time)
140 {
141  switch (event->getType()) {
143  auto& mev = checked_cast<const MouseMotionEvent&>(*event);
144  static const int SCALE = 2;
145  int dx = mev.getX() / SCALE;
146  int dy = mev.getY() / SCALE;
147  if ((dx != 0) || (dy != 0)) {
148  createTrackballStateChange(time, dx, dy, 0, 0);
149  }
150  break;
151  }
153  auto& butEv = checked_cast<const MouseButtonEvent&>(*event);
154  switch (butEv.getButton()) {
156  createTrackballStateChange(time, 0, 0, JOY_BUTTONA, 0);
157  break;
159  createTrackballStateChange(time, 0, 0, JOY_BUTTONB, 0);
160  break;
161  default:
162  // ignore other buttons
163  break;
164  }
165  break;
166  }
168  auto& butEv = checked_cast<const MouseButtonEvent&>(*event);
169  switch (butEv.getButton()) {
171  createTrackballStateChange(time, 0, 0, 0, JOY_BUTTONA);
172  break;
174  createTrackballStateChange(time, 0, 0, 0, JOY_BUTTONB);
175  break;
176  default:
177  // ignore other buttons
178  break;
179  }
180  break;
181  }
182  default:
183  // ignore
184  break;
185  }
186 }
187 
188 void Trackball::createTrackballStateChange(
189  EmuTime::param time, int deltaX, int deltaY, byte press, byte release)
190 {
191  stateChangeDistributor.distributeNew(std::make_shared<TrackballState>(
192  time, deltaX, deltaY, press, release));
193 }
194 
195 // StateChangeListener
196 void Trackball::signalStateChange(const shared_ptr<StateChange>& event)
197 {
198  auto ts = dynamic_cast<TrackballState*>(event.get());
199  if (!ts) return;
200 
201  deltaX = std::min(7, std::max(-8, deltaX + ts->getDeltaX()));
202  deltaY = std::min(7, std::max(-8, deltaY + ts->getDeltaY()));
203  status = (status & ~ts->getPress()) | ts->getRelease();
204 }
205 
206 void Trackball::stopReplay(EmuTime::param time)
207 {
208  // TODO Get actual mouse button(s) state. Is it worth the trouble?
209  byte release = (JOY_BUTTONA | JOY_BUTTONB) & ~status;
210  if ((deltaX != 0) || (deltaY != 0) || (release != 0)) {
211  stateChangeDistributor.distributeNew(
212  std::make_shared<TrackballState>(
213  time, -deltaX, -deltaY, 0, release));
214  }
215 }
216 
217 
218 template<typename Archive>
219 void Trackball::serialize(Archive& ar, unsigned /*version*/)
220 {
221  ar.serialize("deltaX", deltaX);
222  ar.serialize("deltaY", deltaY);
223  ar.serialize("lastValue", lastValue);
224  ar.serialize("status", status);
225 
226  if (ar.isLoader() && isPluggedIn()) {
227  plugHelper(*getConnector(), EmuTime::dummy());
228  }
229 }
232 
233 } // namespace openmsx
static const unsigned RIGHT
Definition: InputEvents.hh:59
static const int JOY_BUTTONB
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, CassettePlayer,"CassettePlayer")
static param dummy()
Definition: EmuTime.hh:21
virtual ~Trackball()
Definition: Trackball.cc:64
unsigned char byte
8 bit unsigned integer
Definition: openmsx.hh:33
void unregisterListener(StateChangeListener &listener)
void registerEventListener(MSXEventListener &listener)
Registers a given object to receive certain events.
This class implements a subset of the proposal for std::string_ref (proposed for the next c++ standar...
Definition: string_ref.hh:18
void distributeNew(const EventPtr &event)
Deliver the event to all registered listeners MSX input devices should call the distributeNew() versi...
void registerListener(StateChangeListener &listener)
(Un)registers the given object to receive state change events.
void serialize(Archive &ar, unsigned)
Definition: Trackball.cc:39
void serialize(Archive &ar, unsigned version)
Definition: Trackball.cc:219
REGISTER_POLYMORPHIC_CLASS(DiskContainer, NowindRomDisk,"NowindRomDisk")
int getDeltaY() const
Definition: Trackball.cc:35
byte getPress() const
Definition: Trackball.cc:36
void unregisterEventListener(MSXEventListener &listener)
Unregisters a previously registered event listener.
bool isPluggedIn() const
Returns true if this pluggable is currently plugged into a connector.
Definition: Pluggable.hh:51
byte getRelease() const
Definition: Trackball.cc:37
const EmuTime & param
Definition: EmuTime.hh:20
static const unsigned LEFT
Definition: InputEvents.hh:57
int getDeltaX() const
Definition: Trackball.cc:34
eventDistributor(eventDistributor_)
TrackballState(EmuTime::param time, int deltaX_, int deltaY_, byte press_, byte release_)
Definition: Trackball.cc:29
Connector * getConnector() const
Get the connector this Pluggable is plugged into.
Definition: Pluggable.hh:45
static const int JOY_BUTTONA
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:802
Base class for all external MSX state changing events.
Definition: StateChange.hh:14
Trackball(MSXEventDistributor &eventDistributor, StateChangeDistributor &stateChangeDistributor)
Definition: Trackball.cc:54