openMSX
OSDWidget.cc
Go to the documentation of this file.
1 #include "OSDWidget.hh"
2 #include "SDLOutputSurface.hh"
3 #include "Display.hh"
4 #include "VideoSystem.hh"
5 #include "CommandException.hh"
6 #include "TclObject.hh"
7 #include "GLUtil.hh"
8 #include "checked_cast.hh"
9 #include "ranges.hh"
10 #include "stl.hh"
11 #include <SDL.h>
12 #include <array>
13 #include <limits>
14 #include <optional>
15 
16 using namespace gl;
17 
18 namespace openmsx {
19 
20 // intersect two rectangles
21 struct IntersectResult { int x, y, w, h; };
22 static constexpr IntersectResult intersect(int xa, int ya, int wa, int ha,
23  int xb, int yb, int wb, int hb)
24 {
25  int x1 = std::max<int>(xa, xb);
26  int y1 = std::max<int>(ya, yb);
27  int x2 = std::min<int>(xa + wa, xb + wb);
28  int y2 = std::min<int>(ya + ha, yb + hb);
29  int w = std::max(0, x2 - x1);
30  int h = std::max(0, y2 - y1);
31  return {x1, y1, w, h};
32 }
33 
35 template<typename T>
36 static constexpr void normalize(T& x, T& w)
37 {
38  if (w < 0) {
39  w = -w;
40  x -= w;
41  }
42 }
43 
45 {
46 public:
47  SDLScopedClip(OutputSurface& output, vec2 xy, vec2 wh);
48  ~SDLScopedClip();
49  SDLScopedClip(const SDLScopedClip&) = delete;
51 private:
52  SDL_Renderer* renderer;
53  std::optional<SDL_Rect> origClip;
54 };
55 
56 
57 SDLScopedClip::SDLScopedClip(OutputSurface& output, vec2 xy, vec2 wh)
58  : renderer(checked_cast<SDLOutputSurface&>(output).getSDLRenderer())
59 {
60  ivec2 i_xy = round(xy); auto [x, y] = i_xy;
61  ivec2 i_wh = round(wh); auto [w, h] = i_wh;
62  normalize(x, w); normalize(y, h);
63 
64  auto [xn, yn, wn, hn] = [&, x=x, y=y, w=w, h=h] {
65  if (SDL_RenderIsClipEnabled(renderer)) {
66  origClip.emplace();
67  SDL_RenderGetClipRect(renderer, &*origClip);
68 
69  return intersect(origClip->x, origClip->y, origClip->w, origClip->h,
70  x, y, w, h);
71  } else {
72  return IntersectResult{x, y, w, h};
73  }
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  GLScopedClip(const GLScopedClip&) = delete;
94  GLScopedClip& operator=(const GLScopedClip&) = delete;
95 private:
96  std::optional<std::array<GLint, 4>> origClip; // x, y, w, h;
97 };
98 
99 
101 {
102  auto& [x, y] = xy;
103  auto& [w, h] = wh;
104  normalize(x, w); normalize(y, h);
105  y = output.getLogicalHeight() - y - h; // openGL sets (0,0) in LOWER-left corner
106 
107  // transform view-space coordinates to clip-space coordinates
108  vec2 scale = output.getViewScale();
109  auto [ix, iy] = round(xy * scale) + output.getViewOffset();
110  auto [iw, ih] = round(wh * scale);
111 
112  if (glIsEnabled(GL_SCISSOR_TEST) == GL_TRUE) {
113  origClip.emplace();
114  glGetIntegerv(GL_SCISSOR_BOX, origClip->data());
115  auto [xn, yn, wn, hn] = intersect(
116  (*origClip)[0], (*origClip)[1], (*origClip)[2], (*origClip)[3],
117  ix, iy, iw, ih);
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 
139  : display(display_)
140  , parent(nullptr)
141  , name(std::move(name_))
142  , z(0.0)
143  , scaled(false)
144  , clip(false)
145  , suppressErrors(false)
146 {
147 }
148 
149 void OSDWidget::addWidget(std::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_unguarded(subWidgets, &widget,
175  [](const auto& p) { return p.get(); });
176  subWidgets.erase(it);
177 }
178 
179 void OSDWidget::resortUp(OSDWidget* elem)
180 {
181  // z-coordinate was increased, first search for elements current position
182  auto it1 = begin(subWidgets);
183  while (it1->get() != elem) ++it1;
184  // next search for the position were it belongs
185  float elemZ = elem->getZ();
186  auto it2 = it1;
187  ++it2;
188  while ((it2 != end(subWidgets)) && ((*it2)->getZ() < elemZ)) ++it2;
189  // now move elements to correct position
190  rotate(it1, it1 + 1, it2);
191 #ifdef DEBUG
192  assert(ranges::is_sorted(subWidgets, {}, &OSDWidget::getZ));
193 #endif
194 }
195 void OSDWidget::resortDown(OSDWidget* elem)
196 {
197  // z-coordinate was decreased, first search for new position
198  auto it1 = begin(subWidgets);
199  float elemZ = elem->getZ();
200  while ((*it1)->getZ() <= elemZ) {
201  ++it1;
202  if (it1 == end(subWidgets)) return;
203  }
204  // next search for the elements current position
205  auto it2 = it1;
206  if ((it2 != begin(subWidgets)) && ((it2 - 1)->get() == elem)) return;
207  while (it2->get() != elem) ++it2;
208  // now move elements to correct position
209  rotate(it1, it2, it2 + 1);
210 #ifdef DEBUG
211  assert(ranges::is_sorted(subWidgets, {}, &OSDWidget::getZ));
212 #endif
213 }
214 
216  Interpreter& interp, std::string_view propName, const TclObject& value)
217 {
218  if (propName == "-type") {
219  throw CommandException("-type property is readonly");
220  } else if (propName == "-mousecoord") {
221  throw CommandException("-mousecoord property is readonly");
222  } else if (propName == "-x") {
223  pos[0] = value.getDouble(interp);
224  } else if (propName == "-y") {
225  pos[1] = value.getDouble(interp);
226  } else if (propName == "-z") {
227  float z2 = value.getDouble(interp);
228  if (z != z2) {
229  bool up = z2 > z; // was z increased?
230  z = z2;
231  if (auto* p = getParent()) {
232  // TODO no need for a full sort: instead remove and re-insert in the correct place
233  if (up) {
234  p->resortUp(this);
235  } else {
236  p->resortDown(this);
237  }
238  }
239  }
240  } else if (propName == "-relx") {
241  relPos[0] = value.getDouble(interp);
242  } else if (propName == "-rely") {
243  relPos[1] = value.getDouble(interp);
244  } else if (propName == "-scaled") {
245  bool scaled2 = value.getBoolean(interp);
246  if (scaled != scaled2) {
247  scaled = scaled2;
249  }
250  } else if (propName == "-clip") {
251  clip = value.getBoolean(interp);
252  } else if (propName == "-suppressErrors") {
253  suppressErrors = value.getBoolean(interp);
254  } else {
255  throw CommandException("No such property: ", propName);
256  }
257 }
258 
259 void OSDWidget::getProperty(std::string_view propName, TclObject& result) const
260 {
261  if (propName == "-type") {
262  result = getType();
263  } else if (propName == "-x") {
264  result = pos[0];
265  } else if (propName == "-y") {
266  result = pos[1];
267  } else if (propName == "-z") {
268  result = z;
269  } else if (propName == "-relx") {
270  result = relPos[0];
271  } else if (propName == "-rely") {
272  result = relPos[1];
273  } else if (propName == "-scaled") {
274  result = scaled;
275  } else if (propName == "-clip") {
276  result = clip;
277  } else if (propName == "-mousecoord") {
278  auto [x, y] = getMouseCoord();
279  result.addListElement(x, y);
280  } else if (propName == "-suppressErrors") {
281  result = suppressErrors;
282  } else {
283  throw CommandException("No such property: ", propName);
284  }
285 }
286 
288 {
289  return 1.0f; // fully opaque
290 }
291 
293 {
294  invalidateLocal();
296 }
297 
299 {
300  for (auto& s : subWidgets) {
301  s->invalidateRecursive();
302  }
303 }
304 
306 {
307  if (suppressErrors) return true;
308  if (const auto* p = getParent()) {
309  return p->needSuppressErrors();
310  }
311  return false;
312 }
313 
315 {
316  paintSDL(output);
317 
318  std::optional<SDLScopedClip> scopedClip;
319  if (clip) {
320  vec2 clipPos, size;
321  getBoundingBox(output, clipPos, size);
322  scopedClip.emplace(output, clipPos, size);
323  }
324 
325  for (auto& s : subWidgets) {
326  s->paintSDLRecursive(output);
327  }
328 }
329 
331 {
332  (void)output;
333 #if COMPONENT_GL
334  paintGL(output);
335 
336  std::optional<GLScopedClip> scopedClip;
337  if (clip) {
338  vec2 clipPos, size;
339  getBoundingBox(output, clipPos, size);
340  scopedClip.emplace(output, clipPos, size);
341  }
342 
343  for (auto& s : subWidgets) {
344  s->paintGLRecursive(output);
345  }
346 #endif
347 }
348 
349 int OSDWidget::getScaleFactor(const OutputSurface& output) const
350 {
351  if (scaled) {
352  return output.getLogicalWidth() / 320;;
353  } else if (getParent()) {
354  return getParent()->getScaleFactor(output);
355  } else {
356  return 1;
357  }
358 }
359 
361  vec2 trPos, vec2 trRelPos) const
362 {
363  vec2 out = trPos
364  + (float(getScaleFactor(output)) * getPos())
365  + (trRelPos * getSize(output));
366  if (const auto* p = getParent()) {
367  out = p->transformPos(output, out, getRelPos());
368  }
369  return out;
370 }
371 
372 vec2 OSDWidget::transformReverse(const OutputSurface& output, vec2 trPos) const
373 {
374  if (const auto* p = getParent()) {
375  trPos = p->transformReverse(output, trPos);
376  return trPos
377  - (getRelPos() * p->getSize(output))
378  - (getPos() * float(getScaleFactor(output)));
379  } else {
380  return trPos;
381  }
382 }
383 
384 vec2 OSDWidget::getMouseCoord() const
385 {
386  auto& videoSystem = getDisplay().getVideoSystem();
387  if (!videoSystem.getCursorEnabled()) {
388  // Host cursor is not visible. Return dummy mouse coords for
389  // the OSD cursor position.
390  // The reason for doing this is that otherwise (e.g. when using
391  // the mouse in an MSX program) it's possible to accidentally
392  // click on the reversebar. This will also block the OSD mouse
393  // in other Tcl scripts (e.g. vampier's nemesis script), but
394  // almost always those scripts will also not be useful when the
395  // host mouse cursor is not visible.
396  //
397  // We need to return coordinates that lay outside any
398  // reasonable range. Initially we returned (NaN, NaN). But for
399  // some reason that didn't work on dingoo: Dingoo uses
400  // softfloat, in c++ NaN seems to behave as expected, but maybe
401  // there's a problem on the tcl side? Anyway, when we return
402  // +inf instead of NaN it does work.
403  return vec2(std::numeric_limits<float>::infinity());
404  }
405 
406  auto* output = getDisplay().getOutputSurface();
407  if (!output) {
408  throw CommandException(
409  "Can't get mouse coordinates: no window visible");
410  }
411 
412  vec2 out = transformReverse(*output, vec2(videoSystem.getMouseCoord()));
413  vec2 size = getSize(*output);
414  if ((size[0] == 0.0f) || (size[1] == 0.0f)) {
415  throw CommandException(
416  "-can't get mouse coordinates: "
417  "widget has zero width or height");
418  }
419  return out / size;
420 }
421 
423  vec2& bbPos, vec2& bbSize) const
424 {
425  vec2 topLeft = transformPos(output, vec2(), vec2(0.0f));
426  vec2 bottomRight = transformPos(output, vec2(), vec2(1.0f));
427  bbPos = topLeft;
428  bbSize = bottomRight - topLeft;
429 }
430 
431 } // namespace openmsx
constexpr TO checked_cast(FROM *from)
Based on checked_cast implementation from the book: C++ Coding Standard item 93: Avoid using static_c...
Definition: checked_cast.hh:14
Represents the output window/screen of openMSX.
Definition: Display.hh:33
VideoSystem & getVideoSystem()
Definition: Display.cc:108
OutputSurface * getOutputSurface()
Definition: Display.cc:114
GLScopedClip & operator=(const GLScopedClip &)=delete
GLScopedClip(const GLScopedClip &)=delete
GLScopedClip(OutputSurface &output, vec2 xy, vec2 wh)
Definition: OSDWidget.cc:100
virtual void getProperty(std::string_view name, TclObject &result) const
Definition: OSDWidget.cc:259
void addWidget(std::unique_ptr< OSDWidget > widget)
Definition: OSDWidget.cc:149
Display & getDisplay() const
Definition: OSDWidget.hh:67
void deleteWidget(OSDWidget &widget)
Definition: OSDWidget.cc:172
int getScaleFactor(const OutputSurface &output) const
Definition: OSDWidget.cc:349
void getBoundingBox(const OutputSurface &output, gl::vec2 &pos, gl::vec2 &size) const
Definition: OSDWidget.cc:422
gl::vec2 transformPos(const OutputSurface &output, gl::vec2 pos, gl::vec2 relPos) const
Definition: OSDWidget.cc:360
virtual void paintGL(OutputSurface &output)=0
void paintGLRecursive(OutputSurface &output)
Definition: OSDWidget.cc:330
virtual float getRecursiveFadeValue() const
Definition: OSDWidget.cc:287
virtual std::string_view getType() const =0
gl::vec2 getRelPos() const
Definition: OSDWidget.hh:34
gl::vec2 getPos() const
Definition: OSDWidget.hh:33
virtual gl::vec2 getSize(const OutputSurface &output) const =0
virtual void paintSDL(OutputSurface &output)=0
bool needSuppressErrors() const
Definition: OSDWidget.cc:305
OSDWidget(Display &display, TclObject name)
Definition: OSDWidget.cc:138
virtual void setProperty(Interpreter &interp, std::string_view name, const TclObject &value)
Definition: OSDWidget.cc:215
void paintSDLRecursive(OutputSurface &output)
Definition: OSDWidget.cc:314
virtual void invalidateLocal()=0
OSDWidget * getParent()
Definition: OSDWidget.hh:37
void invalidateRecursive()
Definition: OSDWidget.cc:292
void invalidateChildren()
Definition: OSDWidget.cc:298
float getZ() const
Definition: OSDWidget.hh:35
A frame buffer where pixels can be written to.
int getLogicalWidth() const
gl::vec2 getViewScale() const
gl::ivec2 getViewOffset() const
int getLogicalHeight() const
A frame buffer where pixels can be written to.
SDLScopedClip(const SDLScopedClip &)=delete
SDLScopedClip & operator=(const SDLScopedClip &)=delete
bool getBoolean(Interpreter &interp) const
Definition: TclObject.cc:91
double getDouble(Interpreter &interp) const
Definition: TclObject.cc:101
void addListElement(const T &t)
Definition: TclObject.hh:129
Definition: gl_mat.hh:23
mat4 rotate(float angle, const vec3 &axis)
Definition: gl_transform.hh:58
vecN< 2, float > vec2
Definition: gl_vec.hh:139
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:287
vecN< N, T > normalize(const vecN< N, T > &x)
Definition: gl_vec.hh:350
constexpr mat4 scale(const vec3 &xyz)
Definition: gl_transform.hh:19
This file implemented 3 utility functions:
Definition: Autofire.cc:9
const T & get(const Event &event)
Definition: Event.hh:725
constexpr KeyMatrixPosition x
Keyboard bindings.
Definition: Keyboard.cc:118
bool is_sorted(ForwardRange &&range, Compare comp={}, Proj proj={})
Definition: ranges.hh:25
size_t size(std::string_view utf8)
auto rfind_unguarded(RANGE &range, const VAL &val, Proj proj={})
Similar to the find(_if)_unguarded functions above, but searches from the back to front.
Definition: stl.hh:109
constexpr auto begin(const zstring_view &x)
Definition: zstring_view.hh:83
constexpr auto end(const zstring_view &x)
Definition: zstring_view.hh:84