openMSX
OSDWidget.cc
Go to the documentation of this file.
1#include "OSDWidget.hh"
2#include "OutputSurface.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
45
47{
48public:
49 GLScopedClip(OutputSurface& output, vec2 xy, vec2 wh);
51 GLScopedClip(const GLScopedClip&) = delete;
53private:
54 std::optional<std::array<GLint, 4>> origClip; // x, y, w, h;
55};
56
57
59{
60 auto& [x, y] = xy;
61 auto& [w, h] = wh;
62 normalize(x, w); normalize(y, h);
63 y = narrow_cast<float>(output.getLogicalHeight()) - y - h; // openGL sets (0,0) in LOWER-left corner
64
65 // transform view-space coordinates to clip-space coordinates
66 vec2 scale = output.getViewScale();
67 auto [ix, iy] = round(xy * scale) + output.getViewOffset();
68 auto [iw, ih] = round(wh * scale);
69
70 if (glIsEnabled(GL_SCISSOR_TEST) == GL_TRUE) {
71 origClip.emplace();
72 glGetIntegerv(GL_SCISSOR_BOX, origClip->data());
73 auto [xn, yn, wn, hn] = intersect(
74 (*origClip)[0], (*origClip)[1], (*origClip)[2], (*origClip)[3],
75 ix, iy, iw, ih);
76 glScissor(xn, yn, wn, hn);
77 } else {
78 glScissor(ix, iy, iw, ih);
79 glEnable(GL_SCISSOR_TEST);
80 }
81}
82
84{
85 if (origClip) {
86 glScissor((*origClip)[0], (*origClip)[1], (*origClip)[2], (*origClip)[3]);
87 } else {
88 glDisable(GL_SCISSOR_TEST);
89 }
90}
91
93
95 : display(display_)
96 , name(std::move(name_))
97{
98}
99
100void OSDWidget::addWidget(std::unique_ptr<OSDWidget> widget)
101{
102 widget->setParent(this);
103
104 // Insert the new widget in the correct place (sorted on ascending Z)
105 // heuristic: often we have either
106 // - many widgets with all the same Z
107 // - only a few total number of subWidgets (possibly with different Z)
108 // In the former case we can simply append at the end. In the latter
109 // case a linear search is probably faster than a binary search. Only
110 // when there are many sub-widgets with not all the same Z (and not
111 // created in sorted Z-order) a binary search would be faster.
112 float widgetZ = widget->getZ();
113 if (subWidgets.empty() || (subWidgets.back()->getZ() <= widgetZ)) {
114 subWidgets.push_back(std::move(widget));
115 } else {
116 auto it = begin(subWidgets);
117 while ((*it)->getZ() <= widgetZ) ++it;
118 subWidgets.insert(it, std::move(widget));
119
120 }
121}
122
124{
125 auto it = rfind_unguarded(subWidgets, &widget,
126 [](const auto& p) { return p.get(); });
127 subWidgets.erase(it);
128}
129
130void OSDWidget::resortUp(OSDWidget* elem)
131{
132 // z-coordinate was increased, first search for elements current position
133 auto it1 = begin(subWidgets);
134 while (it1->get() != elem) ++it1;
135 // next search for the position were it belongs
136 float elemZ = elem->getZ();
137 auto it2 = it1;
138 ++it2;
139 while ((it2 != end(subWidgets)) && ((*it2)->getZ() < elemZ)) ++it2;
140 // now move elements to correct position
141 rotate(it1, it1 + 1, it2);
142#ifdef DEBUG
143 assert(ranges::is_sorted(subWidgets, {}, &OSDWidget::getZ));
144#endif
145}
146void OSDWidget::resortDown(OSDWidget* elem)
147{
148 // z-coordinate was decreased, first search for new position
149 auto it1 = begin(subWidgets);
150 float elemZ = elem->getZ();
151 while ((*it1)->getZ() <= elemZ) {
152 ++it1;
153 if (it1 == end(subWidgets)) return;
154 }
155 // next search for the elements current position
156 auto it2 = it1;
157 if ((it2 != begin(subWidgets)) && ((it2 - 1)->get() == elem)) return;
158 while (it2->get() != elem) ++it2;
159 // now move elements to correct position
160 rotate(it1, it2, it2 + 1);
161#ifdef DEBUG
162 assert(ranges::is_sorted(subWidgets, {}, &OSDWidget::getZ));
163#endif
164}
165
167 Interpreter& interp, std::string_view propName, const TclObject& value)
168{
169 if (propName == "-type") {
170 throw CommandException("-type property is readonly");
171 } else if (propName == "-mousecoord") {
172 throw CommandException("-mousecoord property is readonly");
173 } else if (propName == "-x") {
174 pos[0] = value.getFloat(interp);
175 } else if (propName == "-y") {
176 pos[1] = value.getFloat(interp);
177 } else if (propName == "-z") {
178 float z2 = value.getFloat(interp);
179 if (z != z2) {
180 bool up = z2 > z; // was z increased?
181 z = z2;
182 if (auto* p = getParent()) {
183 // TODO no need for a full sort: instead remove and re-insert in the correct place
184 if (up) {
185 p->resortUp(this);
186 } else {
187 p->resortDown(this);
188 }
189 }
190 }
191 } else if (propName == "-relx") {
192 relPos[0] = value.getFloat(interp);
193 } else if (propName == "-rely") {
194 relPos[1] = value.getFloat(interp);
195 } else if (propName == "-scaled") {
196 bool scaled2 = value.getBoolean(interp);
197 if (scaled != scaled2) {
198 scaled = scaled2;
200 }
201 } else if (propName == "-clip") {
202 clip = value.getBoolean(interp);
203 } else if (propName == "-suppressErrors") {
204 suppressErrors = value.getBoolean(interp);
205 } else {
206 throw CommandException("No such property: ", propName);
207 }
208}
209
210void OSDWidget::getProperty(std::string_view propName, TclObject& result) const
211{
212 if (propName == "-type") {
213 result = getType();
214 } else if (propName == "-x") {
215 result = pos[0];
216 } else if (propName == "-y") {
217 result = pos[1];
218 } else if (propName == "-z") {
219 result = z;
220 } else if (propName == "-relx") {
221 result = relPos[0];
222 } else if (propName == "-rely") {
223 result = relPos[1];
224 } else if (propName == "-scaled") {
225 result = scaled;
226 } else if (propName == "-clip") {
227 result = clip;
228 } else if (propName == "-mousecoord") {
229 auto [x, y] = getMouseCoord();
230 result.addListElement(x, y);
231 } else if (propName == "-suppressErrors") {
232 result = suppressErrors;
233 } else {
234 throw CommandException("No such property: ", propName);
235 }
236}
237
239{
240 return 1.0f; // fully opaque
241}
242
244{
247}
248
250{
251 for (auto& s : subWidgets) {
252 s->invalidateRecursive();
253 }
254}
255
257{
258 if (suppressErrors) return true;
259 if (const auto* p = getParent()) {
260 return p->needSuppressErrors();
261 }
262 return false;
263}
264
266{
267 paint(output);
268
269 std::optional<GLScopedClip> scopedClip;
270 if (clip) {
271 vec2 clipPos, size;
272 getBoundingBox(output, clipPos, size);
273 scopedClip.emplace(output, clipPos, size);
274 }
275
276 for (auto& s : subWidgets) {
277 s->paintRecursive(output);
278 }
279}
280
282{
283 if (scaled) {
284 return output.getLogicalWidth() / 320;
285 } else if (getParent()) {
286 return getParent()->getScaleFactor(output);
287 } else {
288 return 1;
289 }
290}
291
293 vec2 trPos, vec2 trRelPos) const
294{
295 vec2 out = trPos
296 + (float(getScaleFactor(output)) * getPos())
297 + (trRelPos * getSize(output));
298 if (const auto* p = getParent()) {
299 out = p->transformPos(output, out, getRelPos());
300 }
301 return out;
302}
303
304vec2 OSDWidget::transformReverse(const OutputSurface& output, vec2 trPos) const
305{
306 if (const auto* p = getParent()) {
307 trPos = p->transformReverse(output, trPos);
308 return trPos
309 - (getRelPos() * p->getSize(output))
310 - (getPos() * float(getScaleFactor(output)));
311 } else {
312 return trPos;
313 }
314}
315
316vec2 OSDWidget::getMouseCoord() const
317{
318 auto& videoSystem = getDisplay().getVideoSystem();
319 if (!videoSystem.getCursorEnabled()) {
320 // Host cursor is not visible. Return dummy mouse coords for
321 // the OSD cursor position.
322 // The reason for doing this is that otherwise (e.g. when using
323 // the mouse in an MSX program) it's possible to accidentally
324 // click on the reverse bar. This will also block the OSD mouse
325 // in other Tcl scripts (e.g. vampier's nemesis script), but
326 // almost always those scripts will also not be useful when the
327 // host mouse cursor is not visible.
328 //
329 // We need to return coordinates that lay outside any
330 // reasonable range. Initially we returned (NaN, NaN). But for
331 // some reason that didn't work on dingoo: Dingoo uses
332 // softfloat, in c++ NaN seems to behave as expected, but maybe
333 // there's a problem on the tcl side? Anyway, when we return
334 // +inf instead of NaN it does work.
335 return vec2(std::numeric_limits<float>::infinity());
336 }
337
338 auto* output = getDisplay().getOutputSurface();
339 if (!output) {
340 throw CommandException(
341 "Can't get mouse coordinates: no window visible");
342 }
343
344 vec2 out = transformReverse(*output, vec2(videoSystem.getMouseCoord()));
345 vec2 size = getSize(*output);
346 if ((size[0] == 0.0f) || (size[1] == 0.0f)) {
347 throw CommandException(
348 "-can't get mouse coordinates: "
349 "widget has zero width or height");
350 }
351 return out / size;
352}
353
355 vec2& bbPos, vec2& bbSize) const
356{
357 vec2 topLeft = transformPos(output, vec2(), vec2(0.0f));
358 vec2 bottomRight = transformPos(output, vec2(), vec2(1.0f));
359 bbPos = topLeft;
360 bbSize = bottomRight - topLeft;
361}
362
363} // namespace openmsx
Represents the output window/screen of openMSX.
Definition: Display.hh:32
VideoSystem & getVideoSystem()
Definition: Display.cc:94
OutputSurface * getOutputSurface()
Definition: Display.cc:100
GLScopedClip(const GLScopedClip &)=delete
GLScopedClip(OutputSurface &output, vec2 xy, vec2 wh)
Definition: OSDWidget.cc:58
GLScopedClip & operator=(const GLScopedClip &)=delete
virtual void getProperty(std::string_view name, TclObject &result) const
Definition: OSDWidget.cc:210
void addWidget(std::unique_ptr< OSDWidget > widget)
Definition: OSDWidget.cc:100
void deleteWidget(OSDWidget &widget)
Definition: OSDWidget.cc:123
Display & getDisplay() const
Definition: OSDWidget.hh:68
int getScaleFactor(const OutputSurface &output) const
Definition: OSDWidget.cc:281
void getBoundingBox(const OutputSurface &output, gl::vec2 &pos, gl::vec2 &size) const
Definition: OSDWidget.cc:354
gl::vec2 transformPos(const OutputSurface &output, gl::vec2 pos, gl::vec2 relPos) const
Definition: OSDWidget.cc:292
virtual float getRecursiveFadeValue() const
Definition: OSDWidget.cc:238
virtual void paint(OutputSurface &output)=0
virtual std::string_view getType() const =0
gl::vec2 getRelPos() const
Definition: OSDWidget.hh:36
virtual gl::vec2 getSize(const OutputSurface &output) const =0
void paintRecursive(OutputSurface &output)
Definition: OSDWidget.cc:265
bool needSuppressErrors() const
Definition: OSDWidget.cc:256
OSDWidget(Display &display, TclObject name)
Definition: OSDWidget.cc:94
OSDWidget * getParent()
Definition: OSDWidget.hh:39
virtual void setProperty(Interpreter &interp, std::string_view name, const TclObject &value)
Definition: OSDWidget.cc:166
virtual void invalidateLocal()=0
void invalidateRecursive()
Definition: OSDWidget.cc:243
void invalidateChildren()
Definition: OSDWidget.cc:249
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
bool getBoolean(Interpreter &interp) const
Definition: TclObject.cc:89
float getFloat(Interpreter &interp) const
Definition: TclObject.cc:108
void addListElement(const T &t)
Definition: TclObject.hh:127
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
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:109
constexpr auto begin(const zstring_view &x)
constexpr auto end(const zstring_view &x)