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  * Additional copyright for this file:
8  * Copyright (C) 1994-1998 Revolution Software Ltd.
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23  */
24 
25 
26 #include "common/rect.h"
27 
28 #include "sword2/sword2.h"
29 #include "sword2/defs.h"
30 #include "sword2/header.h"
31 #include "sword2/mouse.h"
32 #include "sword2/screen.h"
33 
34 namespace Sword2 {
35 
36 #define MENUDEEP 40
37 #define MAXMENUANIMS 8
38 
clearIconArea(int menu,int pocket,Common::Rect * r)39 void Mouse::clearIconArea(int menu, int pocket, Common::Rect *r) {
40 	byte *buf = _vm->_screen->getScreen();
41 	int16 screenWide = _vm->_screen->getScreenWide();
42 	byte menuIconWidth;
43 
44 	// Initialize menu icon width at correct size
45 	// depending if we are using pc or psx version.
46 	if (Sword2Engine::isPsx())
47 		menuIconWidth = RDMENU_PSXICONWIDE;
48 	else
49 		menuIconWidth = RDMENU_ICONWIDE;
50 
51 
52 	r->top = menu * (RENDERDEEP + MENUDEEP) + (MENUDEEP - RDMENU_ICONDEEP) / 2;
53 	r->bottom = r->top + RDMENU_ICONDEEP;
54 	r->left = RDMENU_ICONSTART + pocket * (menuIconWidth + RDMENU_ICONSPACING);
55 	r->right = r->left + menuIconWidth;
56 
57 	byte *dst = buf + r->top * screenWide + r->left;
58 
59 	for (int i = 0; i < RDMENU_ICONDEEP; i++) {
60 		memset(dst, 0, menuIconWidth);
61 		dst += screenWide;
62 	}
63 }
64 
65 /**
66  * This function should be called regularly to process the menubar system. The
67  * rate at which this function is called will dictate how smooth the menu
68  * system is.
69  */
70 
processMenu()71 void Mouse::processMenu() {
72 	uint8 menu;
73 	uint8 i, j;
74 	uint8 frameCount;
75 	Common::Rect r1, r2;
76 	static int32 lastTime = 0;
77 
78 	byte *buf = _vm->_screen->getScreen();
79 	int16 screenWide = _vm->_screen->getScreenWide();
80 	byte menuIconWidth;
81 
82 	if (Sword2Engine::isPsx())
83 		menuIconWidth = RDMENU_PSXICONWIDE;
84 	else
85 		menuIconWidth = RDMENU_ICONWIDE;
86 
87 
88 	if (lastTime == 0) {
89 		lastTime = _vm->getMillis();
90 		frameCount = 1;
91 	} else {
92 		int32 delta = _vm->getMillis() - lastTime;
93 
94 		if (delta > 250) {
95 			lastTime += delta;
96 			delta = 250;
97 			frameCount = 1;
98 		} else {
99 			frameCount = (uint8) ((_iconCount + 8) * delta / 750);
100 			lastTime += frameCount * 750 / (_iconCount + 8);
101 		}
102 	}
103 
104 	// Note: The "almost hidden" menu state exists only so that the menu
105 	// will be redrawn one last time before it's completely hidden. We do
106 	// not need a corresponding "almost shown" state because the menu will
107 	// always be redrawn while it's shown anyway. (We may want to change
108 	// this later.)
109 
110 	while (frameCount-- > 0) {
111 		for (menu = RDMENU_TOP; menu <= RDMENU_BOTTOM; menu++) {
112 			if (_menuStatus[menu] == RDMENU_HIDDEN || _menuStatus[menu] == RDMENU_ALMOST_HIDDEN || _menuStatus[menu] == RDMENU_SHOWN)
113 				continue;
114 
115 			int target, direction, nextState;
116 
117 			if (_menuStatus[menu] == RDMENU_OPENING) {
118 				target = MAXMENUANIMS;
119 				direction = 1;
120 				nextState = RDMENU_SHOWN;
121 			} else {
122 				target = 0;
123 				direction = -1;
124 				nextState = RDMENU_ALMOST_HIDDEN;
125 			}
126 
127 			bool complete = true;
128 
129 			// Propagate animation from the first icon...
130 			for (i = RDMENU_MAXPOCKETS - 1; i > 0; i--) {
131 				_pocketStatus[menu][i] = _pocketStatus[menu][i - 1];
132 
133 				if (_pocketStatus[menu][i] != target)
134 					complete = false;
135 			}
136 
137 			if (_pocketStatus[menu][i] != target)
138 				complete = false;
139 
140 			// ...and animate the first icon
141 			if (_pocketStatus[menu][0] != target)
142 				_pocketStatus[menu][0] += direction;
143 
144 			if (complete)
145 				_menuStatus[menu] = nextState;
146 		}
147 	}
148 
149 	for (menu = RDMENU_TOP; menu <= RDMENU_BOTTOM; menu++) {
150 		if (_menuStatus[menu] == RDMENU_HIDDEN)
151 			continue;
152 
153 		if (_menuStatus[menu] == RDMENU_ALMOST_HIDDEN)
154 			_menuStatus[menu] = RDMENU_HIDDEN;
155 
156 		// Draw the menu here.
157 		int32 curx = RDMENU_ICONSTART + menuIconWidth / 2;
158 		int32 cury = (MENUDEEP / 2) + (RENDERDEEP + MENUDEEP) * menu;
159 
160 		for (i = 0; i < RDMENU_MAXPOCKETS; i++) {
161 			if (_icons[menu][i]) {
162 				int32 xoff, yoff;
163 
164 				// Since we no longer clear the screen after
165 				// each frame we need to clear the icon area.
166 
167 				clearIconArea(menu, i, &r1);
168 
169 				if (_pocketStatus[menu][i] == MAXMENUANIMS) {
170 					xoff = (menuIconWidth / 2);
171 					r2.left = curx - xoff;
172 					r2.right = r2.left + menuIconWidth;
173 					yoff = (RDMENU_ICONDEEP / 2);
174 					r2.top = cury - yoff;
175 					r2.bottom = r2.top + RDMENU_ICONDEEP;
176 				} else {
177 					xoff = (menuIconWidth / 2) * _pocketStatus[menu][i] / MAXMENUANIMS;
178 					r2.left = curx - xoff;
179 					r2.right = curx + xoff;
180 					yoff = (RDMENU_ICONDEEP / 2) * _pocketStatus[menu][i] / MAXMENUANIMS;
181 					r2.top = cury - yoff;
182 					r2.bottom = cury + yoff;
183 				}
184 
185 				if (xoff != 0 && yoff != 0) {
186 					byte *dst = buf + r2.top * screenWide + r2.left;
187 					byte *src = _icons[menu][i];
188 
189 					if (_pocketStatus[menu][i] != MAXMENUANIMS) {
190 						_vm->_screen->scaleImageFast(
191 							dst, screenWide, r2.right - r2.left, r2.bottom - r2.top,
192 							src, menuIconWidth, menuIconWidth, RDMENU_ICONDEEP);
193 					} else {
194 						for (j = 0; j < RDMENU_ICONDEEP; j++) {
195 							memcpy(dst, src, menuIconWidth);
196 							src += menuIconWidth;
197 							dst += screenWide;
198 						}
199 					}
200 				}
201 				_vm->_screen->updateRect(&r1);
202 			}
203 			curx += (RDMENU_ICONSPACING + menuIconWidth);
204 		}
205 	}
206 }
207 
208 /**
209  * This function brings a specified menu into view.
210  * @param menu RDMENU_TOP or RDMENU_BOTTOM, depending on which menu to show
211  * @return RD_OK, or an error code
212  */
213 
showMenu(uint8 menu)214 int32 Mouse::showMenu(uint8 menu) {
215 
216 	// Do not show menu in PSX version, as there was really
217 	// nothing similar in the original game (menu was started
218 	// using SELECT button in psx pad)
219 	if (Sword2Engine::isPsx() && menu == RDMENU_TOP)
220 		return RD_OK;
221 
222 	// Check for invalid menu parameter
223 	if (menu > RDMENU_BOTTOM)
224 		return RDERR_INVALIDMENU;
225 
226 	// Check that the menu is not currently shown, or in the process of
227 	// being shown.
228 	if (_menuStatus[menu] == RDMENU_SHOWN || _menuStatus[menu] == RDMENU_OPENING)
229 		return RDERR_INVALIDCOMMAND;
230 
231 	_menuStatus[menu] = RDMENU_OPENING;
232 	return RD_OK;
233 }
234 
235 /**
236  * This function hides a specified menu.
237  * @param menu RDMENU_TOP or RDMENU_BOTTOM depending on which menu to hide
238  * @return RD_OK, or an error code
239  */
240 
hideMenu(uint8 menu)241 int32 Mouse::hideMenu(uint8 menu) {
242 
243 	// In PSX version, do nothing. There is no such menu.
244 	if (Sword2Engine::isPsx() && menu == RDMENU_TOP)
245 		return RD_OK;
246 
247 	// Check for invalid menu parameter
248 	if (menu > RDMENU_BOTTOM)
249 		return RDERR_INVALIDMENU;
250 
251 	// Check that the menu is not currently hidden, or in the process of
252 	// being hidden.
253 	if (_menuStatus[menu] == RDMENU_HIDDEN || _menuStatus[menu] == RDMENU_CLOSING)
254 		return RDERR_INVALIDCOMMAND;
255 
256 	_menuStatus[menu] = RDMENU_CLOSING;
257 	return RD_OK;
258 }
259 
260 /**
261  * This function hides both menus immediately.
262  */
263 
closeMenuImmediately()264 void Mouse::closeMenuImmediately() {
265 	Common::Rect r;
266 	int i;
267 
268 	_menuStatus[RDMENU_TOP] = RDMENU_HIDDEN;
269 	_menuStatus[RDMENU_BOTTOM] = RDMENU_HIDDEN;
270 
271 	for (i = 0; i < RDMENU_MAXPOCKETS; i++) {
272 		if (_icons[RDMENU_TOP][i]) {
273 			clearIconArea(RDMENU_TOP, i, &r);
274 			_vm->_screen->updateRect(&r);
275 		}
276 		if (_icons[RDMENU_BOTTOM][i]) {
277 			clearIconArea(RDMENU_BOTTOM, i, &r);
278 			_vm->_screen->updateRect(&r);
279 		}
280 	}
281 
282 	memset(_pocketStatus, 0, sizeof(uint8) * 2 * RDMENU_MAXPOCKETS);
283 }
284 
285 /**
286  * This function sets a menubar icon.
287  * @param menu RDMENU_TOP or RDMENU_BOTTOM, depending on which menu to change
288  * @param pocket the menu pocket to change
289  * @param icon icon data, or NULL to clear the icon
290  * @return RD_OK, or an error code
291  */
292 
setMenuIcon(uint8 menu,uint8 pocket,byte * icon)293 int32 Mouse::setMenuIcon(uint8 menu, uint8 pocket, byte *icon) {
294 	Common::Rect r;
295 	byte menuIconWidth;
296 
297 	if (Sword2Engine::isPsx())
298 		menuIconWidth = RDMENU_PSXICONWIDE;
299 	else
300 		menuIconWidth = RDMENU_ICONWIDE;
301 
302 	// Check for invalid menu parameter.
303 	if (menu > RDMENU_BOTTOM)
304 		return RDERR_INVALIDMENU;
305 
306 	// Check for invalid pocket parameter
307 	if (pocket >= RDMENU_MAXPOCKETS)
308 		return RDERR_INVALIDPOCKET;
309 
310 	// If there is an icon in the requested menu/pocket, clear it out.
311 	if (_icons[menu][pocket]) {
312 		_iconCount--;
313 		free(_icons[menu][pocket]);
314 		_icons[menu][pocket] = NULL;
315 		clearIconArea(menu, pocket, &r);
316 		_vm->_screen->updateRect(&r);
317 	}
318 
319 	// Only put the icon in the pocket if it is not NULL
320 	if (icon != NULL) {
321 		_iconCount++;
322 		_icons[menu][pocket] = (byte *)malloc(menuIconWidth * RDMENU_ICONDEEP);
323 		if (_icons[menu][pocket] == NULL)
324 			return RDERR_OUTOFMEMORY;
325 		memcpy(_icons[menu][pocket], icon, menuIconWidth * RDMENU_ICONDEEP);
326 	}
327 
328 	return RD_OK;
329 }
330 
331 } // End of namespace Sword2
332