openMSX
OSDImageBasedWidget.cc
Go to the documentation of this file.
2#include "OSDTopWidget.hh"
3#include "OSDGUI.hh"
4#include "BaseImage.hh"
5#include "Display.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 auto* output = getDisplay().getOutputSurface();
145 if (!output) return {};
146
147 vec2 parentPos, parentSize;
148 parentImage->getBoundingBox(*output, parentPos, parentSize);
149 auto parentWidth = parentSize[0] / narrow<float>(getScaleFactor(*output));
150
151 auto thisWidth = getRenderedSize()[0];
152 auto scrollWidth = thisWidth - parentWidth;
153 if (scrollWidth <= 0.0f) return {};
154
155 return scrollWidth;
156}
157
158bool OSDImageBasedWidget::isAnimating() const
159{
160 return static_cast<bool>(getScrollWidth());
161}
162
163[[nodiscard]] static float smootherStep(float x)
164{
165 // https://en.wikipedia.org/wiki/Smoothstep
166 // 6x^5 - 15x^4 + 10x^3
167 return ((6.0f * x - 15.0f) * x + 10.0f) * x * x * x;
168}
169
171{
172 // get the original position, possibly this gets modified because of scrolling
173 auto result = OSDWidget::getPos();
174
175 auto width = getScrollWidth();
176 if (!width) return result;
177
178 auto scrollTime = *width / scrollSpeed;
179 auto animationTime = 2.0f * scrollTime + scrollPauseLeft + scrollPauseRight;
180
181 // transform moment in time to animation-timestamp 't'
182 auto now = narrow_cast<float>(Timer::getTime() - startScrollTime) / 1'000'000.0f;
183 auto t = fmodf(now, animationTime);
184
185 // transform animation timestamp to position
186 float relOffsetX = [&]{
187 if (t < scrollPauseLeft) {
188 // no scrolling yet, pausing at the left
189 return 0.0f;
190 } else if (t < (scrollPauseLeft + scrollTime)) {
191 // scrolling to the left
192 return smootherStep((t - scrollPauseLeft) / scrollTime);
193 } else if (t < (scrollPauseLeft + scrollTime + scrollPauseRight)) {
194 // no scrolling yet, pausing at the right
195 return 1.0f;
196 } else {
197 // scrolling to the right
198 return smootherStep(1.0f - ((t - scrollPauseLeft - scrollTime - scrollPauseRight) / scrollTime));
199 }
200 }();
201 result[0] -= *width * relOffsetX;
202 return result;
203}
204
206{
207 return ranges::all_equal(rgba, [](auto c) { return c & 0xff; });
208}
209
211{
212 return getParent()->getRecursiveFadeValue() * getCurrentFadeValue();
213}
214
216{
217 return (getFadedAlpha() != 0) || isRecursiveFading();
218}
219
220bool OSDImageBasedWidget::isFading() const
221{
222 return (startFadeValue != fadeTarget) && (fadePeriod != 0.0f);
223}
224
226{
227 if (isFading()) return true;
228 return getParent()->isRecursiveFading();
229}
230
231float OSDImageBasedWidget::getCurrentFadeValue() const
232{
233 if (!isFading()) {
234 return startFadeValue;
235 }
236 return getCurrentFadeValue(Timer::getTime());
237}
238
239float OSDImageBasedWidget::getCurrentFadeValue(uint64_t now) const
240{
241 assert(now >= startFadeTime);
242
243 auto diff = narrow<int>(now - startFadeTime); // int should be big enough
244 assert(fadePeriod != 0.0f);
245 float delta = narrow_cast<float>(diff) / (1000000.0f * fadePeriod);
246 if (startFadeValue < fadeTarget) {
247 float tmp = startFadeValue + delta;
248 if (tmp >= fadeTarget) {
249 startFadeValue = fadeTarget;
250 return startFadeValue;
251 }
252 return tmp;
253 } else {
254 float tmp = startFadeValue - delta;
255 if (tmp <= fadeTarget) {
256 startFadeValue = fadeTarget;
257 return startFadeValue;
258 }
259 return tmp;
260 }
261}
262
263void OSDImageBasedWidget::updateCurrentFadeValue()
264{
265 auto now = Timer::getTime();
266 if (isFading()) {
267 startFadeValue = getCurrentFadeValue(now);
268 }
269 startFadeTime = now;
270}
271
273{
274 error = false;
275 image.reset();
276}
277
278vec2 OSDImageBasedWidget::getTransformedPos(const OutputSurface& output) const
279{
280 return getParent()->transformPos(
281 output, float(getScaleFactor(output)) * getPos(), getRelPos());
282}
283
284void OSDImageBasedWidget::setError(std::string message)
285{
286 error = true;
287
288 // The suppressErrors property only exists to break an infinite loop
289 // when an error occurs (e.g. couldn't load font) while displaying the
290 // error message on the OSD system.
291 // The difficulty in detecting this loop is that it's not a recursive
292 // loop, but each iteration takes one frame: on the CliComm Tcl callback,
293 // the OSD widgets get created, but only the next frame, when this new
294 // widget is actually drawn the next error occurs.
295 if (!needSuppressErrors()) {
296 getDisplay().getOSDGUI().getTopWidget().queueError(std::move(message));
297 }
298}
299
301{
302 paint(output, false);
303}
304
306{
307 paint(output, true);
308}
309
311{
312 if (!image && !hasError()) {
313 try {
314 if (getDisplay().getOSDGUI().isOpenGL()) {
315 image = createGL(output);
316 } else {
317 image = createSDL(output);
318 }
319 } catch (MSXException& e) {
320 setError(std::move(e).getMessage());
321 }
322 }
323}
324
326{
327 auto* output = getDisplay().getOutputSurface();
328 if (!output) {
329 throw CommandException(
330 "Can't query size: no window visible");
331 }
332 // force creating image (does not yet draw it on screen)
333 const_cast<OSDImageBasedWidget*>(this)->createImage(*output);
334
335 vec2 imageSize = [&] {
336 if (image) {
337 return vec2(image->getSize());
338 } else {
339 // Couldn't be rendered, maybe an (intentionally)
340 // invisible rectangle
341 vec2 dummyPos, size;
342 getBoundingBox(*output, dummyPos, size);
343 return size;
344 }
345 }();
346 return imageSize / float(getScaleFactor(*output));
347}
348
349void OSDImageBasedWidget::paint(OutputSurface& output, bool openGL)
350{
351 // Note: Even when alpha == 0 we still create the image:
352 // It may be needed to get the dimensions to be able to position
353 // child widgets.
354 assert(openGL == getDisplay().getOSDGUI().isOpenGL()); (void)openGL;
355 createImage(output);
356
357 auto fadedAlpha = getFadedAlpha();
358 if ((fadedAlpha != 0) && image) {
359 ivec2 drawPos = round(getTransformedPos(output));
360 image->draw(output, drawPos, fadedAlpha);
361 }
362 if (isRecursiveFading() || isAnimating()) {
364 }
365}
366
367} // namespace openmsx
TclObject t
Represents the output window/screen of openMSX.
Definition: Display.hh:33
OSDGUI & getOSDGUI()
Definition: Display.hh:45
OutputSurface * getOutputSurface()
Definition: Display.cc:113
void refresh() const
Definition: OSDGUI.cc:26
const OSDTopWidget & getTopWidget() const
Definition: OSDGUI.hh:19
gl::vec2 getPos() const override
void setProperty(Interpreter &interp, std::string_view name, const TclObject &value) override
virtual std::unique_ptr< BaseImage > createSDL(OutputSurface &output)=0
std::unique_ptr< BaseImage > image
void createImage(OutputSurface &output)
virtual uint8_t getFadedAlpha() const =0
float getRecursiveFadeValue() const override
bool isRecursiveFading() const override
OSDImageBasedWidget(Display &display, const TclObject &name)
void paintGL(OutputSurface &output) override
virtual std::unique_ptr< BaseImage > createGL(OutputSurface &output)=0
void setError(std::string message)
void getProperty(std::string_view name, TclObject &result) const override
void paintSDL(OutputSurface &output) override
void queueError(std::string message)
Definition: OSDTopWidget.cc:49
virtual void getProperty(std::string_view name, TclObject &result) const
Definition: OSDWidget.cc:255
Display & getDisplay() const
Definition: OSDWidget.hh:69
int getScaleFactor(const OutputSurface &output) const
Definition: OSDWidget.cc:345
void getBoundingBox(const OutputSurface &output, gl::vec2 &pos, gl::vec2 &size) const
Definition: OSDWidget.cc:418
gl::vec2 transformPos(const OutputSurface &output, gl::vec2 pos, gl::vec2 relPos) const
Definition: OSDWidget.cc:356
virtual float getRecursiveFadeValue() const
Definition: OSDWidget.cc:283
gl::vec2 getRelPos() const
Definition: OSDWidget.hh:36
bool needSuppressErrors() const
Definition: OSDWidget.cc:301
OSDWidget * getParent()
Definition: OSDWidget.hh:39
virtual void setProperty(Interpreter &interp, std::string_view name, const TclObject &value)
Definition: OSDWidget.cc:211
virtual bool isRecursiveFading() const =0
virtual gl::vec2 getPos() const
Definition: OSDWidget.hh:35
A frame buffer where pixels can be written to.
unsigned getListLength(Interpreter &interp) const
Definition: TclObject.cc:134
TclObject getListIndex(Interpreter &interp, unsigned index) const
Definition: TclObject.cc:152
float getFloat(Interpreter &interp) const
Definition: TclObject.cc:102
void addListElement(const T &t)
Definition: TclObject.hh:128
int getInt(Interpreter &interp) const
Definition: TclObject.cc:73
constexpr double e
Definition: Math.hh:20
Definition: gl_mat.hh:23
vecN< 2, float > vec2
Definition: gl_vec.hh:149
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:284
constexpr vecN< N, T > clamp(const vecN< N, T > &x, const vecN< N, T > &minVal, const vecN< N, T > &maxVal)
Definition: gl_vec.hh:293
uint64_t getTime()
Get current (real) time in us.
Definition: Timer.cc:7
This file implemented 3 utility functions:
Definition: Autofire.cc:9
constexpr void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:287
bool all_equal(InputRange &&range, Proj proj={})
Definition: ranges.hh:380
auto copy(InputRange &&range, OutputIter out)
Definition: ranges.hh:232
bool equal(InputRange1 &&range1, InputRange2 &&range2, Pred pred={}, Proj1 proj1={}, Proj2 proj2={})
Definition: ranges.hh:343
size_t size(std::string_view utf8)
constexpr auto transform(Range &&range, UnaryOp op)
Definition: view.hh:458
constexpr auto xrange(T e)
Definition: xrange.hh:133