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