openMSX
OSDWidget.cc
Go to the documentation of this file.
1 #include "OSDWidget.hh"
2 #include "OutputSurface.hh"
3 #include "Display.hh"
4 #include "CommandException.hh"
5 #include "TclObject.hh"
6 #include "GLUtil.hh"
7 #include "optional.hh"
8 #include "ranges.hh"
9 #include "stl.hh"
10 #include <SDL.h>
11 #include <array>
12 #include <limits>
13 
14 using std::string;
15 using std::vector;
16 using std::unique_ptr;
17 using namespace gl;
18 
19 namespace openmsx {
20 
21 // intersect two rectangles
22 static void intersect(int xa, int ya, int wa, int ha,
23  int xb, int yb, int wb, int hb,
24  int& x, int& y, int& w, int& h)
25 {
26  int x1 = std::max<int>(xa, xb);
27  int y1 = std::max<int>(ya, yb);
28  int x2 = std::min<int>(xa + wa, xb + wb);
29  int y2 = std::min<int>(ya + ha, yb + hb);
30  x = x1;
31  y = y1;
32  w = std::max(0, x2 - x1);
33  h = std::max(0, y2 - y1);
34 }
35 
37 template<typename T>
38 static void normalize(T& x, T& w)
39 {
40  if (w < 0) {
41  w = -w;
42  x -= w;
43  }
44 }
45 
47 {
48 public:
49  SDLScopedClip(OutputSurface& output, vec2 xy, vec2 wh);
50  ~SDLScopedClip();
51 private:
52  SDL_Renderer* renderer;
53  optional<SDL_Rect> origClip;
54 };
55 
56 
57 SDLScopedClip::SDLScopedClip(OutputSurface& output, vec2 xy, vec2 wh)
58  : renderer(output.getSDLRenderer())
59 {
60  ivec2 i_xy = round(xy); int x = i_xy[0]; int y = i_xy[1];
61  ivec2 i_wh = round(wh); int w = i_wh[0]; int h = i_wh[1];
62  normalize(x, w); normalize(y, h);
63 
64  int xn, yn, wn, hn;
65  if (SDL_RenderIsClipEnabled(renderer)) {
66  origClip.emplace();
67  SDL_RenderGetClipRect(renderer, &*origClip);
68 
69  intersect(origClip->x, origClip->y, origClip->w, origClip->h,
70  x, y, w, h,
71  xn, yn, wn, hn);
72  } else {
73  xn = x; yn = y; wn = w; hn = h;
74  }
75  SDL_Rect newClip = { Sint16(xn), Sint16(yn), Uint16(wn), Uint16(hn) };
76  SDL_RenderSetClipRect(renderer, &newClip);
77 }
78 
80 {
81  SDL_RenderSetClipRect(renderer, origClip ? &*origClip : nullptr);
82 }
83 
85 
86 #if COMPONENT_GL
87 
89 {
90 public:
91  GLScopedClip(OutputSurface& output, vec2 xy, vec2 wh);
92  ~GLScopedClip();
93 private:
94  optional<std::array<GLint, 4>> origClip; // x, y, w, h;
95 };
96 
97 
99 {
100  normalize(xy[0], wh[0]); normalize(xy[1], wh[1]);
101  xy[1] = output.getHeight() - xy[1] - wh[1]; // openGL sets (0,0) in LOWER-left corner
102 
103  // transform view-space coordinates to clip-space coordinates
104  vec2 scale = output.getViewScale();
105  ivec2 i_xy = round(xy * scale) + output.getViewOffset();
106  ivec2 i_wh = round(wh * scale);
107 
108  if (glIsEnabled(GL_SCISSOR_TEST) == GL_TRUE) {
109  origClip.emplace();
110  glGetIntegerv(GL_SCISSOR_BOX, origClip->data());
111  int xn, yn, wn, hn;
112  intersect((*origClip)[0], (*origClip)[1], (*origClip)[2], (*origClip)[3],
113  i_xy[0], i_xy[1], i_wh[0], i_wh[1],
114  xn, yn, wn, hn);
115  glScissor(xn, yn, wn, hn);
116  } else {
117  glScissor(i_xy[0], i_xy[1], i_wh[0], i_wh[1]);
118  glEnable(GL_SCISSOR_TEST);
119  }
120 }
121 
123 {
124  if (origClip) {
125  glScissor((*origClip)[0], (*origClip)[1], (*origClip)[2], (*origClip)[3]);
126  } else {
127  glDisable(GL_SCISSOR_TEST);
128  }
129 }
130 
131 #endif
132 
134 
135 OSDWidget::OSDWidget(Display& display_, const TclObject& name_)
136  : display(display_)
137  , parent(nullptr)
138  , name(name_)
139  , z(0.0)
140  , scaled(false)
141  , clip(false)
142  , suppressErrors(false)
143 {
144 }
145 
146 void OSDWidget::addWidget(unique_ptr<OSDWidget> widget)
147 {
148  widget->setParent(this);
149 
150  // Insert the new widget in the correct place (sorted on ascending Z)
151  // heuristic: often we have either
152  // - many widgets with all the same Z
153  // - only a few total number of subwidgets (possibly with different Z)
154  // In the former case we can simply append at the end. In the latter
155  // case a linear search is probably faster than a binary search. Only
156  // when there are many sub-widgets with not all the same Z (and not
157  // created in sorted Z-order) a binary search would be faster.
158  float widgetZ = widget->getZ();
159  if (subWidgets.empty() || (subWidgets.back()->getZ() <= widgetZ)) {
160  subWidgets.push_back(std::move(widget));
161  } else {
162  auto it = begin(subWidgets);
163  while ((*it)->getZ() <= widgetZ) ++it;
164  subWidgets.insert(it, std::move(widget));
165 
166  }
167 }
168 
170 {
171  auto it = rfind_if_unguarded(subWidgets,
172  [&](const std::unique_ptr<OSDWidget>& p) { return p.get() == &widget; });
173  subWidgets.erase(it);
174 }
175 
176 #ifdef DEBUG
177 struct AscendingZ {
178  bool operator()(const unique_ptr<OSDWidget>& lhs,
179  const unique_ptr<OSDWidget>& rhs) const {
180  return lhs->getZ() < rhs->getZ();
181  }
182 };
183 #endif
184 void OSDWidget::resortUp(OSDWidget* elem)
185 {
186  // z-coordinate was increased, first search for elements current position
187  auto it1 = begin(subWidgets);
188  while (it1->get() != elem) ++it1;
189  // next search for the position were it belongs
190  float elemZ = elem->getZ();
191  auto it2 = it1;
192  ++it2;
193  while ((it2 != end(subWidgets)) && ((*it2)->getZ() < elemZ)) ++it2;
194  // now move elements to correct position
195  rotate(it1, it1 + 1, it2);
196 #ifdef DEBUG
197  assert(ranges::is_sorted(subWidgets, AscendingZ()));
198 #endif
199 }
200 void OSDWidget::resortDown(OSDWidget* elem)
201 {
202  // z-coordinate was decreased, first search for new position
203  auto it1 = begin(subWidgets);
204  float elemZ = elem->getZ();
205  while ((*it1)->getZ() <= elemZ) {
206  ++it1;
207  if (it1 == end(subWidgets)) return;
208  }
209  // next search for the elements current position
210  auto it2 = it1;
211  if ((it2 != begin(subWidgets)) && ((it2 - 1)->get() == elem)) return;
212  while (it2->get() != elem) ++it2;
213  // now move elements to correct position
214  rotate(it1, it2, it2 + 1);
215 #ifdef DEBUG
216  assert(ranges::is_sorted(subWidgets, AscendingZ()));
217 #endif
218 }
219 
220 vector<string_view> OSDWidget::getProperties() const
221 {
222  static const char* const vals[] = {
223  "-type", "-x", "-y", "-z", "-relx", "-rely", "-scaled",
224  "-clip", "-mousecoord", "-suppressErrors",
225  };
226  return to_vector<string_view>(vals);
227 }
228 
230  Interpreter& interp, string_view propName, const TclObject& value)
231 {
232  if (propName == "-type") {
233  throw CommandException("-type property is readonly");
234  } else if (propName == "-mousecoord") {
235  throw CommandException("-mousecoord property is readonly");
236  } else if (propName == "-x") {
237  pos[0] = value.getDouble(interp);
238  } else if (propName == "-y") {
239  pos[1] = value.getDouble(interp);
240  } else if (propName == "-z") {
241  float z2 = value.getDouble(interp);
242  if (z != z2) {
243  bool up = z2 > z; // was z increased?
244  z = z2;
245  if (auto* p = getParent()) {
246  // TODO no need for a full sort: instead remove and re-insert in the correct place
247  if (up) {
248  p->resortUp(this);
249  } else {
250  p->resortDown(this);
251  }
252  }
253  }
254  } else if (propName == "-relx") {
255  relPos[0] = value.getDouble(interp);
256  } else if (propName == "-rely") {
257  relPos[1] = value.getDouble(interp);
258  } else if (propName == "-scaled") {
259  bool scaled2 = value.getBoolean(interp);
260  if (scaled != scaled2) {
261  scaled = scaled2;
263  }
264  } else if (propName == "-clip") {
265  clip = value.getBoolean(interp);
266  } else if (propName == "-suppressErrors") {
267  suppressErrors = value.getBoolean(interp);
268  } else {
269  throw CommandException("No such property: ", propName);
270  }
271 }
272 
273 void OSDWidget::getProperty(string_view propName, TclObject& result) const
274 {
275  if (propName == "-type") {
276  result = getType();
277  } else if (propName == "-x") {
278  result = pos[0];
279  } else if (propName == "-y") {
280  result = pos[1];
281  } else if (propName == "-z") {
282  result = z;
283  } else if (propName == "-relx") {
284  result = relPos[0];
285  } else if (propName == "-rely") {
286  result = relPos[1];
287  } else if (propName == "-scaled") {
288  result = scaled;
289  } else if (propName == "-clip") {
290  result = clip;
291  } else if (propName == "-mousecoord") {
292  vec2 coord = getMouseCoord();
293  result.addListElement(coord[0], coord[1]);
294  } else if (propName == "-suppressErrors") {
295  result = suppressErrors;
296  } else {
297  throw CommandException("No such property: ", propName);
298  }
299 }
300 
302 {
303  return 1.0f; // fully opaque
304 }
305 
307 {
308  invalidateLocal();
310 }
311 
313 {
314  for (auto& s : subWidgets) {
315  s->invalidateRecursive();
316  }
317 }
318 
320 {
321  if (suppressErrors) return true;
322  if (const auto* p = getParent()) {
323  return p->needSuppressErrors();
324  }
325  return false;
326 }
327 
329 {
330  paintSDL(output);
331 
332  optional<SDLScopedClip> scopedClip;
333  if (clip) {
334  vec2 clipPos, size;
335  getBoundingBox(output, clipPos, size);
336  scopedClip.emplace(output, clipPos, size);
337  }
338 
339  for (auto& s : subWidgets) {
340  s->paintSDLRecursive(output);
341  }
342 }
343 
345 {
346  (void)output;
347 #if COMPONENT_GL
348  paintGL(output);
349 
350  optional<GLScopedClip> scopedClip;
351  if (clip) {
352  vec2 clipPos, size;
353  getBoundingBox(output, clipPos, size);
354  scopedClip.emplace(output, clipPos, size);
355  }
356 
357  for (auto& s : subWidgets) {
358  s->paintGLRecursive(output);
359  }
360 #endif
361 }
362 
363 int OSDWidget::getScaleFactor(const OutputSurface& output) const
364 {
365  if (scaled) {
366  return output.getLogicalSize()[0] / 320;;
367  } else if (getParent()) {
368  return getParent()->getScaleFactor(output);
369  } else {
370  return 1;
371  }
372 }
373 
375  vec2 trPos, vec2 trRelPos) const
376 {
377  vec2 out = trPos
378  + (float(getScaleFactor(output)) * getPos())
379  + (trRelPos * getSize(output));
380  if (const auto* p = getParent()) {
381  out = p->transformPos(output, out, getRelPos());
382  }
383  return out;
384 }
385 
386 vec2 OSDWidget::transformReverse(const OutputSurface& output, vec2 trPos) const
387 {
388  if (const auto* p = getParent()) {
389  trPos = p->transformReverse(output, trPos);
390  return trPos
391  - (getRelPos() * p->getSize(output))
392  - (getPos() * float(getScaleFactor(output)));
393  } else {
394  return trPos;
395  }
396 }
397 
398 vec2 OSDWidget::getMouseCoord() const
399 {
400  if (SDL_ShowCursor(SDL_QUERY) == SDL_DISABLE) {
401  // Host cursor is not visible. Return dummy mouse coords for
402  // the OSD cursor position.
403  // The reason for doing this is that otherwise (e.g. when using
404  // the mouse in an MSX program) it's possible to accidentally
405  // click on the reversebar. This will also block the OSD mouse
406  // in other Tcl scripts (e.g. vampier's nemesis script), but
407  // almost always those scripts will also not be useful when the
408  // host mouse cursor is not visible.
409  //
410  // We need to return coordinates that lay outside any
411  // reasonable range. Initially we returned (NaN, NaN). But for
412  // some reason that didn't work on dingoo: Dingoo uses
413  // softfloat, in c++ NaN seems to behave as expected, but maybe
414  // there's a problem on the tcl side? Anyway, when we return
415  // +inf instead of NaN it does work.
416  return vec2(std::numeric_limits<float>::infinity());
417  }
418 
419  auto* output = getDisplay().getOutputSurface();
420  if (!output) {
421  throw CommandException(
422  "Can't get mouse coordinates: no window visible");
423  }
424 
425  int mouseX, mouseY;
426  SDL_GetMouseState(&mouseX, &mouseY);
427 
428  vec2 out = transformReverse(*output, vec2(mouseX, mouseY));
429 
430  vec2 size = getSize(*output);
431  if ((size[0] == 0.0f) || (size[1] == 0.0f)) {
432  throw CommandException(
433  "-can't get mouse coordinates: "
434  "widget has zero width or height");
435  }
436  return out / size;
437 }
438 
440  vec2& bbPos, vec2& bbSize)
441 {
442  vec2 topLeft = transformPos(output, vec2(), vec2(0.0f));
443  vec2 bottomRight = transformPos(output, vec2(), vec2(1.0f));
444  bbPos = topLeft;
445  bbSize = bottomRight - topLeft;
446 }
447 
448 } // namespace openmsx
Represents the output window/screen of openMSX.
Definition: Display.hh:31
void invalidateRecursive()
Definition: OSDWidget.cc:306
void deleteWidget(OSDWidget &widget)
Definition: OSDWidget.cc:169
gl::vec2 getPos() const
Definition: OSDWidget.hh:23
bool needSuppressErrors() const
Definition: OSDWidget.cc:319
gl::vec2 getRelPos() const
Definition: OSDWidget.hh:24
int clip(int x)
Clips x to the range [LO,HI].
Definition: Math.hh:101
vecN< N, T > normalize(const vecN< N, T > &x)
Definition: gl_vec.hh:350
gl::ivec2 getLogicalSize() const
OSDWidget(Display &display, const TclObject &name)
Definition: OSDWidget.cc:135
A frame buffer where pixels can be written to.
vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:287
virtual void getProperty(string_view name, TclObject &result) const
Definition: OSDWidget.cc:273
void paintGLRecursive(OutputSurface &output)
Definition: OSDWidget.cc:344
virtual string_view getType() const =0
mat4 scale(const vec3 &xyz)
Definition: gl_transform.hh:19
void emplace(Args &&... args)
Definition: optional.hh:383
virtual gl::vec2 getSize(const OutputSurface &output) const =0
gl::vec2 getViewScale() const
auto begin(const string_view &x)
Definition: string_view.hh:151
void addWidget(std::unique_ptr< OSDWidget > widget)
Definition: OSDWidget.cc:146
bool is_sorted(ForwardRange &&range)
Definition: ranges.hh:23
void paintSDLRecursive(OutputSurface &output)
Definition: OSDWidget.cc:328
int getScaleFactor(const OutputSurface &output) const
Definition: OSDWidget.cc:363
GLScopedClip(OutputSurface &output, vec2 xy, vec2 wh)
Definition: OSDWidget.cc:98
gl::vec2 transformPos(const OutputSurface &output, gl::vec2 pos, gl::vec2 relPos) const
Definition: OSDWidget.cc:374
gl::ivec2 getViewOffset() const
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
virtual void setProperty(Interpreter &interp, string_view name, const TclObject &value)
Definition: OSDWidget.cc:229
void getBoundingBox(const OutputSurface &output, gl::vec2 &pos, gl::vec2 &size)
Definition: OSDWidget.cc:439
virtual void paintSDL(OutputSurface &output)=0
bool getBoolean(Interpreter &interp) const
Definition: TclObject.cc:82
void invalidateChildren()
Definition: OSDWidget.cc:312
virtual void paintGL(OutputSurface &output)=0
double getDouble(Interpreter &interp) const
Definition: TclObject.cc:92
OSDWidget * getParent()
Definition: OSDWidget.hh:27
This class implements a (close approximation) of the std::string_view class.
Definition: string_view.hh:16
void addListElement(T t)
Definition: TclObject.hh:121
float getZ() const
Definition: OSDWidget.hh:25
virtual std::vector< string_view > getProperties() const
Definition: OSDWidget.cc:220
Display & getDisplay() const
Definition: OSDWidget.hh:51
virtual void invalidateLocal()=0
mat4 rotate(float angle, const vec3 &axis)
Definition: gl_transform.hh:58
OutputSurface * getOutputSurface()
Definition: Display.cc:118
virtual float getRecursiveFadeValue() const
Definition: OSDWidget.cc:301
constexpr auto size(const C &c) -> decltype(c.size())
Definition: span.hh:62
vecN< 2, float > vec2
Definition: gl_vec.hh:139
auto rfind_if_unguarded(RANGE &range, PRED pred)
Definition: stl.hh:174
Definition: gl_mat.hh:24
auto end(const string_view &x)
Definition: string_view.hh:152