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 == subWidgetsMap.end() ? 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 = subWidgets.begin();
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 = subWidgets.begin(); it != subWidgets.end(); ++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 = subWidgets.begin();
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 != subWidgets.end()) && ((*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(subWidgets.begin(), subWidgets.end(), AscendingZ()));
232 #endif
233 }
234 void OSDWidget::resortDown(OSDWidget* elem)
235 {
236  // z-coordinate was decreased, first search for new position
237  auto it1 = subWidgets.begin();
238  double z = elem->getZ();
239  while ((*it1)->getZ() <= z) {
240  ++it1;
241  if (it1 == subWidgets.end()) return;
242  }
243  // next search for the elements current position
244  auto it2 = it1;
245  if ((it2 != subWidgets.begin()) && ((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(subWidgets.begin(), subWidgets.end(), 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 {
265  if (name == "-type") {
266  throw CommandException("-type property is readonly");
267  } else if (name == "-mousecoord") {
268  throw CommandException("-mousecoord property is readonly");
269  } else if (name == "-x") {
270  x = value.getDouble();
271  } else if (name == "-y") {
272  y = value.getDouble();
273  } else if (name == "-z") {
274  double z2 = value.getDouble();
275  if (z != z2) {
276  bool up = z2 > z; // was z increased?
277  z = z2;
278  if (OSDWidget* parent = getParent()) {
279  // TODO no need for a full sort: instead remove and re-insert in the correct place
280  if (up) {
281  parent->resortUp(this);
282  } else {
283  parent->resortDown(this);
284  }
285  }
286  }
287  } else if (name == "-relx") {
288  relx = value.getDouble();
289  } else if (name == "-rely") {
290  rely = value.getDouble();
291  } else if (name == "-scaled") {
292  bool scaled2 = value.getBoolean();
293  if (scaled != scaled2) {
294  scaled = scaled2;
296  }
297  } else if (name == "-clip") {
298  clip = value.getBoolean();
299  } else if (name == "-suppressErrors") {
300  suppressErrors = value.getBoolean();
301  } else {
302  throw CommandException("No such property: " + name);
303  }
304 }
305 
306 void OSDWidget::getProperty(string_ref name, TclObject& result) const
307 {
308  if (name == "-type") {
309  result.setString(getType());
310  } else if (name == "-x") {
311  result.setDouble(x);
312  } else if (name == "-y") {
313  result.setDouble(y);
314  } else if (name == "-z") {
315  result.setDouble(z);
316  } else if (name == "-relx") {
317  result.setDouble(relx);
318  } else if (name == "-rely") {
319  result.setDouble(rely);
320  } else if (name == "-scaled") {
321  result.setBoolean(scaled);
322  } else if (name == "-clip") {
323  result.setBoolean(clip);
324  } else if (name == "-mousecoord") {
325  double x, y;
326  getMouseCoord(x, y);
327  result.addListElement(x);
328  result.addListElement(y);
329  } else if (name == "-suppressErrors") {
330  result.setBoolean(suppressErrors);
331  } else {
332  throw CommandException("No such property: " + name);
333  }
334 }
335 
337 {
338  return 1.0; // fully opaque
339 }
340 
342 {
343  invalidateLocal();
345 }
346 
348 {
349  for (auto& s : subWidgets) {
350  s->invalidateRecursive();
351  }
352 }
353 
355 {
356  if (suppressErrors) return true;
357  if (const OSDWidget* parent = getParent()) {
358  return parent->needSuppressErrors();
359  }
360  return false;
361 }
362 
364 {
365  paintSDL(output);
366 
367  std::unique_ptr<SDLScopedClip> scopedClip;
368  if (clip) {
369  int x, y, w, h;
370  getBoundingBox(output, x, y, w, h);
371  scopedClip = make_unique<SDLScopedClip>(output, x, y, w, h);
372  }
373 
374  // Iterate over a copy because drawing could cause errors (e.g. can't
375  // load font or image), those error are (indirectly via CliComm) passed
376  // to a Tcl callback and that callback can destroy or create extra
377  // widgets.
378  auto copy = subWidgets;
379  for (auto& s : copy) {
380  s->paintSDLRecursive(output);
381  }
382 }
383 
385 {
386  (void)output;
387 #if COMPONENT_GL
388  paintGL(output);
389 
390  std::unique_ptr<GLScopedClip> scopedClip;
391  if (clip) {
392  int x, y, w, h;
393  getBoundingBox(output, x, y, w, h);
394  scopedClip = make_unique<GLScopedClip>(output, x, y, w, h);
395  }
396 
397  auto copy = subWidgets;
398  for (auto& s : copy) {
399  s->paintGLRecursive(output);
400  }
401 #endif
402 }
403 
405 {
406  if (scaled) {
407  return output.getOutputWidth() / 320;;
408  } else if (getParent()) {
409  return getParent()->getScaleFactor(output);
410  } else {
411  return 1;
412  }
413 }
414 
416  double x, double y, double relx, double rely,
417  double& outx, double& outy) const
418 {
419  double width, height;
420  getWidthHeight(output, width, height);
421  int factor = getScaleFactor(output);
422  outx = x + factor * getX() + relx * width;
423  outy = y + factor * getY() + rely * height;
424  if (const OSDWidget* parent = getParent()) {
425  parent->transformXY(output, outx, outy, getRelX(), getRelY(),
426  outx, outy);
427  }
428 }
429 
430 void OSDWidget::transformReverse(
431  const OutputRectangle& output, double x, double y,
432  double& outx, double& outy) const
433 {
434  if (const OSDWidget* parent = getParent()) {
435  parent->transformReverse(output, x, y, x, y);
436  double width, height;
437  parent->getWidthHeight(output, width, height);
438  int factor = getScaleFactor(output);
439  outx = x - (getRelX() * width ) - (getX() * factor);
440  outy = y - (getRelY() * height) - (getY() * factor);
441  } else {
442  outx = x;
443  outy = y;
444  }
445 }
446 
447 void OSDWidget::getMouseCoord(double& outx, double& outy) const
448 {
449  if (SDL_ShowCursor(SDL_QUERY) == SDL_DISABLE) {
450  // Host cursor is not visible. Return dummy mouse coords for
451  // the OSD cursor position.
452  // The reason for doing this is that otherwise (e.g. when using
453  // the mouse in an MSX program) it's possible to accidentally
454  // click on the reversebar. This will also block the OSD mouse
455  // in other Tcl scripts (e.g. vampier's nemesis script), but
456  // almost always those scripts will also not be useful when the
457  // host mouse cursor is not visible.
458  //
459  // We need to return coordinates that lay outside any
460  // reasonable range. Initially we returned (NaN, NaN). But for
461  // some reason that didn't work on dingoo: Dingoo uses
462  // softfloat, in c++ NaN seems to behave as expected, but maybe
463  // there's a problem on the tcl side? Anyway, when we return
464  // +inf instead of NaN it does work.
465  outx = std::numeric_limits<double>::infinity();
466  outy = std::numeric_limits<double>::infinity();
467  return;
468  }
469 
470  SDL_Surface* surface = SDL_GetVideoSurface();
471  if (!surface) {
472  throw CommandException(
473  "Can't get mouse coordinates: no window visible");
474  }
475  DummyOutputRectangle output(surface->w, surface->h);
476 
477  int mouseX, mouseY;
478  SDL_GetMouseState(&mouseX, &mouseY);
479 
480  transformReverse(output, mouseX, mouseY, outx, outy);
481 
482  double width, height;
483  getWidthHeight(output, width, height);
484  if ((width == 0) || (height == 0)) {
485  throw CommandException(
486  "-can't get mouse coordinates: "
487  "widget has zero width or height");
488  }
489  outx /= width;
490  outy /= height;
491 }
492 
494  int& x, int& y, int& w, int& h)
495 {
496  double x1, y1, x2, y2;
497  transformXY(output, 0.0, 0.0, 0.0, 0.0, x1, y1);
498  transformXY(output, 0.0, 0.0, 1.0, 1.0, x2, y2);
499  x = int(x1 + 0.5);
500  y = int(y1 + 0.5);
501  w = int(x2 - x1 + 0.5);
502  h = int(y2 - y1 + 0.5);
503 }
504 
505 void OSDWidget::listWidgetNames(const string& parentName, vector<string>& result) const
506 {
507  string pname = parentName;
508  if (!pname.empty()) pname += '.';
509  for (auto& s : subWidgets) {
510  string name = pname + s->getName();
511  result.push_back(name);
512  s->listWidgetNames(name, result);
513  }
514 }
515 
516 } // namespace openmsx
void setDouble(double value)
Definition: TclObject.cc:132
OSDWidget * getParent()
Definition: OSDWidget.cc:141
virtual string_ref getType() const =0
int getScaleFactor(const OutputRectangle &surface) const
Definition: OSDWidget.cc:404
void invalidateRecursive()
Definition: OSDWidget.cc:341
void deleteWidget(OSDWidget &widget)
Definition: OSDWidget.cc:196
virtual void setProperty(string_ref name, const TclObject &value)
Definition: OSDWidget.cc:263
void splitOnFirst(string_ref str, string_ref chars, string_ref &first, string_ref &last)
Definition: StringOp.cc:300
bool needSuppressErrors() const
Definition: OSDWidget.cc:354
virtual void getWidthHeight(const OutputRectangle &output, double &width, double &height) const =0
virtual double getRecursiveFadeValue() const
Definition: OSDWidget.cc:336
OSDWidget * findSubWidget(string_ref name)
Definition: OSDWidget.cc:156
double getRelX() const
Definition: OSDWidget.hh:24
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:121
double getX() const
Definition: OSDWidget.hh:21
double getRelY() const
Definition: OSDWidget.hh:25
virtual unsigned getOutputWidth() const =0
void paintGLRecursive(OutputSurface &output)
Definition: OSDWidget.cc:384
bool getBoolean() const
Definition: TclObject.cc:195
virtual void getProperty(string_ref name, TclObject &result) const
Definition: OSDWidget.cc:306
const std::string & getName() const
Definition: OSDWidget.cc:136
void paintSDLRecursive(OutputSurface &output)
Definition: OSDWidget.cc:363
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:22
void invalidateChildren()
Definition: OSDWidget.cc:347
virtual void paintGL(OutputSurface &output)=0
void addListElement(string_ref element)
Definition: TclObject.cc:154
void transformXY(const OutputRectangle &output, double x, double y, double relx, double rely, double &outx, double &outy) const
Definition: OSDWidget.cc:415
OSDWidget(const std::string &name)
Definition: OSDWidget.cc:121
void setString(string_ref value)
Definition: TclObject.cc:99
void getBoundingBox(const OutputRectangle &output, int &x, int &y, int &w, int &h)
Definition: OSDWidget.cc:493
virtual void invalidateLocal()=0
int clip(int x)
Clips x to the range [LO,HI].
Definition: Math.hh:29
virtual std::vector< string_ref > getProperties() const
Definition: OSDWidget.cc:254
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() const
Definition: TclObject.cc:204
#define UNREACHABLE
Definition: unreachable.hh:56