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