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