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