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