openMSX
OSDWidget.cc
Go to the documentation of this file.
1 #include "OSDWidget.hh"
2 #include "OutputSurface.hh"
3 #include "CommandException.hh"
4 #include "TclObject.hh"
5 #include "StringOp.hh"
6 #include "GLUtil.hh"
7 #include "memory.hh"
8 #include "stl.hh"
9 #include <SDL.h>
10 #include <algorithm>
11 #include <limits>
12 
13 using std::string;
14 using std::vector;
15 using std::unique_ptr;
16 using namespace gl;
17 
18 namespace openmsx {
19 
20 // intersect two rectangles
21 static void intersect(int xa, int ya, int wa, int ha,
22  int xb, int yb, int wb, int hb,
23  int& x, int& y, int& w, int& h)
24 {
25  int x1 = std::max<int>(xa, xb);
26  int y1 = std::max<int>(ya, yb);
27  int x2 = std::min<int>(xa + wa, xb + wb);
28  int y2 = std::min<int>(ya + ha, yb + hb);
29  x = x1;
30  y = y1;
31  w = std::max(0, x2 - x1);
32  h = std::max(0, y2 - y1);
33 }
34 
36 
37 static void normalize(int& x, int& w)
38 {
39  if (w < 0) {
40  w = -w;
41  x -= w;
42  }
43 }
44 
46 {
47 public:
48  SDLScopedClip(OutputSurface& output, int x, int y, int w, int h);
49  ~SDLScopedClip();
50 private:
51  SDL_Surface* surface;
52  SDL_Rect origClip;
53 };
54 
55 
56 SDLScopedClip::SDLScopedClip(OutputSurface& output, int x, int y, int w, int h)
57  : surface(output.getSDLSurface())
58 {
59  normalize(x, w); normalize(y, h);
60  SDL_GetClipRect(surface, &origClip);
61 
62  int xn, yn, wn, hn;
63  intersect(origClip.x, origClip.y, origClip.w, origClip.h,
64  x, y, w, h,
65  xn, yn, wn, hn);
66  SDL_Rect newClip = { Sint16(xn), Sint16(yn), Uint16(wn), Uint16(hn) };
67  SDL_SetClipRect(surface, &newClip);
68 }
69 
71 {
72  SDL_SetClipRect(surface, &origClip);
73 }
74 
76 
77 #if COMPONENT_GL
78 
80 {
81 public:
82  GLScopedClip(OutputSurface& output, int x, int y, int w, int h);
83  ~GLScopedClip();
84 private:
85  GLint box[4]; // x, y, w, h;
86  GLboolean wasEnabled;
87 };
88 
89 
90 GLScopedClip::GLScopedClip(OutputSurface& output, int x, int y, int w, int h)
91 {
92  normalize(x, w); normalize(y, h);
93  y = output.getHeight() - y - h; // openGL sets (0,0) in LOWER-left corner
94 
95  wasEnabled = glIsEnabled(GL_SCISSOR_TEST);
96  if (wasEnabled == GL_TRUE) {
97  glGetIntegerv(GL_SCISSOR_BOX, box);
98  int xn, yn, wn, hn;
99  intersect(box[0], box[1], box[2], box[3],
100  x, y, w, h,
101  xn, yn, wn, hn);
102  glScissor(xn, yn, wn, hn);
103  } else {
104  glScissor(x, y, w, h);
105  glEnable(GL_SCISSOR_TEST);
106  }
107 }
108 
110 {
111  if (wasEnabled == GL_TRUE) {
112  glScissor(box[0], box[1], box[2], box[3]);
113  } else {
114  glDisable(GL_SCISSOR_TEST);
115  }
116 }
117 
118 #endif
119 
121 
123  : parent(nullptr)
124  , name(name_)
125  , z(0.0)
126  , scaled(false)
127  , clip(false)
128  , suppressErrors(false)
129 {
130 }
131 
133 {
134 }
135 
136 void OSDWidget::addWidget(unique_ptr<OSDWidget> widget)
137 {
138  widget->setParent(this);
139 
140  // Insert the new widget in the correct place (sorted on ascending Z)
141  // heuristic: often we have either
142  // - many widgets with all the same Z
143  // - only a few total number of subwidgets (possibly with different Z)
144  // In the former case we can simply append at the end. In the latter
145  // case a linear search is probably faster than a binary search. Only
146  // when there are many sub-widgets with not all the same Z (and not
147  // created in sorted Z-order) a binary search would be faster.
148  float z = widget->getZ();
149  if (subWidgets.empty() || (subWidgets.back()->getZ() <= z)) {
150  subWidgets.push_back(std::move(widget));
151  } else {
152  auto it = begin(subWidgets);
153  while ((*it)->getZ() <= z) ++it;
154  subWidgets.insert(it, std::move(widget));
155 
156  }
157 }
158 
160 {
161  auto it = find_if_unguarded(subWidgets,
162  [&](const std::unique_ptr<OSDWidget>& p) { return p.get() == &widget; });
163  subWidgets.erase(it);
164 }
165 
166 #ifdef DEBUG
167 struct AscendingZ {
168  bool operator()(const unique_ptr<OSDWidget>& lhs,
169  const unique_ptr<OSDWidget>& rhs) const {
170  return lhs->getZ() < rhs->getZ();
171  }
172 };
173 #endif
174 void OSDWidget::resortUp(OSDWidget* elem)
175 {
176  // z-coordinate was increased, first search for elements current position
177  auto it1 = begin(subWidgets);
178  while (it1->get() != elem) ++it1;
179  // next search for the position were it belongs
180  float z = elem->getZ();
181  auto it2 = it1;
182  ++it2;
183  while ((it2 != end(subWidgets)) && ((*it2)->getZ() < z)) ++it2;
184  // now move elements to correct position
185  rotate(it1, it1 + 1, it2);
186 #ifdef DEBUG
187  assert(std::is_sorted(begin(subWidgets), end(subWidgets), AscendingZ()));
188 #endif
189 }
190 void OSDWidget::resortDown(OSDWidget* elem)
191 {
192  // z-coordinate was decreased, first search for new position
193  auto it1 = begin(subWidgets);
194  float z = elem->getZ();
195  while ((*it1)->getZ() <= z) {
196  ++it1;
197  if (it1 == end(subWidgets)) return;
198  }
199  // next search for the elements current position
200  auto it2 = it1;
201  if ((it2 != begin(subWidgets)) && ((it2 - 1)->get() == elem)) return;
202  while (it2->get() != elem) ++it2;
203  // now move elements to correct position
204  rotate(it1, it2, it2 + 1);
205 #ifdef DEBUG
206  assert(std::is_sorted(begin(subWidgets), end(subWidgets), AscendingZ()));
207 #endif
208 }
209 
210 vector<string_ref> OSDWidget::getProperties() const
211 {
212  static const char* const vals[] = {
213  "-type", "-x", "-y", "-z", "-relx", "-rely", "-scaled",
214  "-clip", "-mousecoord", "-suppressErrors",
215  };
216  return vector<string_ref>(std::begin(vals), std::end(vals));
217 }
218 
220  Interpreter& interp, string_ref name, const TclObject& value)
221 {
222  if (name == "-type") {
223  throw CommandException("-type property is readonly");
224  } else if (name == "-mousecoord") {
225  throw CommandException("-mousecoord property is readonly");
226  } else if (name == "-x") {
227  pos[0] = value.getDouble(interp);
228  } else if (name == "-y") {
229  pos[1] = value.getDouble(interp);
230  } else if (name == "-z") {
231  float z2 = value.getDouble(interp);
232  if (z != z2) {
233  bool up = z2 > z; // was z increased?
234  z = z2;
235  if (OSDWidget* parent = getParent()) {
236  // TODO no need for a full sort: instead remove and re-insert in the correct place
237  if (up) {
238  parent->resortUp(this);
239  } else {
240  parent->resortDown(this);
241  }
242  }
243  }
244  } else if (name == "-relx") {
245  relPos[0] = value.getDouble(interp);
246  } else if (name == "-rely") {
247  relPos[1] = value.getDouble(interp);
248  } else if (name == "-scaled") {
249  bool scaled2 = value.getBoolean(interp);
250  if (scaled != scaled2) {
251  scaled = scaled2;
253  }
254  } else if (name == "-clip") {
255  clip = value.getBoolean(interp);
256  } else if (name == "-suppressErrors") {
257  suppressErrors = value.getBoolean(interp);
258  } else {
259  throw CommandException("No such property: " + name);
260  }
261 }
262 
263 void OSDWidget::getProperty(string_ref name, TclObject& result) const
264 {
265  if (name == "-type") {
266  result.setString(getType());
267  } else if (name == "-x") {
268  result.setDouble(pos[0]);
269  } else if (name == "-y") {
270  result.setDouble(pos[1]);
271  } else if (name == "-z") {
272  result.setDouble(z);
273  } else if (name == "-relx") {
274  result.setDouble(relPos[0]);
275  } else if (name == "-rely") {
276  result.setDouble(relPos[1]);
277  } else if (name == "-scaled") {
278  result.setBoolean(scaled);
279  } else if (name == "-clip") {
280  result.setBoolean(clip);
281  } else if (name == "-mousecoord") {
282  vec2 coord = getMouseCoord();
283  result.addListElement(coord[0]);
284  result.addListElement(coord[1]);
285  } else if (name == "-suppressErrors") {
286  result.setBoolean(suppressErrors);
287  } else {
288  throw CommandException("No such property: " + name);
289  }
290 }
291 
293 {
294  return 1.0f; // fully opaque
295 }
296 
298 {
299  invalidateLocal();
301 }
302 
304 {
305  for (auto& s : subWidgets) {
306  s->invalidateRecursive();
307  }
308 }
309 
311 {
312  if (suppressErrors) return true;
313  if (const OSDWidget* parent = getParent()) {
314  return parent->needSuppressErrors();
315  }
316  return false;
317 }
318 
320 {
321  paintSDL(output);
322 
323  std::unique_ptr<SDLScopedClip> scopedClip;
324  if (clip) {
325  ivec2 pos, size;
326  getBoundingBox(output, pos, size);
327  scopedClip = make_unique<SDLScopedClip>(
328  output, pos[0], pos[1], size[0], size[1]);
329  }
330 
331  for (auto& s : subWidgets) {
332  s->paintSDLRecursive(output);
333  }
334 }
335 
337 {
338  (void)output;
339 #if COMPONENT_GL
340  paintGL(output);
341 
342  std::unique_ptr<GLScopedClip> scopedClip;
343  if (clip) {
344  ivec2 pos, size;
345  getBoundingBox(output, pos, size);
346  scopedClip = make_unique<GLScopedClip>(
347  output, pos[0], pos[1], size[0], size[1]);
348  }
349 
350  for (auto& s : subWidgets) {
351  s->paintGLRecursive(output);
352  }
353 #endif
354 }
355 
357 {
358  if (scaled) {
359  return output.getOutputWidth() / 320;;
360  } else if (getParent()) {
361  return getParent()->getScaleFactor(output);
362  } else {
363  return 1;
364  }
365 }
366 
368  vec2 pos, vec2 relPos) const
369 {
370  vec2 out = pos
371  + (float(getScaleFactor(output)) * getPos())
372  + (relPos * getSize(output));
373  if (const OSDWidget* parent = getParent()) {
374  out = parent->transformPos(output, out, getRelPos());
375  }
376  return out;
377 }
378 
379 vec2 OSDWidget::transformReverse(const OutputRectangle& output, vec2 pos) const
380 {
381  if (const OSDWidget* parent = getParent()) {
382  pos = parent->transformReverse(output, pos);
383  return pos
384  - (getRelPos() * parent->getSize(output))
385  - (getPos() * float(getScaleFactor(output)));
386  } else {
387  return pos;
388  }
389 }
390 
391 vec2 OSDWidget::getMouseCoord() const
392 {
393  if (SDL_ShowCursor(SDL_QUERY) == SDL_DISABLE) {
394  // Host cursor is not visible. Return dummy mouse coords for
395  // the OSD cursor position.
396  // The reason for doing this is that otherwise (e.g. when using
397  // the mouse in an MSX program) it's possible to accidentally
398  // click on the reversebar. This will also block the OSD mouse
399  // in other Tcl scripts (e.g. vampier's nemesis script), but
400  // almost always those scripts will also not be useful when the
401  // host mouse cursor is not visible.
402  //
403  // We need to return coordinates that lay outside any
404  // reasonable range. Initially we returned (NaN, NaN). But for
405  // some reason that didn't work on dingoo: Dingoo uses
406  // softfloat, in c++ NaN seems to behave as expected, but maybe
407  // there's a problem on the tcl side? Anyway, when we return
408  // +inf instead of NaN it does work.
409  return vec2(std::numeric_limits<float>::infinity());
410  }
411 
412  SDL_Surface* surface = SDL_GetVideoSurface();
413  if (!surface) {
414  throw CommandException(
415  "Can't get mouse coordinates: no window visible");
416  }
417  DummyOutputRectangle output(surface->w, surface->h);
418 
419  int mouseX, mouseY;
420  SDL_GetMouseState(&mouseX, &mouseY);
421 
422  vec2 out = transformReverse(output, vec2(mouseX, mouseY));
423 
424  vec2 size = getSize(output);
425  if ((size[0] == 0.0f) || (size[1] == 0.0f)) {
426  throw CommandException(
427  "-can't get mouse coordinates: "
428  "widget has zero width or height");
429  }
430  return out / size;
431 }
432 
434  ivec2& pos, ivec2& size)
435 {
436  vec2 topLeft = transformPos(output, vec2(), vec2(0.0f));
437  vec2 bottomRight = transformPos(output, vec2(), vec2(1.0f));
438  pos = round(topLeft);
439  size = round(bottomRight - topLeft);
440 }
441 
442 } // namespace openmsx
void setDouble(double value)
Definition: TclObject.cc:47
virtual string_ref getType() const =0
int getScaleFactor(const OutputRectangle &surface) const
Definition: OSDWidget.cc:356
string_ref::const_iterator end(const string_ref &x)
Definition: string_ref.hh:167
void invalidateRecursive()
Definition: OSDWidget.cc:297
void deleteWidget(OSDWidget &widget)
Definition: OSDWidget.cc:159
gl::vec2 getPos() const
Definition: OSDWidget.hh:26
bool getBoolean(Interpreter &interp) const
Definition: TclObject.cc:119
bool needSuppressErrors() const
Definition: OSDWidget.cc:310
int clip(int x)
Clips x to the range [LO,HI].
Definition: Math.hh:34
void getBoundingBox(const OutputRectangle &output, gl::ivec2 &pos, gl::ivec2 &size)
Definition: OSDWidget.cc:433
vecN< N, T > normalize(const vecN< N, T > &x)
Definition: gl_vec.hh:329
virtual gl::vec2 getSize(const OutputRectangle &output) const =0
This class implements a subset of the proposal for std::string_ref (proposed for the next c++ standar...
Definition: string_ref.hh:18
A frame buffer where pixels can be written to.
void setBoolean(bool value)
Definition: TclObject.cc:36
vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:266
virtual unsigned getOutputWidth() const =0
void paintGLRecursive(OutputSurface &output)
Definition: OSDWidget.cc:336
virtual void getProperty(string_ref name, TclObject &result) const
Definition: OSDWidget.cc:263
void addWidget(std::unique_ptr< OSDWidget > widget)
Definition: OSDWidget.cc:136
void paintSDLRecursive(OutputSurface &output)
Definition: OSDWidget.cc:319
gl::vec2 getRelPos() const
Definition: OSDWidget.hh:27
GLScopedClip(OutputSurface &output, int x, int y, int w, int h)
Definition: OSDWidget.cc:90
vecN< N, int > round(const vecN< N, T > &x)
Definition: gl_vec.hh:345
OSDWidget(const TclObject &name)
Definition: OSDWidget.cc:122
unsigned getHeight() const
virtual ~OSDWidget()
Definition: OSDWidget.cc:132
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
gl::vec2 transformPos(const OutputRectangle &output, gl::vec2 pos, gl::vec2 relPos) const
Definition: OSDWidget.cc:367
virtual void paintSDL(OutputSurface &output)=0
void invalidateChildren()
Definition: OSDWidget.cc:303
virtual void paintGL(OutputSurface &output)=0
OSDWidget * getParent()
Definition: OSDWidget.hh:30
void addListElement(string_ref element)
Definition: TclObject.cc:69
void setString(string_ref value)
Definition: TclObject.cc:14
size_t size() const
virtual void invalidateLocal()=0
virtual std::vector< string_ref > getProperties() const
Definition: OSDWidget.cc:210
ITER find_if_unguarded(ITER first, ITER last, PRED pred)
Faster alternative to 'find_if' when it's guaranteed that the predicate will be true for at least one...
Definition: stl.hh:143
mat4 rotate(float angle, const vec3 &axis)
Definition: gl_transform.hh:56
virtual float getRecursiveFadeValue() const
Definition: OSDWidget.cc:292
string_ref::const_iterator begin(const string_ref &x)
Definition: string_ref.hh:166
virtual void setProperty(Interpreter &interp, string_ref name, const TclObject &value)
Definition: OSDWidget.cc:219
vecN< 2, float > vec2
Definition: gl_vec.hh:127
Definition: gl_mat.hh:23
double getDouble(Interpreter &interp) const
Definition: TclObject.cc:129