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  // * The counter seems to be 8-bit wide, though only 4 bits (bit 7 and
102  // 2-0) are connected to the MSX.
103  // * It also contains a test program to read the trackball position.
104  // This program first reads the (X or Y) value and only then toggles
105  // pin 8. This seems to suggest the actual (X or Y) value is always
106  // present on reads and toggling pin 8 resets this value and switches
107  // to the other axis.
108  signed char& delta = (lastValue & 4) ? deltaY : deltaX;
109  unsigned t = delta + 128;
110  return (status & ~0x0F) | ((t & 0x80) >> 4) | (t & 0x07);
111 }
112 
113 void Trackball::write(byte value, EmuTime::param /*time*/)
114 {
115  byte diff = lastValue ^ value;
116  lastValue = value;
117  if (diff & 0x4) {
118  // pin 8 flipped
119  if (value & 4) {
120  deltaX = 0;
121  } else {
122  deltaY = 0;
123  }
124  }
125 }
126 
127 // MSXEventListener
128 void Trackball::signalEvent(const shared_ptr<const Event>& event,
129  EmuTime::param time)
130 {
131  switch (event->getType()) {
133  auto& mev = checked_cast<const MouseMotionEvent&>(*event);
134  static const int SCALE = 2;
135  int dx = mev.getX() / SCALE;
136  int dy = mev.getY() / SCALE;
137  if ((dx != 0) || (dy != 0)) {
138  createTrackballStateChange(time, dx, dy, 0, 0);
139  }
140  break;
141  }
143  auto& butEv = checked_cast<const MouseButtonEvent&>(*event);
144  switch (butEv.getButton()) {
146  createTrackballStateChange(time, 0, 0, JOY_BUTTONA, 0);
147  break;
149  createTrackballStateChange(time, 0, 0, JOY_BUTTONB, 0);
150  break;
151  default:
152  // ignore other buttons
153  break;
154  }
155  break;
156  }
158  auto& butEv = checked_cast<const MouseButtonEvent&>(*event);
159  switch (butEv.getButton()) {
161  createTrackballStateChange(time, 0, 0, 0, JOY_BUTTONA);
162  break;
164  createTrackballStateChange(time, 0, 0, 0, JOY_BUTTONB);
165  break;
166  default:
167  // ignore other buttons
168  break;
169  }
170  break;
171  }
172  default:
173  // ignore
174  break;
175  }
176 }
177 
178 void Trackball::createTrackballStateChange(
179  EmuTime::param time, int deltaX, int deltaY, byte press, byte release)
180 {
181  stateChangeDistributor.distributeNew(std::make_shared<TrackballState>(
182  time, deltaX, deltaY, press, release));
183 }
184 
185 // StateChangeListener
186 void Trackball::signalStateChange(const shared_ptr<StateChange>& event)
187 {
188  auto ts = dynamic_cast<TrackballState*>(event.get());
189  if (!ts) return;
190 
191  deltaX = std::min(127, std::max(-128, deltaX + ts->getDeltaX()));
192  deltaY = std::min(127, std::max(-128, deltaY + ts->getDeltaY()));
193  status = (status & ~ts->getPress()) | ts->getRelease();
194 }
195 
196 void Trackball::stopReplay(EmuTime::param time)
197 {
198  // TODO Get actual mouse button(s) state. Is it worth the trouble?
199  byte release = (JOY_BUTTONA | JOY_BUTTONB) & ~status;
200  if ((deltaX != 0) || (deltaY != 0) || (release != 0)) {
201  stateChangeDistributor.distributeNew(
202  std::make_shared<TrackballState>(
203  time, -deltaX, -deltaY, 0, release));
204  }
205 }
206 
207 
208 template<typename Archive>
209 void Trackball::serialize(Archive& ar, unsigned /*version*/)
210 {
211  ar.serialize("deltaX", deltaX);
212  ar.serialize("deltaY", deltaY);
213  ar.serialize("lastValue", lastValue);
214  ar.serialize("status", status);
215 
216  if (ar.isLoader() && isPluggedIn()) {
217  plugHelper(*getConnector(), EmuTime::dummy());
218  }
219 }
222 
223 } // namespace openmsx