1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "common/system.h"
24
25 #include "cryomni3d/cryomni3d.h"
26
27 #include "cryomni3d/versailles/toolbar.h"
28
29 namespace CryOmni3D {
30 namespace Versailles {
31
init(const Sprites * sprites,FontManager * fontManager,const Common::Array<Common::String> * messages,Inventory * inventory,CryOmni3DEngine * engine)32 void Toolbar::init(const Sprites *sprites, FontManager *fontManager,
33 const Common::Array<Common::String> *messages, Inventory *inventory,
34 CryOmni3DEngine *engine) {
35 _sprites = sprites;
36 _fontManager = fontManager;
37 _messages = messages;
38 _inventory = inventory;
39 _engine = engine;
40
41 _bgSurface.create(640, 60, Graphics::PixelFormat::createFormatCLUT8());
42 _destSurface.create(640, 60, Graphics::PixelFormat::createFormatCLUT8());
43
44 // Inventory
45 addZone(51, 56, Common::Point(211, 8), &Toolbar::callbackInventory<0>);
46 addZone(51, 56, Common::Point(258, 8), &Toolbar::callbackInventory<1>);
47 addZone(51, 56, Common::Point(305, 8), &Toolbar::callbackInventory<2>);
48 addZone(51, 56, Common::Point(352, 8), &Toolbar::callbackInventory<3>);
49 addZone(51, 56, Common::Point(399, 8), &Toolbar::callbackInventory<4>);
50 addZone(51, 56, Common::Point(446, 8), &Toolbar::callbackInventory<5>);
51 addZone(51, 56, Common::Point(493, 8), &Toolbar::callbackInventory<6>);
52 addZone(51, 56, Common::Point(540, 8), &Toolbar::callbackInventory<7>);
53
54 // Documentation
55 const Graphics::Cursor &cursorDoc = _sprites->getCursor(133);
56 Common::Point docPos(627 - cursorDoc.getWidth(), 42 - cursorDoc.getHeight());
57 addZone(133, 137, docPos, &Toolbar::callbackDocumentation);
58
59 // Options
60 const Graphics::Cursor &cursorOpt = _sprites->getCursor(225);
61 Common::Point optPos(0, 60 - cursorOpt.getHeight());
62 addZone(225, 225, optPos, &Toolbar::callbackOptions);
63
64 // Previous or next
65 addZone(183, uint16(-1), Common::Point(190, 18), &Toolbar::callbackInventoryPrev);
66 addZone(240, uint16(-1), Common::Point(574, 18), &Toolbar::callbackInventoryNext);
67 // View
68 addZone(142, uint16(-1), Common::Point(158, 12), &Toolbar::callbackViewObject);
69 }
70
~Toolbar()71 Toolbar::~Toolbar() {
72 _bgSurface.free();
73 _destSurface.free();
74 }
75
inventoryChanged(uint newPosition)76 void Toolbar::inventoryChanged(uint newPosition) {
77 if (newPosition != uint(-1) && newPosition > _inventoryOffset) {
78 _inventoryOffset = newPosition - 7;
79 }
80 // Refresh
81 updateZones();
82 }
83
addZone(uint16 cursorMainId,uint16 cursorSecondaryId,Common::Point position,ZoneCallback callback)84 void Toolbar::addZone(uint16 cursorMainId, uint16 cursorSecondaryId, Common::Point position,
85 ZoneCallback callback) {
86 const Graphics::Cursor &cursorMain = _sprites->getCursor(cursorMainId);
87 Common::Rect rct(cursorMain.getWidth(), cursorMain.getHeight());
88 rct.moveTo(position);
89
90 // By default it's the secondary image
91 Zone zone = { rct, cursorMainId, cursorSecondaryId, callback, true, false };
92 _zones.push_back(zone);
93 }
94
hitTestZones(const Common::Point & mousePos) const95 Common::Array<Toolbar::Zone>::const_iterator Toolbar::hitTestZones(const Common::Point &mousePos)
96 const {
97 Common::Array<Zone>::const_iterator it;
98 for (it = _zones.begin(); it != _zones.end(); it++) {
99 if (!it->hidden && it->rect.contains(mousePos) && it->callback) {
100 break;
101 }
102 }
103 return it;
104 }
105
captureEvent(const Common::Point & mousePos,uint dragStatus)106 uint Toolbar::captureEvent(const Common::Point &mousePos, uint dragStatus) {
107 uint result = 0;
108 Common::Array<Zone>::const_iterator it = hitTestZones(mousePos);
109 if (it != _zones.end()) {
110 result = (this->*(it->callback))(dragStatus);
111 }
112 return result;
113 }
114
updateZones()115 void Toolbar::updateZones() {
116 _zones[8].secondary = !_engine->hasPlaceDocumentation();
117
118 Inventory::const_iterator inventoryIt, inventorySelectedIt;
119 if (!_inventoryEnabled) {
120 _inventoryMaxOffset = 0;
121 _inventoryOffset = 0;
122 _zones[10].secondary = true;
123 _zones[11].secondary = true;
124 inventoryIt = _inventory->end();
125 inventorySelectedIt = _inventory->end();
126 } else {
127 _inventoryMaxOffset = 0;
128 // Find an object in inventory after the 8 first
129 for (inventoryIt = _inventory->begin() + 8; inventoryIt != _inventory->end(); inventoryIt++) {
130 if (*inventoryIt != nullptr) {
131 _inventoryMaxOffset = (inventoryIt - _inventory->begin()) - 7;
132 }
133 }
134 _zones[10].secondary = !_inventoryMaxOffset;
135 _zones[11].secondary = !_inventoryMaxOffset;
136 if (_inventoryOffset > _inventoryMaxOffset) {
137 // Clamp inventory offset to its max
138 _inventoryOffset = _inventoryMaxOffset;
139 }
140 inventoryIt = _inventory->begin() + _inventoryOffset;
141 inventorySelectedIt = _inventory->begin() + _inventorySelected;
142 }
143 // Inventory zones are from 0 to 7
144 for (Common::Array<Zone>::iterator zoneIt = _zones.begin(); zoneIt != _zones.begin() + 8;
145 zoneIt++, inventoryIt++) {
146 if (!_inventoryEnabled) {
147 zoneIt->hidden = true;
148 zoneIt->imageMain = 0;
149 zoneIt->imageSecondary = 0;
150 zoneIt->secondary = false;
151 } else if (inventoryIt >= _inventory->end() || *inventoryIt == nullptr) {
152 // Nothing in inventory at this position
153 zoneIt->hidden = false;
154 zoneIt->imageMain = 51;
155 zoneIt->imageSecondary = 56;
156 zoneIt->secondary = true;
157 } else {
158 // Setup inventory icon
159 zoneIt->hidden = false;
160 zoneIt->imageMain = (*inventoryIt)->idCA();
161 zoneIt->imageSecondary = (*inventoryIt)->idCl();
162 zoneIt->secondary = (inventorySelectedIt != inventoryIt);
163 }
164 }
165 }
166
callbackInventory(uint invId,uint dragStatus)167 uint Toolbar::callbackInventory(uint invId, uint dragStatus) {
168 if (!_inventoryEnabled) {
169 return 0;
170 }
171
172 invId += _inventoryOffset;
173 Object *obj = nullptr;
174 if (invId < _inventory->size()) {
175 obj = (*_inventory)[invId];
176 }
177 if (obj == nullptr) {
178 return 0;
179 }
180
181 if (!obj->valid()) {
182 return 0;
183 }
184
185 switch (dragStatus) {
186 case kDragStatus_Pressed:
187 _inventorySelected = invId;
188 _engine->setCursor(181);
189 _zones[12].secondary = (obj->viewCallback() == nullptr);
190 _inventoryButtonDragging = true;
191 return 1;
192 case kDragStatus_Dragging:
193 if (_inventorySelected == invId) {
194 return 0;
195 }
196 _inventorySelected = invId;
197 _zones[12].secondary = (obj->viewCallback() == nullptr);
198 _inventoryButtonDragging = true;
199 return 1;
200 case kDragStatus_Finished:
201 _engine->setCursor(obj->idSl());
202 _inventory->setSelectedObject(obj);
203 _inventorySelected = invId;
204 return 1;
205 default:
206 return 0;
207 }
208
209 }
210
callbackInventoryPrev(uint dragStatus)211 uint Toolbar::callbackInventoryPrev(uint dragStatus) {
212 if (!_inventoryEnabled) {
213 return 0;
214 }
215
216 if (dragStatus == kDragStatus_Pressed && _inventoryOffset > 0) {
217 // Restart auto repeat only if there could be something
218 _engine->setAutoRepeatClick(150);
219 _inventoryOffset--;
220 return 1;
221 }
222 // In any other case we didn't do anything
223 return 0;
224 }
225
callbackInventoryNext(uint dragStatus)226 uint Toolbar::callbackInventoryNext(uint dragStatus) {
227 if (!_inventoryEnabled) {
228 return 0;
229 }
230
231 if (dragStatus == kDragStatus_Pressed && _inventoryOffset < _inventoryMaxOffset) {
232 _engine->setAutoRepeatClick(150);
233 _inventoryOffset++;
234 return 1;
235 }
236 // In any other case we didn't do anything
237 return 0;
238 }
239
callbackViewObject(uint dragStatus)240 uint Toolbar::callbackViewObject(uint dragStatus) {
241 if (!_inventoryEnabled) {
242 return 0;
243 }
244
245 _mouseInViewObject = true;
246
247 if (_inventorySelected == uint(-1)) {
248 // Nothing selected in toolbar
249 return 0;
250 }
251 Inventory::const_iterator inventorySelectedIt = _inventory->begin() + _inventorySelected;
252 Object *selectedObject = *inventorySelectedIt;
253 if (selectedObject == nullptr || selectedObject->viewCallback() == nullptr) {
254 // Nothing to view, the sprite isn't even displayed
255 return 0;
256 }
257
258 switch (dragStatus) {
259 case kDragStatus_NoDrag:
260 _backupSelectedObject = selectedObject;
261 _engine->setCursor(181);
262 return 0;
263 case kDragStatus_Pressed:
264 case kDragStatus_Dragging:
265 return 1;
266 case kDragStatus_Finished:
267 // Just clicked
268 _engine->showMouse(false);
269 (*selectedObject->viewCallback())();
270 _engine->showMouse(true);
271 _parentMustRedraw = true;
272 _shortExit = true;
273 return 1;
274 default:
275 return 0;
276 }
277 }
278
callbackOptions(uint dragStatus)279 uint Toolbar::callbackOptions(uint dragStatus) {
280 _mouseInOptions = true;
281
282 switch (dragStatus) {
283 case kDragStatus_NoDrag:
284 _backupSelectedObject = _inventory->selectedObject();
285 _engine->setCursor(181);
286 return 0;
287 case kDragStatus_Pressed:
288 case kDragStatus_Dragging:
289 // Nothing to do, we wait release
290 return 0;
291 case kDragStatus_Finished:
292 // Just clicked
293 _engine->displayOptions();
294 _parentMustRedraw = true;
295 _shortExit = true;
296 _engine->setMousePos(Common::Point(320, 240)); // Center of screen
297 // Displaying options hides the mouse
298 _engine->showMouse(true);
299 return 0;
300 default:
301 return 0;
302 }
303 }
304
callbackDocumentation(uint dragStatus)305 uint Toolbar::callbackDocumentation(uint dragStatus) {
306 _mouseInOptions = true;
307
308 switch (dragStatus) {
309 case kDragStatus_NoDrag:
310 case kDragStatus_Pressed:
311 case kDragStatus_Dragging:
312 // Nothing to do, we wait release
313 return 0;
314 case kDragStatus_Finished:
315 // Just clicked
316 if (_engine->displayPlaceDocumentation()) {
317 _parentMustRedraw = true;
318 _shortExit = true;
319 _engine->setMousePos(Common::Point(320, 240)); // Center of screen
320 }
321 return 0;
322 default:
323 return 0;
324 }
325 }
326
drawToolbar(const Graphics::Surface * original)327 void Toolbar::drawToolbar(const Graphics::Surface *original) {
328 if (_position > 60) {
329 _position = 60;
330 }
331
332 if (_position != 0) {
333 // Not entirely drawn, we must copy a part of the original image
334 Common::Rect rct(0, 420, 640, 420 + _position);
335 _destSurface.copyRectToSurface(*original, 0, 0, rct);
336 }
337
338 if (_position == 60) {
339 // Entirely hidden, just stop there, we have nothing to draw
340 return;
341 }
342
343 // Not entirely hidden, we must display the transparent background prepared for us
344 Common::Rect rct(0, _position, 640, 60);
345 _destSurface.copyRectToSurface(_bgSurface, 0, _position, rct);
346
347 // Now draw the various zones on the surface
348 for (Common::Array<Zone>::const_iterator it = _zones.begin(); it != _zones.end(); it++) {
349 if (it->hidden) {
350 continue;
351 }
352
353 uint16 spriteId = it->secondary ? it->imageSecondary : it->imageMain;
354 if (spriteId == uint16(-1)) {
355 continue;
356 }
357
358 Common::Rect dst = it->rect;
359 dst.translate(0, _position);
360
361 // Clip the rectangle to fit inside the surface
362 dst.clip(Common::Rect(_destSurface.w, _destSurface.h));
363
364 if (dst.isEmpty()) {
365 continue;
366 }
367
368 const Graphics::Surface &sprite = _sprites->getSurface(spriteId);
369 _destSurface.transBlitFrom(sprite, Common::Rect(dst.width(), dst.height()), dst,
370 _sprites->getKeyColor(spriteId));
371 }
372
373 // And now draw the object description if needed
374 if (_inventoryEnabled && _inventoryHovered != uint(-1)) {
375 Object *obj = (*_inventory)[_inventoryHovered];
376
377 uint zoneId = _inventoryHovered - _inventoryOffset;
378 if (zoneId >= 8) {
379 // The object is hidden: huh?
380 return;
381 }
382
383 _fontManager->setSurface(&_destSurface);
384 _fontManager->setForeColor(243);
385 _fontManager->setCurrentFont(5);
386 _fontManager->setTransparentBackground(true);
387 const Common::String &objName = (*_messages)[obj->idOBJ()];
388 uint x = 195 - _fontManager->getStrWidth(objName);
389 uint startX = _zones[zoneId].rect.left + kTextOffset;
390 _fontManager->displayStr(x, 38 + _position, objName);
391 _destSurface.hLine(x, 54 + _position, startX - 1, 243); // minus 1 because hLine draws inclusive
392 _destSurface.vLine(startX, 42 + _position, 54 + _position, 243);
393 }
394 }
395
displayToolbar(const Graphics::Surface * original)396 bool Toolbar::displayToolbar(const Graphics::Surface *original) {
397 /**
398 * In game there are 2 functions to handle toolbar: one in warp and one in fixed images
399 * This one is the warp one and fixed images have a more asynchronous one during pop-up/down phases
400 * Let's make it simple for now
401 */
402
403 // WORKAROUND: Set cursor here to be more consistent: it's thumb cursor just before showing until just after showed
404 _engine->setCursor(181);
405
406 _parentMustRedraw = false;
407 _shortExit = false;
408
409 // Prepare the background of the toolbar by making it translucent
410 // Get the lowest part of the image
411 const Graphics::Surface subset = original->getSubArea(Common::Rect(0, original->h - _bgSurface.h,
412 _bgSurface.w, original->h));
413 _engine->makeTranslucent(_bgSurface, subset);
414
415 // WORKAROUND: Reset the inventory status at init to let sprites highlighted until toolbar is hidden
416 _inventorySelected = uint(-1);
417 _inventoryHovered = uint(-1);
418 _zones[12].secondary = true;
419
420 updateZones();
421
422 for (_position = 60; _position > 0; _position--) {
423 // Make the toolbar go up
424 drawToolbar(original);
425 g_system->copyRectToScreen(_destSurface.getPixels(), _destSurface.pitch, 0,
426 original->h - _destSurface.h, _destSurface.w, _destSurface.h);
427 g_system->updateScreen();
428
429 // Slow down animation
430 g_system->delayMillis(10);
431
432 _engine->pollEvents();
433 if (_engine->shouldAbort()) {
434 return false;
435 }
436 }
437
438 // Flush events
439 _engine->clearKeys();
440 _engine->waitMouseRelease();
441
442 handleToolbarEvents(original);
443 if (_engine->shouldAbort()) {
444 return false;
445 }
446
447 if (_shortExit) {
448 return _parentMustRedraw;
449 }
450
451 for (_position = 0; _position <= 60; _position++) {
452 // Make the toolbar go up
453 drawToolbar(original);
454 g_system->copyRectToScreen(_destSurface.getPixels(), _destSurface.pitch, 0,
455 original->h - _destSurface.h, _destSurface.w, _destSurface.h);
456 g_system->updateScreen();
457
458 // Slow down animation
459 g_system->delayMillis(10);
460
461 _engine->pollEvents();
462 if (_engine->shouldAbort()) {
463 return false;
464 }
465 }
466
467 return _parentMustRedraw;
468 }
469
handleToolbarEvents(const Graphics::Surface * original)470 void Toolbar::handleToolbarEvents(const Graphics::Surface *original) {
471 bool mouseInsideToolbar;
472 bool exitToolbar = false;
473 bool redrawToolbar;
474
475 // Don't have anything hovered for now
476 _inventoryHovered = uint(-1);
477 _inventorySelected = uint(-1);
478 _inventory->setSelectedObject(nullptr);
479 _backupSelectedObject = nullptr;
480
481 // Refresh zones because we erased selected object
482 updateZones();
483
484 // No need of original surface because the toolbar is fully displayed
485 drawToolbar(original);
486
487 g_system->copyRectToScreen(_destSurface.getPixels(), _destSurface.pitch, 0,
488 original->h - _destSurface.h, _destSurface.w, _destSurface.h);
489 g_system->updateScreen();
490
491 _engine->setCursor(181);
492
493 mouseInsideToolbar = (_engine->getMousePos().y > 388);
494
495 while (!exitToolbar) {
496 _mouseInOptions = false;
497 _mouseInViewObject = false;
498
499 _engine->pollEvents();
500 if (_engine->shouldAbort()) {
501 exitToolbar = true;
502 break;
503 }
504
505 redrawToolbar = false;
506 if (_engine->checkKeysPressed(2, Common::KEYCODE_ESCAPE, Common::KEYCODE_SPACE) ||
507 _engine->getCurrentMouseButton() == 2) {
508 _engine->waitMouseRelease();
509 exitToolbar = true;
510 break;
511 }
512
513 Common::Point mousePosInToolbar = _engine->getMousePos();
514 mousePosInToolbar -= Common::Point(0, 420);
515
516 if (captureEvent(mousePosInToolbar, _engine->getDragStatus())) {
517 // Something has changed with the zones handling, update zones
518 updateZones();
519 redrawToolbar = true;
520 } else if (_engine->getDragStatus() == kDragStatus_Pressed) {
521 // A click happened and wasn't handled, deselect object
522 _inventorySelected = uint(-1);
523 _inventory->setSelectedObject(nullptr);
524 _engine->setCursor(181);
525 // Reset view object
526 _zones[12].secondary = true;
527 updateZones();
528 redrawToolbar = true;
529 }
530
531 if (!mouseInsideToolbar) {
532 mouseInsideToolbar = (_engine->getMousePos().y > 388);
533 } else if (_engine->getMousePos().y <= 388) {
534 // mouseInsideToolbar is true and the mouse is outside the toolbar
535 exitToolbar = true;
536 break;
537 }
538
539 if (_engine->getCurrentMouseButton() == 1) {
540 // When the mouse button is down, nothing is selected
541 // It's selected on release
542 _inventory->setSelectedObject(nullptr);
543 }
544
545 if (_backupSelectedObject != nullptr && !(_mouseInOptions || _mouseInViewObject) &&
546 !_engine->getCurrentMouseButton()) {
547 _inventory->setSelectedObject(_backupSelectedObject);
548 _engine->setCursor(_backupSelectedObject->idSl());
549 _backupSelectedObject = nullptr;
550 }
551
552 // Hover the inventory objects
553 if (_inventory->selectedObject() == nullptr /* || _inventoryButtonDragging */) {
554 // The 2nd above condition is maybe useless because when the mouse button is down the selected object is always null
555 bool shouldHover = false;
556 Common::Array<Zone>::const_iterator zoneIt = hitTestZones(mousePosInToolbar);
557 uint zoneId = zoneIt - _zones.begin();
558 uint inventoryId = zoneId + _inventoryOffset;
559 if (zoneId < 8 && inventoryId < _inventory->size() && (*_inventory)[inventoryId] != nullptr) {
560 // It's the inventory
561 shouldHover = true;
562 if (_inventoryHovered != inventoryId && (*_inventory)[inventoryId]->valid()) {
563 // It's not the one currently hovered and it's a valid object
564 _inventoryHovered = inventoryId;
565 redrawToolbar = true;
566 }
567 }
568 if (!shouldHover && _inventoryHovered != uint(-1) && !_mouseInViewObject) {
569 // Remove hovering
570 _inventoryHovered = uint(-1);
571 _inventorySelected = uint(-1);
572 updateZones();
573 if (!_inventory->selectedObject()) {
574 // Reset back the cursor if nothing is selected
575 _engine->setCursor(181);
576 }
577 // Remove view
578 _zones[12].secondary = true;
579 redrawToolbar = true;
580 }
581 _inventoryButtonDragging = false;
582 }
583
584 if (_parentMustRedraw) {
585 break;
586 }
587
588 if (redrawToolbar) {
589 drawToolbar(original);
590 g_system->copyRectToScreen(_destSurface.getPixels(), _destSurface.pitch, 0,
591 original->h - _destSurface.h, _destSurface.w, _destSurface.h);
592 }
593
594 g_system->updateScreen();
595 g_system->delayMillis(10);
596 }
597
598 // Hide description when finished and selected object
599 // WORKAROUND: moved to the start to keep the selected object hilighted until the toolbar disappearance
600 }
601
602 } // End of namespace Versailles
603 } // End of namespace CryOmni3D
604