openMSX
OSDImageBasedWidget.cc
Go to the documentation of this file.
2#include "OSDTopWidget.hh"
3#include "OSDGUI.hh"
4#include "Display.hh"
5#include "GLImage.hh"
6#include "TclObject.hh"
7#include "CommandException.hh"
8#include "Timer.hh"
9#include "narrow.hh"
10#include "ranges.hh"
11#include "stl.hh"
12#include "view.hh"
13#include "xrange.hh"
14#include <algorithm>
15#include <cassert>
16#include <cstdint>
17
18using namespace gl;
19
20namespace openmsx {
21
23 : OSDWidget(display_, name_)
24{
25 ranges::fill(rgba, 0x000000ff); // black, opaque
26}
27
29
30[[nodiscard]] static std::array<uint32_t, 4> get4(Interpreter& interp, const TclObject& value)
31{
32 std::array<uint32_t, 4> result;
33 auto len = value.getListLength(interp);
34 if (len == 4) {
35 for (auto i : xrange(4)) {
36 result[i] = value.getListIndex(interp, i).getInt(interp);
37 }
38 } else if (len == 1) {
39 ranges::fill(result, value.getInt(interp));
40 } else {
41 throw CommandException("Expected either 1 or 4 values.");
42 }
43 return result;
44}
46 Interpreter& interp, std::string_view propName, const TclObject& value)
47{
48 if (propName == "-rgba") {
49 std::array<uint32_t, 4> newRGBA = get4(interp, value);
50 setRGBA(newRGBA);
51 } else if (propName == "-rgb") {
52 std::array<uint32_t, 4> newRGB = get4(interp, value);
53 std::array<uint32_t, 4> newRGBA;
54 for (auto i : xrange(4)) {
55 newRGBA[i] = (rgba[i] & 0x000000ff) |
56 ((newRGB[i] << 8) & 0xffffff00);
57 }
58 setRGBA(newRGBA);
59 } else if (propName == "-alpha") {
60 std::array<uint32_t, 4> newAlpha = get4(interp, value);
61 std::array<uint32_t, 4> newRGBA;
62 for (auto i : xrange(4)) {
63 newRGBA[i] = (rgba[i] & 0xffffff00) |
64 (newAlpha[i] & 0x000000ff);
65 }
66 setRGBA(newRGBA);
67 } else if (propName == "-fadePeriod") {
68 updateCurrentFadeValue();
69 fadePeriod = value.getFloat(interp);
70 } else if (propName == "-fadeTarget") {
71 updateCurrentFadeValue();
72 fadeTarget = std::clamp(value.getFloat(interp), 0.0f, 1.0f);
73 } else if (propName == "-fadeCurrent") {
74 startFadeValue = std::clamp(value.getFloat(interp), 0.0f, 1.0f);
75 startFadeTime = Timer::getTime();
76 } else if (propName == "-scrollSpeed") {
77 scrollSpeed = std::max(0.0f, value.getFloat(interp));
78 startScrollTime = Timer::getTime();
79 } else if (propName == "-scrollPauseLeft") {
80 scrollPauseLeft = std::max(0.0f, value.getFloat(interp));
81 } else if (propName == "-scrollPauseRight") {
82 scrollPauseRight = std::max(0.0f, value.getFloat(interp));
83 } else if (propName == "-query-size") {
84 throw CommandException("-query-size property is readonly");
85 } else {
86 OSDWidget::setProperty(interp, propName, value);
87 }
88}
89
90void OSDImageBasedWidget::setRGBA(std::span<const uint32_t, 4> newRGBA)
91{
92 if (ranges::equal(rgba, newRGBA)) {
93 return; // not changed
94 }
96 ranges::copy(newRGBA, rgba);
97}
98
99static void set4(std::span<const uint32_t, 4> rgba, uint32_t mask, unsigned shift, TclObject& result)
100{
101 if (ranges::all_equal(rgba)) {
102 result = (rgba[0] & mask) >> shift;
103 } else {
104 result.addListElements(view::transform(xrange(4), [&](auto i) {
105 return int((rgba[i] & mask) >> shift);
106 }));
107 }
108}
109void OSDImageBasedWidget::getProperty(std::string_view propName, TclObject& result) const
110{
111 if (propName == "-rgba") {
112 set4(rgba, 0xffffffff, 0, result);
113 } else if (propName == "-rgb") {
114 set4(rgba, 0xffffff00, 8, result);
115 } else if (propName == "-alpha") {
116 set4(rgba, 0x000000ff, 0, result);
117 } else if (propName == "-fadePeriod") {
118 result = fadePeriod;
119 } else if (propName == "-fadeTarget") {
120 result = fadeTarget;
121 } else if (propName == "-fadeCurrent") {
122 result = getCurrentFadeValue();
123 } else if (propName == "-scrollSpeed") {
124 result = scrollSpeed;
125 } else if (propName == "-scrollPauseLeft") {
126 result = scrollPauseLeft;
127 } else if (propName == "-scrollPauseRight") {
128 result = scrollPauseRight;
129 } else if (propName == "-query-size") {
130 auto [w, h] = getRenderedSize();
131 result.addListElement(w, h);
132 } else {
133 OSDWidget::getProperty(propName, result);
134 }
135}
136
137std::optional<float> OSDImageBasedWidget::getScrollWidth() const
138{
139 if (scrollSpeed == 0.0f) return {};
140
141 const auto* parentImage = dynamic_cast<const OSDImageBasedWidget*>(getParent());
142 if (!parentImage) return {};
143
144 const auto* output = getDisplay().getOutputSurface();
145 if (!output) return {};
146
147 auto [parentPos, parentSize] = parentImage->getBoundingBox(*output);
148 auto parentWidth = parentSize.x / narrow<float>(getScaleFactor(*output));
149
150 auto thisWidth = getRenderedSize().x;
151 auto scrollWidth = thisWidth - parentWidth;
152 if (scrollWidth <= 0.0f) return {};
153
154 return scrollWidth;
155}
156
157bool OSDImageBasedWidget::isAnimating() const
158{
159 return static_cast<bool>(getScrollWidth());
160}
161
162[[nodiscard]] static float smootherStep(float x)
163{
164 // https://en.wikipedia.org/wiki/Smoothstep
165 // 6x^5 - 15x^4 + 10x^3
166 return ((6.0f * x - 15.0f) * x + 10.0f) * x * x * x;
167}
168
170{
171 // get the original position, possibly this gets modified because of scrolling
172 auto result = OSDWidget::getPos();
173
174 auto width = getScrollWidth();
175 if (!width) return result;
176
177 auto scrollTime = *width / scrollSpeed;
178 auto animationTime = 2.0f * scrollTime + scrollPauseLeft + scrollPauseRight;
179
180 // transform moment in time to animation-timestamp 't'
181 auto now = narrow_cast<float>(Timer::getTime() - startScrollTime) / 1'000'000.0f;
182 auto t = fmodf(now, animationTime);
183
184 // transform animation timestamp to position
185 float relOffsetX = [&]{
186 if (t < scrollPauseLeft) {
187 // no scrolling yet, pausing at the left
188 return 0.0f;
189 } else if (t < (scrollPauseLeft + scrollTime)) {
190 // scrolling to the left
191 return smootherStep((t - scrollPauseLeft) / scrollTime);
192 } else if (t < (scrollPauseLeft + scrollTime + scrollPauseRight)) {
193 // no scrolling yet, pausing at the right
194 return 1.0f;
195 } else {
196 // scrolling to the right
197 return smootherStep(1.0f - ((t - scrollPauseLeft - scrollTime - scrollPauseRight) / scrollTime));
198 }
199 }();
200 result.x -= *width * relOffsetX;
201 return result;
202}
203
205{
206 return ranges::all_equal(rgba, [](auto c) { return c & 0xff; });
207}
208
210{
211 return getParent()->getRecursiveFadeValue() * getCurrentFadeValue();
212}
213
215{
216 return (getFadedAlpha() != 0) || isRecursiveFading();
217}
218
219bool OSDImageBasedWidget::isFading() const
220{
221 return (startFadeValue != fadeTarget) && (fadePeriod != 0.0f);
222}
223
225{
226 if (isFading()) return true;
227 return getParent()->isRecursiveFading();
228}
229
230float OSDImageBasedWidget::getCurrentFadeValue() const
231{
232 if (!isFading()) {
233 return startFadeValue;
234 }
235 return getCurrentFadeValue(Timer::getTime());
236}
237
238float OSDImageBasedWidget::getCurrentFadeValue(uint64_t now) const
239{
240 assert(now >= startFadeTime);
241
242 auto diff = narrow<int>(now - startFadeTime); // int should be big enough
243 assert(fadePeriod != 0.0f);
244 float delta = narrow_cast<float>(diff) / (1000000.0f * fadePeriod);
245 if (startFadeValue < fadeTarget) {
246 float tmp = startFadeValue + delta;
247 if (tmp >= fadeTarget) {
248 startFadeValue = fadeTarget;
249 return startFadeValue;
250 }
251 return tmp;
252 } else {
253 float tmp = startFadeValue - delta;
254 if (tmp <= fadeTarget) {
255 startFadeValue = fadeTarget;
256 return startFadeValue;
257 }
258 return tmp;
259 }
260}
261
262void OSDImageBasedWidget::updateCurrentFadeValue()
263{
264 auto now = Timer::getTime();
265 if (isFading()) {
266 startFadeValue = getCurrentFadeValue(now);
267 }
268 startFadeTime = now;
269}
270
272{
273 error = false;
274 image.reset();
275}
276
277vec2 OSDImageBasedWidget::getTransformedPos(const OutputSurface& output) const
278{
279 return getParent()->transformPos(
280 output, float(getScaleFactor(output)) * getPos(), getRelPos());
281}
282
283void OSDImageBasedWidget::setError(std::string message)
284{
285 error = true;
286
287 // The suppressErrors property only exists to break an infinite loop
288 // when an error occurs (e.g. couldn't load font) while displaying the
289 // error message on the OSD system.
290 // The difficulty in detecting this loop is that it's not a recursive
291 // loop, but each iteration takes one frame: on the CliComm Tcl callback,
292 // the OSD widgets get created, but only the next frame, when this new
293 // widget is actually drawn the next error occurs.
294 if (!needSuppressErrors()) {
295 getDisplay().getOSDGUI().getTopWidget().queueError(std::move(message));
296 }
297}
298
300{
301 if (!image && !hasError()) {
302 try {
303 image = create(output);
304 } catch (MSXException& e) {
305 setError(std::move(e).getMessage());
306 }
307 }
308}
309
311{
312 auto* output = getDisplay().getOutputSurface();
313 if (!output) {
314 throw CommandException(
315 "Can't query size: no window visible");
316 }
317 // force creating image (does not yet draw it on screen)
318 const_cast<OSDImageBasedWidget*>(this)->createImage(*output);
319
320 vec2 imageSize = [&] {
321 if (image) {
322 return vec2(image->getSize());
323 } else {
324 // Couldn't be rendered, maybe an (intentionally)
325 // invisible rectangle
326 return getBoundingBox(*output).size;
327 }
328 }();
329 return imageSize / float(getScaleFactor(*output));
330}
331
333{
334 // Note: Even when alpha == 0 we still create the image:
335 // It may be needed to get the dimensions to be able to position
336 // child widgets.
337 createImage(output);
338
339 if (auto fadedAlpha = getFadedAlpha();
340 (fadedAlpha != 0) && image) {
341 ivec2 drawPos = round(getTransformedPos(output));
342 image->draw(drawPos, fadedAlpha);
343 }
344 if (isRecursiveFading() || isAnimating()) {
346 }
347}
348
349} // namespace openmsx
TclObject t
Represents the output window/screen of openMSX.
Definition Display.hh:31
OSDGUI & getOSDGUI()
Definition Display.hh:47
OutputSurface * getOutputSurface()
Definition Display.cc:97
void refresh() const
Definition OSDGUI.cc:29
const OSDTopWidget & getTopWidget() const
Definition OSDGUI.hh:20
virtual std::unique_ptr< GLImage > create(OutputSurface &output)=0
gl::vec2 getPos() const override
void setProperty(Interpreter &interp, std::string_view name, const TclObject &value) override
void createImage(OutputSurface &output)
virtual uint8_t getFadedAlpha() const =0
float getRecursiveFadeValue() const override
void paint(OutputSurface &output) override
bool isRecursiveFading() const override
OSDImageBasedWidget(Display &display, const TclObject &name)
std::unique_ptr< GLImage > image
void setError(std::string message)
void getProperty(std::string_view name, TclObject &result) const override
void queueError(std::string message)
virtual void getProperty(std::string_view name, TclObject &result) const
Definition OSDWidget.cc:213
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
virtual float getRecursiveFadeValue() const
Definition OSDWidget.cc:241
gl::vec2 getRelPos() const
Definition OSDWidget.hh:38
bool needSuppressErrors() const
Definition OSDWidget.cc:259
OSDWidget * getParent()
Definition OSDWidget.hh:41
virtual void setProperty(Interpreter &interp, std::string_view name, const TclObject &value)
Definition OSDWidget.cc:169
virtual bool isRecursiveFading() const =0
virtual gl::vec2 getPos() const
Definition OSDWidget.hh:37
BoundingBox getBoundingBox(const OutputSurface &output) const
Definition OSDWidget.cc:356
A frame buffer where pixels can be written to.
unsigned getListLength(Interpreter &interp) const
Definition TclObject.cc:155
TclObject getListIndex(Interpreter &interp, unsigned index) const
Definition TclObject.cc:173
float getFloat(Interpreter &interp) const
Definition TclObject.cc:107
void addListElement(const T &t)
Definition TclObject.hh:133
int getInt(Interpreter &interp) const
Definition TclObject.cc:69
Definition gl_mat.hh:23
vecN< 2, float > vec2
Definition gl_vec.hh:382
uint64_t getTime()
Get current (real) time in us.
Definition Timer.cc:7
This file implemented 3 utility functions:
Definition Autofire.cc:11
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:315
bool all_equal(InputRange &&range, Proj proj={})
Definition ranges.hh:415
constexpr bool equal(InputRange1 &&range1, InputRange2 &&range2, Pred pred={}, Proj1 proj1={}, Proj2 proj2={})
Definition ranges.hh:378
constexpr auto copy(InputRange &&range, OutputIter out)
Definition ranges.hh:252
constexpr auto transform(Range &&range, UnaryOp op)
Definition view.hh:520
constexpr auto xrange(T e)
Definition xrange.hh:132