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