Video timing should now be right as well.
[scummvm-innocent.git] / engines / innocent / graphics.cpp
blob811707bd9754b5dda6c7dc77ca296370d3a07d4f
1 #include "innocent/graphics.h"
3 #include <algorithm>
4 #include <functional>
6 #include "common/system.h"
7 #include "common/util.h"
8 #include "graphics/cursorman.h"
10 #include "innocent/animation.h"
11 #include "innocent/debug.h"
12 #include "innocent/debugger.h"
13 #include "innocent/exit.h"
14 #include "innocent/innocent.h"
15 #include "innocent/logic.h"
16 #include "innocent/resources.h"
17 #include "innocent/room.h"
18 #include "innocent/util.h"
20 using namespace std;
22 namespace Innocent {
24 DECLARE_SINGLETON(Graphics);
26 Common::Point &operator+=(Common::Point &p1, const Common::Point &p2) { return p1 = Common::Point(p1.x + p2.x, p1.y + p2.y); }
28 void Graphics::setEngine(Engine *engine) {
29 _engine = engine;
30 _framebuffer.reset(new Surface);
31 _framebuffer->create(320, 200, 1);
32 _willFadein = false;
34 _speech = 0;
35 _speechFramesLeft = 0;
38 void Graphics::init() {
39 _resources = _engine->resources();
40 _system = _engine->_system;
41 loadInterface();
44 void Graphics::paint() {
45 debugC(2, kDebugLevelFlow | kDebugLevelGraphics, ">>>start paint procedure");
47 paintBackdrop();
48 paintExits();
49 paintAnimations();
50 paintSpeech();
52 debugC(3, kDebugLevelGraphics, "painting paintables");
53 foreach (Paintable *, _paintables)
54 (*it)->paint(this);
56 unless (_afterRepaintHooks.empty()) {
57 debugC(3, kDebugLevelGraphics | kDebugLevelScript, "running hooks");
58 foreach (CodePointer, _afterRepaintHooks)
59 it->run();
60 _afterRepaintHooks.clear();
63 debugC(2, kDebugLevelFlow | kDebugLevelGraphics, "<<<end paint procedure");
66 void Graphics::paintExits() {
67 debugC(3, kDebugLevelFlow | kDebugLevelGraphics, "painting exits");
68 foreach(Exit *, _engine->logic()->room()->exits())
69 (*it)->paint(this);
72 void Graphics::loadInterface() {
73 _resources->loadInterfaceImage(_interface, _interfacePalette);
76 void Graphics::prepareInterfacePalette() {
77 _engine->_system->setPalette(_interfacePalette + 160 * 4, 160, 96);
80 void Graphics::paintInterface() {
81 // _framebuffer->blit(_interface, Common::Point(152, 48));
84 void Graphics::setBackdrop(uint16 id) {
85 byte palette[0x400];
86 _backdrop.reset(_resources->loadBackdrop(id, palette));
87 setPalette(palette, 0, 256);
88 paintBackdrop();
91 void Graphics::willFadein(FadeFlags f) {
92 _willFadein = true;
93 _fadeFlags = f;
94 if (f & kPartialFade)
95 clearPalette(160, 96);
96 else
97 clearPalette();
100 void Graphics::paintBackdrop() {
101 // TODO cropping
102 debugC(3, kDebugLevelGraphics, "painting backdrop");
103 _framebuffer->blit(_backdrop.get());
106 void Graphics::paintSpeech() {
107 if (!_speech) return;
109 if (!_speechFramesLeft) {
110 delete _speech;
111 _speech = 0;
112 CodePointer cb = _speechDoneCallback;
113 _speechDoneCallback.reset();
114 cb.run();
115 return;
118 paintText(0, 0, 235, _speech);
120 _speechFramesLeft--;
123 void Graphics::paintAnimations() {
124 debugC(3, kDebugLevelGraphics, "painting animations");
125 Common::List<Animation *> animations = _engine->logic()->animations();
126 for (Common::List<Animation *>::iterator it = animations.begin(); it != animations.end(); ++it)
127 (*it)->paint(this);
130 // it's modal anyway
131 static int _mOption = 0;
132 static Common::Rect _optionRects[10];
133 static uint16 _optionValues[10];
135 uint16 Graphics::ask(uint16 left, uint16 top, byte width, byte height, byte *string) {
136 width += 2;
137 height += 2;
138 enum {
139 kFrameTileHeight = 12,
140 kFrameTileWidth = 16
143 Surface frame;
144 frame.create(width * kFrameTileWidth, height * kFrameTileHeight+4, 1);
146 Sprite **frames = _resources->frames();
148 Common::Point tile(0, 0);
150 paint(frames[kFrameTopLeft], tile, &frame);
151 tile.x += kFrameTileWidth;
152 for (int x = 1; x < width - 1; x++) {
153 paint(frames[kFrameTop], tile, &frame);
154 tile.x += kFrameTileWidth;
156 paint(frames[kFrameTopRight], tile, &frame);
158 tile.y += kFrameTileHeight;
159 tile.x = 0;
161 for (int y = 1; y < height - 1; y++) {
162 paint(frames[kFrameLeft], tile, &frame);
163 tile.x += kFrameTileWidth;
164 for (int x = 1; x < width - 1; x++) {
165 paint(frames[kFrameFill], tile, &frame);
166 tile.x += kFrameTileWidth;
168 paint(frames[kFrameRight], tile, &frame);
169 tile.y += kFrameTileHeight;
170 tile.x = 0;
173 paint(frames[kFrameBottomLeft], tile, &frame);
174 tile.x += kFrameTileWidth;
175 for (int x = 1; x < width - 1; x++) {
176 paint(frames[kFrameBottom], tile, &frame);
177 tile.x += kFrameTileWidth;
179 paint(frames[kFrameBottomRight], tile, &frame);
181 _mOption = 0;
183 // TODO this should use the interpreter's built-in font
184 // (but it does look nicer this way)
185 paintText(10, 16, 254, string, &frame);
187 _system->copyRectToScreen(reinterpret_cast<byte *>(frame.pixels), frame.pitch, left, top, width * kFrameTileWidth, height * kFrameTileHeight+4);
189 bool show = true;
190 while (show) {
191 _system->updateScreen();
192 _engine->debugger()->onFrame();
193 Common::Event event;
194 while (_engine->eventMan()->pollEvent(event)) {
195 switch(event.type) {
196 case Common::EVENT_LBUTTONUP:
197 if (_mOption == 0)
198 return 0xffff;
199 else
200 for (int i = 0; i < _mOption; i++) {
201 Common::Point p = event.mouse;
202 p.x -= left;
203 p.y -= top;
204 if (_optionRects[i].contains(p))
205 return _optionValues[i];
207 default:
208 break;
211 _system->delayMillis(1000/60);
214 return 0xffff;
217 enum {
218 kOptionColour = 254,
219 kSelectedOptionColour = 227
222 Common::Rect Graphics::paintText(uint16 left, uint16 top, byte colour, byte *string, Surface *dest) {
223 byte ch = 0;
224 uint16 current_left = left;
225 uint16 current_top = top;
226 uint16 max_left = left;
227 byte current_colour = colour;
229 int opt;
230 while ((ch = *(string++))) {
231 switch(ch) {
232 case kStringMove:
233 current_left = READ_LE_UINT16(string);
234 string += 2;
235 current_top = READ_LE_UINT16(string);
236 string += 2;
237 debugC(3, kDebugLevelGraphics, "string move to %d:%d", current_left, current_top);
238 break;
239 case kStringSetColour:
240 current_colour = *(string++);
241 break;
242 case kStringDefaultColour:
243 current_colour = colour;
244 break;
245 case kStringAdvance:
246 current_left += *(string++);
247 break;
248 case kStringCenter:
249 current_left = (320 - calculateLineWidth(string))/2;
250 break;
251 case '\n':
252 case '\r':
253 current_left = left;
254 current_top += kLineHeight;
255 break;
256 case kStringMenuOption:
257 opt = _mOption++;
258 _optionRects[opt] = paintText(current_left, current_top, kOptionColour, string, dest);
259 while (*(string++));
260 _optionValues[opt] = READ_LE_UINT16(string);
261 debugC(2, kDebugLevelGraphics | kDebugLevelScript, "option value %d: 0x%x", opt, _optionValues[opt]);
262 string += 2;
263 break;
264 default:
265 current_left += paintChar(current_left, current_top, current_colour, ch, dest);
266 if (current_left > max_left)
267 max_left = current_left;
271 return Common::Rect(left, top, max_left, current_top + kLineHeight);
274 byte Graphics::clampChar(byte ch) {
275 if (ch == '#')
276 return '!';
277 if (ch < ' ' || ch > '~')
278 return '?';
279 return ch;
282 uint16 Graphics::calculateLineWidth(byte *string) const {
283 byte ch;
284 uint16 total = 0;
285 while ((ch = *(string++))) {
286 if (ch == '\n' || ch == '\r')
287 break;
288 total += getGlyphWidth(ch);
290 return total;
293 uint16 Graphics::getGlyphWidth(byte ch) const {
294 if (ch == ' ')
295 return 4;
296 else
297 return getGlyph(ch)->w-1;
300 Sprite *Graphics::getGlyph(byte ch) const {
301 // TODO perhaps cache or sth
302 ch = clampChar(ch);
303 if (ch == ' ')
304 return 0; // space has no glyph, just width 4
305 return _resources->getGlyph(ch);
309 * @returns char width
311 uint16 Graphics::paintChar(uint16 left, uint16 top, byte colour, byte ch, Surface *dest) const {
312 Sprite *glyph = getGlyph(ch);
313 int w;
314 if (glyph) {
315 glyph->recolour(colour);
316 paint(glyph, Common::Point(left, top+glyph->h), dest);
317 w = glyph->w - 1;
318 delete glyph;
319 } else return 4;
320 return w;
323 void Graphics::paint(const Sprite *sprite, Common::Point pos, Surface *dest) const {
324 debugC(4, kDebugLevelGraphics, "painting sprite at %d:%d (+%d:%d) [%dx%d]", pos.x, pos.y, sprite->_hotPoint.x, sprite->_hotPoint.y, sprite->w, sprite->h);
325 pos += sprite->_hotPoint;
327 Common::Rect r(sprite->w, sprite->h);
328 r.moveTo(pos);
329 // this is actually bottom
330 r.translate(0, -sprite->h);
332 r.clip(319, 199);
333 debugC(4, kDebugLevelGraphics, "transformed rect: %d:%d %d:%d", r.left, r.top, r.right, r.bottom);
335 dest->blit(sprite, r, 0);
338 Common::Point Graphics::cursorPosition() const {
339 debugC(1, kDebugLevelGraphics, "cursor position STUB");
340 return Common::Point(160, 100);
343 void Graphics::updateScreen() {
344 _system->copyRectToScreen(reinterpret_cast<byte *>(_framebuffer->pixels), _framebuffer->pitch, 0, 0, 320, 200);
346 if (_willFadein && (_fadeFlags & kPartialFade)) {
347 debugC(3, kDebugLevelGraphics, "performing partial fade in");
348 _willFadein = false;
349 fadeIn(_interfacePalette + 160*4, 160, 96);
350 } else if (_willFadein && !(_fadeFlags & kPartialFade)) {
351 fadeIn();
352 _willFadein = false;
355 _system->updateScreen();
358 void Graphics::showCursor() {
359 Sprite *cursor = _resources->getCursor();
360 assert(cursor->pitch == cursor->w);
361 ::Graphics::CursorManager &m = ::Graphics::CursorManager::instance();
362 m.replaceCursor(reinterpret_cast<byte *>(cursor->pixels), cursor->w, cursor->h, cursor->_hotPoint.x, cursor->_hotPoint.y, 0);
363 m.showMouse(true);
366 void Graphics::hideCursor() {
367 ::Graphics::CursorManager::instance().showMouse(false);
370 void Graphics::paintRect(const Common::Rect &r, byte colour) {
371 _framebuffer->frameRect(r, colour);
374 void Graphics::push(Paintable *p) {
375 debugC(3, kDebugLevelGraphics, "pushing to paintables");
376 _paintables.push_back(p);
379 void Graphics::pop(Paintable *p) {
380 debugC(3, kDebugLevelGraphics, "popping from paintables");
381 _paintables.remove(p);
384 void Graphics::hookAfterRepaint(CodePointer &p) {
385 _afterRepaintHooks.push_back(p);
388 const char Graphics::_charwidths[] = {
389 #include "charwidths.data"
392 void Graphics::clearPalette(int offset, int count) {
393 byte pal[0x400];
394 fill(pal, pal+0x400, 0);
395 _system->setPalette(pal, offset, count);
398 void Graphics::setPalette(const byte *colours, uint start, uint num) {
399 _system->setPalette(colours, start, num);
402 struct Tr : public unary_function<byte, byte> {
403 byte operator()(const byte &b) const { return 0xff & ((b << 1) - 63); }
406 void Graphics::fadeIn(const byte *colours, uint start, uint num) {
407 byte buf[0x400];
408 if (!colours) {
409 _system->grabPalette(buf, start, num);
410 colours = buf;
413 const int bytes = num * 4;
414 byte current[0x400];
416 fill(current, current + bytes, 0);
418 byte off = 255;
419 for (int j = 0; j < 63; j++) {
420 off -= 4;
421 for (int i = 0; i < bytes; i++)
422 current[i] = colours[i] - MIN(off, colours[i]);
424 _system->setPalette((current), start, num);
425 _system->updateScreen();
426 Eng.delay(1000/25);
428 if (Eng.escapePressed()) {
429 _system->setPalette(colours, start, num);
430 _system->updateScreen();
431 return;
436 void Graphics::fadeOut(FadeFlags f) {
437 int bytes = 0x400;
438 int offset = 0;
439 int colours = 256;
440 byte current[0x400];
442 if (f == kPartialFade) {
443 bytes = 96 * 4;
444 offset = 160;
445 colours = 96;
448 _system->grabPalette(current, offset, colours);
450 for (int j = 0; j < 63; j++) {
451 for (int i = 0; i < bytes; i++)
452 current[i] -= MIN<byte>(4, current[i]);
454 _system->setPalette((current), offset, colours);
455 _system->updateScreen();
456 Eng.delay(1000/25);
458 if (Eng.escapePressed()) {
459 Log.skipAnimation();
460 return;
466 void Graphics::say(const byte *text, uint16 length, uint16 frames) {
467 if (_speech)
468 error("queuing speech not supported yet.");
470 _speech = new byte[length];
471 memcpy(_speech, text, length);
472 _speechFramesLeft = frames;
473 paintSpeech();
476 void Graphics::runWhenSaid(const CodePointer &cb) {
477 unless (_speechDoneCallback.isEmpty())
478 error("queuing events on speech complete not supported yet");
480 _speechDoneCallback = cb;
483 } // End of namespace Innocent