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 "checked_cast.hh"
11#include "narrow.hh"
12#include "ranges.hh"
13#include "stl.hh"
14
15#include <SDL.h>
16
17#include <array>
18#include <limits>
19#include <optional>
20
21using namespace gl;
22
23namespace openmsx {
24
25// intersect two rectangles
26struct IntersectResult { int x, y, w, h; };
27static constexpr IntersectResult intersect(int xa, int ya, int wa, int ha,
28 int xb, int yb, int wb, int hb)
29{
30 int x1 = std::max<int>(xa, xb);
31 int y1 = std::max<int>(ya, yb);
32 int x2 = std::min<int>(xa + wa, xb + wb);
33 int y2 = std::min<int>(ya + ha, yb + hb);
34 int w = std::max(0, x2 - x1);
35 int h = std::max(0, y2 - y1);
36 return {x1, y1, w, h};
37}
38
40template<typename T>
41static constexpr void normalize(T& x, T& w)
42{
43 if (w < 0) {
44 w = -w;
45 x -= w;
46 }
47}
48
49
51{
52public:
53 GLScopedClip(const OutputSurface& output, vec2 xy, vec2 wh);
55 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 vec2 clipPos, size;
276 getBoundingBox(output, clipPos, size);
277 scopedClip.emplace(output, clipPos, size);
278 }
279
280 for (auto& s : subWidgets) {
281 s->paintRecursive(output);
282 }
283}
284
286{
287 if (scaled) {
288 return output.getLogicalWidth() / 320;
289 } else if (getParent()) {
290 return getParent()->getScaleFactor(output);
291 } else {
292 return 1;
293 }
294}
295
297 vec2 trPos, vec2 trRelPos) const
298{
299 vec2 out = trPos
300 + (float(getScaleFactor(output)) * getPos())
301 + (trRelPos * getSize(output));
302 if (const auto* p = getParent()) {
303 out = p->transformPos(output, out, getRelPos());
304 }
305 return out;
306}
307
308vec2 OSDWidget::transformReverse(const OutputSurface& output, vec2 trPos) const
309{
310 if (const auto* p = getParent()) {
311 trPos = p->transformReverse(output, trPos);
312 return trPos
313 - (getRelPos() * p->getSize(output))
314 - (getPos() * float(getScaleFactor(output)));
315 } else {
316 return trPos;
317 }
318}
319
320vec2 OSDWidget::getMouseCoord() const
321{
322 auto& videoSystem = getDisplay().getVideoSystem();
323 if (!videoSystem.getCursorEnabled()) {
324 // Host cursor is not visible. Return dummy mouse coords for
325 // the OSD cursor position.
326 // The reason for doing this is that otherwise (e.g. when using
327 // the mouse in an MSX program) it's possible to accidentally
328 // click on the reverse bar. This will also block the OSD mouse
329 // in other Tcl scripts (e.g. vampier's nemesis script), but
330 // almost always those scripts will also not be useful when the
331 // host mouse cursor is not visible.
332 //
333 // We need to return coordinates that lay outside any
334 // reasonable range. Initially we returned (NaN, NaN). But for
335 // some reason that didn't work on dingoo: Dingoo uses
336 // softfloat, in c++ NaN seems to behave as expected, but maybe
337 // there's a problem on the tcl side? Anyway, when we return
338 // +inf instead of NaN it does work.
339 return vec2(std::numeric_limits<float>::infinity());
340 }
341
342 auto* output = getDisplay().getOutputSurface();
343 if (!output) {
344 throw CommandException(
345 "Can't get mouse coordinates: no window visible");
346 }
347
348 vec2 out = transformReverse(*output, vec2(videoSystem.getMouseCoord()));
349 vec2 size = getSize(*output);
350 if ((size.x == 0.0f) || (size.y == 0.0f)) {
351 throw CommandException(
352 "-can't get mouse coordinates: "
353 "widget has zero width or height");
354 }
355 return out / size;
356}
357
359 vec2& bbPos, vec2& bbSize) const
360{
361 vec2 topLeft = transformPos(output, vec2(), vec2(0.0f));
362 vec2 bottomRight = transformPos(output, vec2(), vec2(1.0f));
363 bbPos = topLeft;
364 bbSize = bottomRight - topLeft;
365}
366
367} // 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(const GLScopedClip &)=delete
GLScopedClip(const OutputSurface &output, vec2 xy, vec2 wh)
Definition OSDWidget.cc:62
GLScopedClip & operator=(const 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:285
void getBoundingBox(const OutputSurface &output, gl::vec2 &pos, gl::vec2 &size) const
Definition OSDWidget.cc:358
gl::vec2 transformPos(const OutputSurface &output, gl::vec2 pos, gl::vec2 relPos) const
Definition OSDWidget.cc:296
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
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)