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::shared_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(const shared_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(widget);
188  } else {
189  auto it = begin(subWidgets);
190  while ((*it)->getZ() <= z) ++it;
191  subWidgets.insert(it, 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 shared_ptr<OSDWidget>& lhs,
213  const shared_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  // Iterate over a copy because drawing could cause errors (e.g. can't
376  // load font or image), those error are (indirectly via CliComm) passed
377  // to a Tcl callback and that callback can destroy or create extra
378  // widgets.
379  auto copy = subWidgets;
380  for (auto& s : copy) {
381  s->paintSDLRecursive(output);
382  }
383 }
384 
386 {
387  (void)output;
388 #if COMPONENT_GL
389  paintGL(output);
390 
391  std::unique_ptr<GLScopedClip> scopedClip;
392  if (clip) {
393  int x, y, w, h;
394  getBoundingBox(output, x, y, w, h);
395  scopedClip = make_unique<GLScopedClip>(output, x, y, w, h);
396  }
397 
398  auto copy = subWidgets;
399  for (auto& s : copy) {
400  s->paintGLRecursive(output);
401  }
402 #endif
403 }
404 
406 {
407  if (scaled) {
408  return output.getOutputWidth() / 320;;
409  } else if (getParent()) {
410  return getParent()->getScaleFactor(output);
411  } else {
412  return 1;
413  }
414 }
415 
417  double x, double y, double relx, double rely,
418  double& outx, double& outy) const
419 {
420  double width, height;
421  getWidthHeight(output, width, height);
422  int factor = getScaleFactor(output);
423  outx = x + factor * getX() + relx * width;
424  outy = y + factor * getY() + rely * height;
425  if (const OSDWidget* parent = getParent()) {
426  parent->transformXY(output, outx, outy, getRelX(), getRelY(),
427  outx, outy);
428  }
429 }
430 
431 void OSDWidget::transformReverse(
432  const OutputRectangle& output, double x, double y,
433  double& outx, double& outy) const
434 {
435  if (const OSDWidget* parent = getParent()) {
436  parent->transformReverse(output, x, y, x, y);
437  double width, height;
438  parent->getWidthHeight(output, width, height);
439  int factor = getScaleFactor(output);
440  outx = x - (getRelX() * width ) - (getX() * factor);
441  outy = y - (getRelY() * height) - (getY() * factor);
442  } else {
443  outx = x;
444  outy = y;
445  }
446 }
447 
448 void OSDWidget::getMouseCoord(double& outx, double& outy) const
449 {
450  if (SDL_ShowCursor(SDL_QUERY) == SDL_DISABLE) {
451  // Host cursor is not visible. Return dummy mouse coords for
452  // the OSD cursor position.
453  // The reason for doing this is that otherwise (e.g. when using
454  // the mouse in an MSX program) it's possible to accidentally
455  // click on the reversebar. This will also block the OSD mouse
456  // in other Tcl scripts (e.g. vampier's nemesis script), but
457  // almost always those scripts will also not be useful when the
458  // host mouse cursor is not visible.
459  //
460  // We need to return coordinates that lay outside any
461  // reasonable range. Initially we returned (NaN, NaN). But for
462  // some reason that didn't work on dingoo: Dingoo uses
463  // softfloat, in c++ NaN seems to behave as expected, but maybe
464  // there's a problem on the tcl side? Anyway, when we return
465  // +inf instead of NaN it does work.
466  outx = std::numeric_limits<double>::infinity();
467  outy = std::numeric_limits<double>::infinity();
468  return;
469  }
470 
471  SDL_Surface* surface = SDL_GetVideoSurface();
472  if (!surface) {
473  throw CommandException(
474  "Can't get mouse coordinates: no window visible");
475  }
476  DummyOutputRectangle output(surface->w, surface->h);
477 
478  int mouseX, mouseY;
479  SDL_GetMouseState(&mouseX, &mouseY);
480 
481  transformReverse(output, mouseX, mouseY, outx, outy);
482 
483  double width, height;
484  getWidthHeight(output, width, height);
485  if ((width == 0) || (height == 0)) {
486  throw CommandException(
487  "-can't get mouse coordinates: "
488  "widget has zero width or height");
489  }
490  outx /= width;
491  outy /= height;
492 }
493 
495  int& x, int& y, int& w, int& h)
496 {
497  double x1, y1, x2, y2;
498  transformXY(output, 0.0, 0.0, 0.0, 0.0, x1, y1);
499  transformXY(output, 0.0, 0.0, 1.0, 1.0, x2, y2);
500  x = int(x1 + 0.5);
501  y = int(y1 + 0.5);
502  w = int(x2 - x1 + 0.5);
503  h = int(y2 - y1 + 0.5);
504 }
505 
506 void OSDWidget::listWidgetNames(const string& parentName, vector<string>& result) const
507 {
508  string pname = parentName;
509  if (!pname.empty()) pname += '.';
510  for (auto& s : subWidgets) {
511  string name = pname + s->getName();
512  result.push_back(name);
513  s->listWidgetNames(name, result);
514  }
515 }
516 
517 } // 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:405
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:385
virtual void getProperty(string_ref name, TclObject &result) const
Definition: OSDWidget.cc:307
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
void addWidget(const std::shared_ptr< OSDWidget > &widget)
Definition: OSDWidget.cc:172
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:416
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:494
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