openMSX
OSDImageBasedWidget.cc
Go to the documentation of this file.
1 #include "OSDImageBasedWidget.hh"
2 #include "OSDGUI.hh"
3 #include "BaseImage.hh"
4 #include "OutputSurface.hh"
5 #include "Display.hh"
6 #include "CliComm.hh"
7 #include "TclObject.hh"
8 #include "CommandException.hh"
9 #include "Timer.hh"
10 #include "xrange.hh"
11 #include <cassert>
12 
13 using std::string;
14 using std::vector;
15 
16 namespace openmsx {
17 
18 OSDImageBasedWidget::OSDImageBasedWidget(const OSDGUI& gui_, const string& name)
19  : OSDWidget(name)
20  , gui(gui_)
21  , startFadeTime(0)
22  , fadePeriod(0.0)
23  , fadeTarget(1.0)
24  , startFadeValue(1.0)
25  , error(false)
26 {
27  for (auto i : xrange(4)) {
28  rgba[i] = 0x000000ff;
29  }
30 }
31 
33 {
34 }
35 
36 vector<string_ref> OSDImageBasedWidget::getProperties() const
37 {
38  auto result = OSDWidget::getProperties();
39  static const char* const vals[] = {
40  "-rgba", "-rgb", "-alpha", "-fadePeriod", "-fadeTarget",
41  "-fadeCurrent",
42  };
43  result.insert(result.end(), std::begin(vals), std::end(vals));
44  return result;
45 }
46 
47 static void get4(const TclObject& value, unsigned* result)
48 {
49  unsigned len = value.getListLength();
50  if (len == 4) {
51  for (auto i : xrange(4)) {
52  result[i] = value.getListIndex(i).getInt();
53  }
54  } else if (len == 1) {
55  unsigned val = value.getInt();
56  for (auto i : xrange(4)) {
57  result[i] = val;
58  }
59  } else {
60  throw CommandException("Expected either 1 or 4 values.");
61  }
62 }
64 {
65  if (name == "-rgba") {
66  unsigned newRGBA[4];
67  get4(value, newRGBA);
68  setRGBA(newRGBA);
69  } else if (name == "-rgb") {
70  unsigned newRGB[4];
71  get4(value, newRGB);
72  unsigned newRGBA[4];
73  for (auto i : xrange(4)) {
74  newRGBA[i] = (rgba[i] & 0x000000ff) |
75  ((newRGB[i] << 8) & 0xffffff00);
76  }
77  setRGBA(newRGBA);
78  } else if (name == "-alpha") {
79  unsigned newAlpha[4];
80  get4(value, newAlpha);
81  unsigned newRGBA[4];
82  for (auto i : xrange(4)) {
83  newRGBA[i] = (rgba[i] & 0xffffff00) |
84  (newAlpha[i] & 0x000000ff);
85  }
86  setRGBA(newRGBA);
87  } else if (name == "-fadePeriod") {
88  updateCurrentFadeValue();
89  fadePeriod = value.getDouble();
90  } else if (name == "-fadeTarget") {
91  updateCurrentFadeValue();
92  fadeTarget = std::max(0.0, std::min(1.0 , value.getDouble()));
93  } else if (name == "-fadeCurrent") {
94  startFadeValue = std::max(0.0, std::min(1.0, value.getDouble()));
95  startFadeTime = Timer::getTime();
96  } else {
97  OSDWidget::setProperty(name, value);
98  }
99 }
100 
101 void OSDImageBasedWidget::setRGBA(const unsigned newRGBA[4])
102 {
103  if ((rgba[0] == newRGBA[0]) &&
104  (rgba[1] == newRGBA[1]) &&
105  (rgba[2] == newRGBA[2]) &&
106  (rgba[3] == newRGBA[3])) {
107  // not changed
108  return;
109  }
110  invalidateLocal();
111  for (auto i : xrange(4)) {
112  rgba[i] = newRGBA[i];
113  }
114 }
115 
116 static void set4(const unsigned rgba[4], unsigned mask, unsigned shift, TclObject& result)
117 {
118  if ((rgba[0] == rgba[1]) && (rgba[0] == rgba[2]) && (rgba[0] == rgba[3])) {
119  result.setInt((rgba[0] & mask) >> shift);
120  } else {
121 
122  for (auto i : xrange(4)) {
123  result.addListElement(int((rgba[i] & mask) >> shift));
124  }
125  }
126 }
128 {
129  if (name == "-rgba") {
130  set4(rgba, 0xffffffff, 0, result);
131  } else if (name == "-rgb") {
132  set4(rgba, 0xffffff00, 8, result);
133  } else if (name == "-alpha") {
134  set4(rgba, 0x000000ff, 0, result);
135  } else if (name == "-fadePeriod") {
136  result.setDouble(fadePeriod);
137  } else if (name == "-fadeTarget") {
138  result.setDouble(fadeTarget);
139  } else if (name == "-fadeCurrent") {
140  result.setDouble(getCurrentFadeValue());
141  } else {
142  OSDWidget::getProperty(name, result);
143  }
144 }
145 
146 static bool constantAlpha(const unsigned rgba[4])
147 {
148  return ((rgba[0] & 0xff) == (rgba[1] & 0xff)) &&
149  ((rgba[0] & 0xff) == (rgba[2] & 0xff)) &&
150  ((rgba[0] & 0xff) == (rgba[3] & 0xff));
151 }
153 {
154  return constantAlpha(rgba);
155 }
156 
158 {
159  return getParent()->getRecursiveFadeValue() * getCurrentFadeValue();
160 }
161 
162 bool OSDImageBasedWidget::isFading() const
163 {
164  return (startFadeValue != fadeTarget) && (fadePeriod != 0.0);
165 }
166 
167 double OSDImageBasedWidget::getCurrentFadeValue() const
168 {
169  if (!isFading()) {
170  return startFadeValue;
171  }
172  return getCurrentFadeValue(Timer::getTime());
173 }
174 
175 double OSDImageBasedWidget::getCurrentFadeValue(uint64_t now) const
176 {
177  assert(now >= startFadeTime);
178 
179  int diff = int(now - startFadeTime); // int should be big enough
180  assert(fadePeriod != 0.0);
181  double delta = diff / (1000000.0 * fadePeriod);
182  if (startFadeValue < fadeTarget) {
183  double tmp = startFadeValue + delta;
184  if (tmp >= fadeTarget) {
185  startFadeValue = fadeTarget;
186  return startFadeValue;
187  }
188  return tmp;
189  } else {
190  double tmp = startFadeValue - delta;
191  if (tmp <= fadeTarget) {
192  startFadeValue = fadeTarget;
193  return startFadeValue;
194  }
195  return tmp;
196  }
197 }
198 
199 void OSDImageBasedWidget::updateCurrentFadeValue()
200 {
201  auto now = Timer::getTime();
202  if (isFading()) {
203  startFadeValue = getCurrentFadeValue(now);
204  }
205  startFadeTime = now;
206 }
207 
209 {
210  error = false;
211  image.reset();
212 }
213 
214 void OSDImageBasedWidget::getTransformedXY(const OutputRectangle& output,
215  double& outx, double& outy) const
216 {
217  const OSDWidget* parent = getParent();
218  assert(parent);
219  int factor = getScaleFactor(output);
220  double x = factor * getX();
221  double y = factor * getY();
222  parent->transformXY(output, x, y, getRelX(), getRelY(), outx, outy);
223 }
224 
225 void OSDImageBasedWidget::setError(const string& message)
226 {
227  error = true;
228 
229  // The suppressErrors property only exists to break an infinite loop
230  // when an error occurs (e.g. couldn't load font) while displaying the
231  // error message on the OSD system.
232  // The difficulty in detecting this loop is that it's not a recursive
233  // loop, but each iteration takes one frame: on the CliComm Tcl callback,
234  // the OSD widgets get created, but only the next frame, when this new
235  // widget is actually drawn the next error occurs.
236  if (!needSuppressErrors()) {
237  gui.getDisplay().getCliComm().printWarning(message);
238  }
239 }
240 
242 {
243  paint(output, false);
244 }
245 
247 {
248  paint(output, true);
249 }
250 
252 {
253  if (!image.get() && !hasError()) {
254  try {
255  if (gui.isOpenGL()) {
256  image = createGL(output);
257  } else {
258  image = createSDL(output);
259  }
260  } catch (MSXException& e) {
261  setError(e.getMessage());
262  }
263  }
264 }
265 
266 void OSDImageBasedWidget::paint(OutputSurface& output, bool openGL)
267 {
268  // Note: Even when alpha == 0 we still create the image:
269  // It may be needed to get the dimensions to be able to position
270  // child widgets.
271  assert(openGL == gui.isOpenGL()); (void)openGL;
272  createImage(output);
273 
274  byte fadedAlpha = getFadedAlpha();
275  if ((fadedAlpha != 0) && image.get()) {
276  double x, y;
277  getTransformedXY(output, x, y);
278  image->draw(output, int(x + 0.5), int(y + 0.5), fadedAlpha);
279  }
280  if (isFading()) {
281  gui.refresh();
282  }
283 }
284 
285 } // namespace openmsx