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