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 
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  double 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  double 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  double 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  x = value.getDouble(interp);
252  } else if (name == "-y") {
253  y = value.getDouble(interp);
254  } else if (name == "-z") {
255  double 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  relx = value.getDouble(interp);
270  } else if (name == "-rely") {
271  rely = 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(x);
293  } else if (name == "-y") {
294  result.setDouble(y);
295  } else if (name == "-z") {
296  result.setDouble(z);
297  } else if (name == "-relx") {
298  result.setDouble(relx);
299  } else if (name == "-rely") {
300  result.setDouble(rely);
301  } else if (name == "-scaled") {
302  result.setBoolean(scaled);
303  } else if (name == "-clip") {
304  result.setBoolean(clip);
305  } else if (name == "-mousecoord") {
306  double x, y;
307  getMouseCoord(x, y);
308  result.addListElement(x);
309  result.addListElement(y);
310  } else if (name == "-suppressErrors") {
311  result.setBoolean(suppressErrors);
312  } else {
313  throw CommandException("No such property: " + name);
314  }
315 }
316 
318 {
319  return 1.0; // fully opaque
320 }
321 
323 {
324  invalidateLocal();
326 }
327 
329 {
330  for (auto& s : subWidgets) {
331  s->invalidateRecursive();
332  }
333 }
334 
336 {
337  if (suppressErrors) return true;
338  if (const OSDWidget* parent = getParent()) {
339  return parent->needSuppressErrors();
340  }
341  return false;
342 }
343 
345 {
346  paintSDL(output);
347 
348  std::unique_ptr<SDLScopedClip> scopedClip;
349  if (clip) {
350  int x, y, w, h;
351  getBoundingBox(output, x, y, w, h);
352  scopedClip = make_unique<SDLScopedClip>(output, x, y, w, h);
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  int x, y, w, h;
369  getBoundingBox(output, x, y, w, h);
370  scopedClip = make_unique<GLScopedClip>(output, x, y, w, h);
371  }
372 
373  for (auto& s : subWidgets) {
374  s->paintGLRecursive(output);
375  }
376 #endif
377 }
378 
380 {
381  if (scaled) {
382  return output.getOutputWidth() / 320;;
383  } else if (getParent()) {
384  return getParent()->getScaleFactor(output);
385  } else {
386  return 1;
387  }
388 }
389 
391  double x, double y, double relx, double rely,
392  double& outx, double& outy) const
393 {
394  double width, height;
395  getWidthHeight(output, width, height);
396  int factor = getScaleFactor(output);
397  outx = x + factor * getX() + relx * width;
398  outy = y + factor * getY() + rely * height;
399  if (const OSDWidget* parent = getParent()) {
400  parent->transformXY(output, outx, outy, getRelX(), getRelY(),
401  outx, outy);
402  }
403 }
404 
405 void OSDWidget::transformReverse(
406  const OutputRectangle& output, double x, double y,
407  double& outx, double& outy) const
408 {
409  if (const OSDWidget* parent = getParent()) {
410  parent->transformReverse(output, x, y, x, y);
411  double width, height;
412  parent->getWidthHeight(output, width, height);
413  int factor = getScaleFactor(output);
414  outx = x - (getRelX() * width ) - (getX() * factor);
415  outy = y - (getRelY() * height) - (getY() * factor);
416  } else {
417  outx = x;
418  outy = y;
419  }
420 }
421 
422 void OSDWidget::getMouseCoord(double& outx, double& outy) const
423 {
424  if (SDL_ShowCursor(SDL_QUERY) == SDL_DISABLE) {
425  // Host cursor is not visible. Return dummy mouse coords for
426  // the OSD cursor position.
427  // The reason for doing this is that otherwise (e.g. when using
428  // the mouse in an MSX program) it's possible to accidentally
429  // click on the reversebar. This will also block the OSD mouse
430  // in other Tcl scripts (e.g. vampier's nemesis script), but
431  // almost always those scripts will also not be useful when the
432  // host mouse cursor is not visible.
433  //
434  // We need to return coordinates that lay outside any
435  // reasonable range. Initially we returned (NaN, NaN). But for
436  // some reason that didn't work on dingoo: Dingoo uses
437  // softfloat, in c++ NaN seems to behave as expected, but maybe
438  // there's a problem on the tcl side? Anyway, when we return
439  // +inf instead of NaN it does work.
440  outx = std::numeric_limits<double>::infinity();
441  outy = std::numeric_limits<double>::infinity();
442  return;
443  }
444 
445  SDL_Surface* surface = SDL_GetVideoSurface();
446  if (!surface) {
447  throw CommandException(
448  "Can't get mouse coordinates: no window visible");
449  }
450  DummyOutputRectangle output(surface->w, surface->h);
451 
452  int mouseX, mouseY;
453  SDL_GetMouseState(&mouseX, &mouseY);
454 
455  transformReverse(output, mouseX, mouseY, outx, outy);
456 
457  double width, height;
458  getWidthHeight(output, width, height);
459  if ((width == 0) || (height == 0)) {
460  throw CommandException(
461  "-can't get mouse coordinates: "
462  "widget has zero width or height");
463  }
464  outx /= width;
465  outy /= height;
466 }
467 
469  int& x, int& y, int& w, int& h)
470 {
471  double x1, y1, x2, y2;
472  transformXY(output, 0.0, 0.0, 0.0, 0.0, x1, y1);
473  transformXY(output, 0.0, 0.0, 1.0, 1.0, x2, y2);
474  x = int(x1 + 0.5);
475  y = int(y1 + 0.5);
476  w = int(x2 - x1 + 0.5);
477  h = int(y2 - y1 + 0.5);
478 }
479 
480 void OSDWidget::listWidgetNames(const string& parentName, vector<string>& result) const
481 {
482  string pname = parentName;
483  if (!pname.empty()) pname += '.';
484  for (auto& s : subWidgets) {
485  string name = pname + s->getName();
486  result.push_back(name);
487  s->listWidgetNames(name, result);
488  }
489 }
490 
491 } // namespace openmsx
void setDouble(double value)
Definition: TclObject.cc:98
virtual string_ref getType() const =0
int getScaleFactor(const OutputRectangle &surface) const
Definition: OSDWidget.cc:379
string_ref::const_iterator end(const string_ref &x)
Definition: string_ref.hh:150
void invalidateRecursive()
Definition: OSDWidget.cc:322
void deleteWidget(OSDWidget &widget)
Definition: OSDWidget.cc:176
bool getBoolean(Interpreter &interp) const
Definition: TclObject.cc:170
void splitOnFirst(string_ref str, string_ref chars, string_ref &first, string_ref &last)
Definition: StringOp.cc:324
bool needSuppressErrors() const
Definition: OSDWidget.cc:335
virtual void getWidthHeight(const OutputRectangle &output, double &width, double &height) const =0
virtual double getRecursiveFadeValue() const
Definition: OSDWidget.cc:317
int clip(int x)
Clips x to the range [LO,HI].
Definition: Math.hh:28
OSDWidget * findSubWidget(string_ref name)
Definition: OSDWidget.cc:136
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:87
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: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:344
GLScopedClip(OutputSurface &output, int x, int y, int w, int h)
Definition: OSDWidget.cc:89
unsigned getHeight() const
virtual ~OSDWidget()
Definition: OSDWidget.cc:132
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:7
virtual void paintSDL(OutputSurface &output)=0
double getY() const
Definition: OSDWidget.hh:23
void invalidateChildren()
Definition: OSDWidget.cc:328
virtual void paintGL(OutputSurface &output)=0
OSDWidget * getParent()
Definition: OSDWidget.hh:28
void addListElement(string_ref element)
Definition: TclObject.cc:120
void transformXY(const OutputRectangle &output, double x, double y, double relx, double rely, double &outx, double &outy) const
Definition: OSDWidget.cc:390
OSDWidget(const std::string &name)
Definition: OSDWidget.cc:121
void setString(string_ref value)
Definition: TclObject.cc:65
void getBoundingBox(const OutputRectangle &output, int &x, int &y, int &w, int &h)
Definition: OSDWidget.cc:468
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
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
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:180
const std::string & getName() const
Definition: OSDWidget.hh:21
#define UNREACHABLE
Definition: unreachable.hh:35