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