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 
134 void OSDWidget::addWidget(unique_ptr<OSDWidget> widget)
135 {
136  widget->setParent(this);
137 
138  // Insert the new widget in the correct place (sorted on ascending Z)
139  // heuristic: often we have either
140  // - many widgets with all the same Z
141  // - only a few total number of subwidgets (possibly with different Z)
142  // In the former case we can simply append at the end. In the latter
143  // case a linear search is probably faster than a binary search. Only
144  // when there are many sub-widgets with not all the same Z (and not
145  // created in sorted Z-order) a binary search would be faster.
146  float widgetZ = widget->getZ();
147  if (subWidgets.empty() || (subWidgets.back()->getZ() <= widgetZ)) {
148  subWidgets.push_back(std::move(widget));
149  } else {
150  auto it = begin(subWidgets);
151  while ((*it)->getZ() <= widgetZ) ++it;
152  subWidgets.insert(it, std::move(widget));
153 
154  }
155 }
156 
158 {
159  auto it = rfind_if_unguarded(subWidgets,
160  [&](const std::unique_ptr<OSDWidget>& p) { return p.get() == &widget; });
161  subWidgets.erase(it);
162 }
163 
164 #ifdef DEBUG
165 struct AscendingZ {
166  bool operator()(const unique_ptr<OSDWidget>& lhs,
167  const unique_ptr<OSDWidget>& rhs) const {
168  return lhs->getZ() < rhs->getZ();
169  }
170 };
171 #endif
172 void OSDWidget::resortUp(OSDWidget* elem)
173 {
174  // z-coordinate was increased, first search for elements current position
175  auto it1 = begin(subWidgets);
176  while (it1->get() != elem) ++it1;
177  // next search for the position were it belongs
178  float elemZ = elem->getZ();
179  auto it2 = it1;
180  ++it2;
181  while ((it2 != end(subWidgets)) && ((*it2)->getZ() < elemZ)) ++it2;
182  // now move elements to correct position
183  rotate(it1, it1 + 1, it2);
184 #ifdef DEBUG
185  assert(std::is_sorted(begin(subWidgets), end(subWidgets), AscendingZ()));
186 #endif
187 }
188 void OSDWidget::resortDown(OSDWidget* elem)
189 {
190  // z-coordinate was decreased, first search for new position
191  auto it1 = begin(subWidgets);
192  float elemZ = elem->getZ();
193  while ((*it1)->getZ() <= elemZ) {
194  ++it1;
195  if (it1 == end(subWidgets)) return;
196  }
197  // next search for the elements current position
198  auto it2 = it1;
199  if ((it2 != begin(subWidgets)) && ((it2 - 1)->get() == elem)) return;
200  while (it2->get() != elem) ++it2;
201  // now move elements to correct position
202  rotate(it1, it2, it2 + 1);
203 #ifdef DEBUG
204  assert(std::is_sorted(begin(subWidgets), end(subWidgets), AscendingZ()));
205 #endif
206 }
207 
208 vector<string_ref> OSDWidget::getProperties() const
209 {
210  static const char* const vals[] = {
211  "-type", "-x", "-y", "-z", "-relx", "-rely", "-scaled",
212  "-clip", "-mousecoord", "-suppressErrors",
213  };
214  return vector<string_ref>(std::begin(vals), std::end(vals));
215 }
216 
218  Interpreter& interp, string_ref propName, const TclObject& value)
219 {
220  if (propName == "-type") {
221  throw CommandException("-type property is readonly");
222  } else if (propName == "-mousecoord") {
223  throw CommandException("-mousecoord property is readonly");
224  } else if (propName == "-x") {
225  pos[0] = value.getDouble(interp);
226  } else if (propName == "-y") {
227  pos[1] = value.getDouble(interp);
228  } else if (propName == "-z") {
229  float z2 = value.getDouble(interp);
230  if (z != z2) {
231  bool up = z2 > z; // was z increased?
232  z = z2;
233  if (auto* p = getParent()) {
234  // TODO no need for a full sort: instead remove and re-insert in the correct place
235  if (up) {
236  p->resortUp(this);
237  } else {
238  p->resortDown(this);
239  }
240  }
241  }
242  } else if (propName == "-relx") {
243  relPos[0] = value.getDouble(interp);
244  } else if (propName == "-rely") {
245  relPos[1] = value.getDouble(interp);
246  } else if (propName == "-scaled") {
247  bool scaled2 = value.getBoolean(interp);
248  if (scaled != scaled2) {
249  scaled = scaled2;
251  }
252  } else if (propName == "-clip") {
253  clip = value.getBoolean(interp);
254  } else if (propName == "-suppressErrors") {
255  suppressErrors = value.getBoolean(interp);
256  } else {
257  throw CommandException("No such property: " + propName);
258  }
259 }
260 
261 void OSDWidget::getProperty(string_ref propName, TclObject& result) const
262 {
263  if (propName == "-type") {
264  result.setString(getType());
265  } else if (propName == "-x") {
266  result.setDouble(pos[0]);
267  } else if (propName == "-y") {
268  result.setDouble(pos[1]);
269  } else if (propName == "-z") {
270  result.setDouble(z);
271  } else if (propName == "-relx") {
272  result.setDouble(relPos[0]);
273  } else if (propName == "-rely") {
274  result.setDouble(relPos[1]);
275  } else if (propName == "-scaled") {
276  result.setBoolean(scaled);
277  } else if (propName == "-clip") {
278  result.setBoolean(clip);
279  } else if (propName == "-mousecoord") {
280  vec2 coord = getMouseCoord();
281  result.addListElement(coord[0]);
282  result.addListElement(coord[1]);
283  } else if (propName == "-suppressErrors") {
284  result.setBoolean(suppressErrors);
285  } else {
286  throw CommandException("No such property: " + propName);
287  }
288 }
289 
291 {
292  return 1.0f; // fully opaque
293 }
294 
296 {
297  invalidateLocal();
299 }
300 
302 {
303  for (auto& s : subWidgets) {
304  s->invalidateRecursive();
305  }
306 }
307 
309 {
310  if (suppressErrors) return true;
311  if (const auto* p = getParent()) {
312  return p->needSuppressErrors();
313  }
314  return false;
315 }
316 
318 {
319  paintSDL(output);
320 
321  std::unique_ptr<SDLScopedClip> scopedClip;
322  if (clip) {
323  ivec2 clipPos, size;
324  getBoundingBox(output, clipPos, size);
325  scopedClip = make_unique<SDLScopedClip>(
326  output, clipPos[0], clipPos[1], size[0], size[1]);
327  }
328 
329  for (auto& s : subWidgets) {
330  s->paintSDLRecursive(output);
331  }
332 }
333 
335 {
336  (void)output;
337 #if COMPONENT_GL
338  paintGL(output);
339 
340  std::unique_ptr<GLScopedClip> scopedClip;
341  if (clip) {
342  ivec2 clipPos, size;
343  getBoundingBox(output, clipPos, size);
344  scopedClip = make_unique<GLScopedClip>(
345  output, clipPos[0], clipPos[1], size[0], size[1]);
346  }
347 
348  for (auto& s : subWidgets) {
349  s->paintGLRecursive(output);
350  }
351 #endif
352 }
353 
355 {
356  if (scaled) {
357  return output.getOutputSize()[0] / 320;;
358  } else if (getParent()) {
359  return getParent()->getScaleFactor(output);
360  } else {
361  return 1;
362  }
363 }
364 
366  vec2 trPos, vec2 trRelPos) const
367 {
368  vec2 out = trPos
369  + (float(getScaleFactor(output)) * getPos())
370  + (trRelPos * getSize(output));
371  if (const auto* p = getParent()) {
372  out = p->transformPos(output, out, getRelPos());
373  }
374  return out;
375 }
376 
377 vec2 OSDWidget::transformReverse(const OutputRectangle& output, vec2 trPos) const
378 {
379  if (const auto* p = getParent()) {
380  trPos = p->transformReverse(output, trPos);
381  return trPos
382  - (getRelPos() * p->getSize(output))
383  - (getPos() * float(getScaleFactor(output)));
384  } else {
385  return trPos;
386  }
387 }
388 
389 vec2 OSDWidget::getMouseCoord() const
390 {
391  if (SDL_ShowCursor(SDL_QUERY) == SDL_DISABLE) {
392  // Host cursor is not visible. Return dummy mouse coords for
393  // the OSD cursor position.
394  // The reason for doing this is that otherwise (e.g. when using
395  // the mouse in an MSX program) it's possible to accidentally
396  // click on the reversebar. This will also block the OSD mouse
397  // in other Tcl scripts (e.g. vampier's nemesis script), but
398  // almost always those scripts will also not be useful when the
399  // host mouse cursor is not visible.
400  //
401  // We need to return coordinates that lay outside any
402  // reasonable range. Initially we returned (NaN, NaN). But for
403  // some reason that didn't work on dingoo: Dingoo uses
404  // softfloat, in c++ NaN seems to behave as expected, but maybe
405  // there's a problem on the tcl side? Anyway, when we return
406  // +inf instead of NaN it does work.
407  return vec2(std::numeric_limits<float>::infinity());
408  }
409 
410  auto resolution = getDisplay().getOutputScreenResolution();
411  if (resolution[0] < 0) {
412  throw CommandException(
413  "Can't get mouse coordinates: no window visible");
414  }
415  DummyOutputRectangle output(resolution);
416 
417  int mouseX, mouseY;
418  SDL_GetMouseState(&mouseX, &mouseY);
419 
420  vec2 out = transformReverse(output, vec2(mouseX, mouseY));
421 
422  vec2 size = getSize(output);
423  if ((size[0] == 0.0f) || (size[1] == 0.0f)) {
424  throw CommandException(
425  "-can't get mouse coordinates: "
426  "widget has zero width or height");
427  }
428  return out / size;
429 }
430 
432  ivec2& bbPos, ivec2& bbSize)
433 {
434  vec2 topLeft = transformPos(output, vec2(), vec2(0.0f));
435  vec2 bottomRight = transformPos(output, vec2(), vec2(1.0f));
436  bbPos = round(topLeft);
437  bbSize = round(bottomRight - topLeft);
438 }
439 
440 } // 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:295
void deleteWidget(OSDWidget &widget)
Definition: OSDWidget.cc:157
gl::vec2 getPos() const
Definition: OSDWidget.hh:27
bool needSuppressErrors() const
Definition: OSDWidget.cc:308
gl::vec2 getRelPos() const
Definition: OSDWidget.hh:28
virtual std::vector< string_ref > getProperties() const
Definition: OSDWidget.cc:208
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:431
vecN< N, T > normalize(const vecN< N, T > &x)
Definition: gl_vec.hh:340
gl::vec2 transformPos(const OutputRectangle &output, gl::vec2 pos, gl::vec2 relPos) const
Definition: OSDWidget.cc:365
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:277
void paintGLRecursive(OutputSurface &output)
Definition: OSDWidget.cc:334
void addWidget(std::unique_ptr< OSDWidget > widget)
Definition: OSDWidget.cc:134
virtual void getProperty(string_ref name, TclObject &result) const
Definition: OSDWidget.cc:261
void paintSDLRecursive(OutputSurface &output)
Definition: OSDWidget.cc:317
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:356
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:301
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:354
string_ref::const_iterator begin(const string_ref &x)
Definition: string_ref.hh:166
virtual float getRecursiveFadeValue() const
Definition: OSDWidget.cc:290
virtual void setProperty(Interpreter &interp, string_ref name, const TclObject &value)
Definition: OSDWidget.cc:217
vecN< 2, float > vec2
Definition: gl_vec.hh:138
auto rfind_if_unguarded(RANGE &range, PRED pred) -> decltype(std::begin(range))
Definition: stl.hh:174
Definition: gl_mat.hh:24