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