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 "bladerunner/ui/ui_dropdown.h"
24 
25 #include "bladerunner/audio_player.h"
26 #include "bladerunner/bladerunner.h"
27 #include "bladerunner/ui/kia.h"
28 #include "bladerunner/ui/ui_image_picker.h"
29 #include "bladerunner/ui/ui_scroll_box.h"
30 #include "bladerunner/subtitles.h"
31 #include "bladerunner/font.h"
32 #include "bladerunner/game_info.h"
33 #include "bladerunner/shape.h"
34 #include "bladerunner/game_constants.h"
35 
36 #include "common/debug.h"
37 
38 namespace BladeRunner {
39 
40 const Color256 UIDropDown::kColors[] = {
41 	{   0,   0,   0 }, // Black - unpressed (framing rectange)
42 	{  16,   8,   8 },
43 	{  32,  24,   8 },
44 	{  56,  32,  16 },
45 	{  72,  48,  16 },
46 	{  88,  56,  24 }, // Mouse-over (framing rectange)
47 	{ 104,  72,  32 },
48 	{ 128,  80,  40 },
49 	{ 136,  96,  48 },
50 	{ 152, 112,  56 },
51 	{ 168, 128,  72 }  // Pressed (framing rectange)
52 };
53 
UIDropDown(BladeRunnerEngine * vm,UIDropDownLineSelectedCallback * ddlLineSelectedCallback,UIDropDownGenericCallback * ddlCancelledCallback,UIDropDownGenericCallback * ddlTopFrameClickCallback,void * callbackData,Common::String labelStr,int controlLeftX,int controlTopY,int scrollBoxMaxLineCount)54 UIDropDown::UIDropDown(BladeRunnerEngine *vm,
55 	                                      UIDropDownLineSelectedCallback *ddlLineSelectedCallback,
56 	                                      UIDropDownGenericCallback *ddlCancelledCallback,
57 	                                      UIDropDownGenericCallback *ddlTopFrameClickCallback,
58 	                                      void *callbackData,
59 	                                      Common::String labelStr,
60 	                                      int controlLeftX,
61 	                                      int controlTopY,
62 	                                      int scrollBoxMaxLineCount) : UIComponent(vm) {
63 
64 	_isVisible                         = false;
65 
66 	_labelStr                          = labelStr;
67 	_controlLeftX                      = MAX(controlLeftX, 0);
68 	// TODO The id should be used to eg. grab info about the selected subtitles from an engine's subtitles object
69 	_lineSelectedId                    = -1;
70 
71 	_lineSelectorFrameRectColor        = 0;
72 	_lineSelectorFrameRectHasFocus     = false;
73 	// A framing (outlining) rectangle to highlight the selected option field on top of the scrollbox
74 	controlTopY                        = CLIP(controlTopY, 0, 600);
75 	_lineSelectorFrameRect             = Common::Rect(0, controlTopY, 0, controlTopY + kDropDownButtonShapeHeight);
76 
77 	// TODO This eventually should be set to a default probably by the outside caller class(kia_section_settings)
78 	//      Current explicit assignment only serves as placeholder / proof of concept
79 	_lineSelectedStr                   = "English (SCUMMVM) v7 [ENG]";
80 	_lineSelectorScrollBox             =  new UIScrollBox(_vm, scrollBoxLineSelectCallback, this, scrollBoxMaxLineCount, 2, false, Common::Rect(0, 0, 0, 55 + kFrameRectPaddingPx), Common::Rect(0, 0, 0, 55));
81 	_lineSelectorScrollBoxMaxLineWidth = 0;
82 
83 	_lineDropdownBtn                   = new UIImagePicker(_vm, 2);
84 
85 	_ddlLineSelectedCallback  = ddlLineSelectedCallback;
86 	_ddlCancelledCallback     = ddlCancelledCallback;
87 	_ddlTopFrameClickCallback = ddlTopFrameClickCallback;
88 	_callbackData             = callbackData;
89 
90 	_mouseX = 0;
91 	_mouseY = 0;
92 }
93 
~UIDropDown()94 UIDropDown::~UIDropDown() {
95 	delete _lineSelectorScrollBox;
96 	delete _lineDropdownBtn;
97 }
98 
activate()99 void UIDropDown::activate() {
100 	_lineDropdownBtn->resetImages();
101 	// Actual button shape
102 	// defineImage actually increases internally the bottom and right bounds for the rect to be inclusive (for the contains() method)
103 	_lineDropdownBtn->defineImage(0, Common::Rect(0, _lineSelectorFrameRect.top + 1, kDropDownButtonShapeWidth - 1, _lineSelectorFrameRect.bottom - 1), _vm->_kia->_shapes->get(73), _vm->_kia->_shapes->get(74), _vm->_kia->_shapes->get(75), nullptr);
104 	// Clickable Selected/Active Line Description area
105 	_lineDropdownBtn->defineImage(1, Common::Rect(0, _lineSelectorFrameRect.top, kDropDownButtonShapeWidth - 1, _lineSelectorFrameRect.bottom - 1), nullptr, nullptr, nullptr, nullptr);
106 	_lineDropdownBtn->activate(nullptr, nullptr, mouseDownLDBCallback, nullptr, this);
107 
108 	_lineSelectorScrollBox->setBoxTop(_lineSelectorFrameRect.bottom);
109 	_lineSelectorScrollBox->setScrollbarTop(_lineSelectorFrameRect.bottom);
110 
111 	_lineSelectorScrollBox->hide(); // show upon click on field or dropdown button
112 	show();
113 }
114 
115 
deactivate()116 void UIDropDown::deactivate() {
117 	_isVisible                = false;
118 
119 	_lineDropdownBtn->deactivate();
120 	_lineSelectorScrollBox->hide();
121 }
122 
draw(Graphics::Surface & surface)123 void UIDropDown::draw(Graphics::Surface &surface) {
124 	if (!_isVisible) {
125 		return;
126 	}
127 
128 	int posStartOfSelectedLineDesc = _controlLeftX + _vm->_mainFont->getStringWidth(_labelStr) + _vm->_mainFont->getCharWidth(' ');
129 	_vm->_mainFont->drawString(&surface, _labelStr, _controlLeftX, _lineSelectorFrameRect.top, surface.w, surface.format.RGBToColor(232, 208, 136));
130 	_vm->_mainFont->drawString(&surface, _lineSelectedStr,
131 		                        posStartOfSelectedLineDesc,
132 		                        _lineSelectorFrameRect.top, surface.w, surface.format.RGBToColor(240, 232, 192));
133 
134 	// TODO add a clipping for description field here
135 	int posEndOfSelectedLineDesc = posStartOfSelectedLineDesc + _vm->_mainFont->getStringWidth(_lineSelectedStr) + _vm->_mainFont->getCharWidth(' ');
136 
137 	_lineDropdownBtn->setImageLeft(0, posEndOfSelectedLineDesc );
138 
139 	_lineDropdownBtn->setImageLeft(1, posStartOfSelectedLineDesc - kFrameRectPaddingPx);
140 	_lineDropdownBtn->setImageWidth(1, posEndOfSelectedLineDesc + kFrameRectPaddingPx - posStartOfSelectedLineDesc);
141 
142 	_lineDropdownBtn->draw(surface);
143 //	_lineDropdownBtn->drawTooltip(surface, _mouseX, _mouseY);
144 
145 	_lineSelectorFrameRect.moveTo(posStartOfSelectedLineDesc - kFrameRectPaddingPx, _lineSelectorFrameRect.top);
146 	_lineSelectorFrameRect.setWidth(posEndOfSelectedLineDesc + kDropDownButtonShapeWidth + kFrameRectPaddingPx - posStartOfSelectedLineDesc);
147 
148 	_lineSelectorScrollBox->draw(surface);
149 
150 	int lineSelectorFrameRectTargetColor;
151 	if (_lineSelectorScrollBox->isVisible()) {
152 		lineSelectorFrameRectTargetColor = 10;
153 	} else if (_lineSelectorFrameRectHasFocus) {
154 		lineSelectorFrameRectTargetColor = 5;
155 	} else {
156 		lineSelectorFrameRectTargetColor = 0;
157 	}
158 
159 	// Ensures animated transition of the frame's (outlining rectangle's) color to the new one
160 	if (_lineSelectorFrameRectColor < lineSelectorFrameRectTargetColor) {
161 		++_lineSelectorFrameRectColor;
162 	}
163 
164 	// Ensures animated transition of the frame's (outlining rectangle's) color to the new one
165 	if (_lineSelectorFrameRectColor > lineSelectorFrameRectTargetColor) {
166 		--_lineSelectorFrameRectColor;
167 	}
168 	surface.frameRect(_lineSelectorFrameRect,
169 	                  surface.format.RGBToColor(kColors[_lineSelectorFrameRectColor].r,
170 	                                            kColors[_lineSelectorFrameRectColor].g,
171 	                                            kColors[_lineSelectorFrameRectColor].b));
172 }
173 
show()174 void UIDropDown::show() {
175 	_isVisible = true;
176 }
177 
hide()178 void UIDropDown::hide() {
179 	_isVisible = false;
180 }
181 
isVisible()182 bool UIDropDown::isVisible() {
183 	return _isVisible;
184 }
185 
isDropDownMenuExpanded()186 bool UIDropDown::isDropDownMenuExpanded() {
187 	return _lineSelectorScrollBox->isVisible();
188 }
189 
clearLines()190 void UIDropDown::clearLines() {
191 	_lineSelectorScrollBox->clearLines();
192 	_lineSelectorScrollBoxMaxLineWidth = 0;
193 }
194 
addLine(const Common::String & text,int lineData)195 void UIDropDown::addLine(const Common::String &text, int lineData) {
196 	_lineSelectorScrollBox->addLine(text, lineData, 0x08);
197 	_lineSelectorScrollBoxMaxLineWidth = MAX(_vm->_mainFont->getStringWidth(text), _lineSelectorScrollBoxMaxLineWidth);
198 }
199 
addLine(const char * text,int lineData)200 void UIDropDown::addLine(const char *text, int lineData) {
201 	_lineSelectorScrollBox->addLine(text, lineData, 0x08);
202 	_lineSelectorScrollBoxMaxLineWidth = MAX(_vm->_mainFont->getStringWidth(text), _lineSelectorScrollBoxMaxLineWidth);
203 }
204 
sortLines()205 void UIDropDown::sortLines() {
206 	_lineSelectorScrollBox->sortLines();
207 }
208 
handleMouseMove(int mouseX,int mouseY)209 void UIDropDown::handleMouseMove(int mouseX, int mouseY) {
210 	if (!_isVisible) {
211 		return;
212 	}
213 
214 	_mouseX = mouseX;
215 	_mouseY = mouseY;
216 
217 	// contains() does not include right or bottom boundary "line"
218 	if (_lineSelectorFrameRect.contains(mouseX, mouseY)) {
219 		if (!_lineSelectorFrameRectHasFocus && !_lineSelectorScrollBox->isVisible()) {
220 			_vm->_audioPlayer->playAud(_vm->_gameInfo->getSfxTrack(kSfxTEXT3), 100, 0, 0, 50, 0);
221 		}
222 		_lineSelectorFrameRectHasFocus = true;
223 	} else {
224 		_lineSelectorFrameRectHasFocus = false;
225 	}
226 
227 	_lineSelectorScrollBox->handleMouseMove(mouseX, mouseY);
228 	_lineDropdownBtn->handleMouseAction(mouseX, mouseY, false, false, false);
229 }
230 
handleMouseDown(bool alternateButton)231 void UIDropDown::handleMouseDown(bool alternateButton) {
232 	if (!_isVisible) {
233 		return;
234 	}
235 
236 	if (!alternateButton) {
237 		_lineSelectorScrollBox->handleMouseDown(false);
238 		_lineDropdownBtn->handleMouseAction(_mouseX, _mouseY, true, false, false);
239 		if (!_lineSelectorFrameRectHasFocus
240 			&& _lineSelectorScrollBox->isVisible()
241 			&& !_lineSelectorScrollBox->hasFocus()
242 		) {
243 			_ddlCancelledCallback(_callbackData, this);
244 			showSelectionDropdown(false);
245 		}
246 	}
247 }
248 
handleMouseScroll(int direction)249 void UIDropDown::handleMouseScroll(int direction) {
250 	if (!_isVisible) {
251 		return;
252 	}
253 
254 	if (_lineSelectorScrollBox->isVisible()) {
255 		_lineSelectorScrollBox->handleMouseScroll(direction);
256 	}
257 }
258 
handleMouseUp(bool alternateButton)259 void UIDropDown::handleMouseUp(bool alternateButton) {
260 	if (!_isVisible) {
261 		return;
262 	}
263 
264 	if (!alternateButton) {
265 		_lineSelectorScrollBox->handleMouseUp(false);
266 		_lineDropdownBtn->handleMouseAction(_mouseX, _mouseY, false, true, false);
267 	}
268 }
269 
scrollBoxLineSelectCallback(void * callbackData,void * source,int lineData,int mouseButton)270 void UIDropDown::scrollBoxLineSelectCallback(void *callbackData, void *source, int lineData, int mouseButton) {
271 	UIDropDown *self = (UIDropDown *)callbackData;
272 
273 	if (source == self->_lineSelectorScrollBox && lineData >= 0) {
274 		Common::String selectedLangDescStr = self->_lineSelectorScrollBox->getLineText(lineData);
275 		self->_lineSelectedId  = lineData;
276 		self->_lineSelectedStr = selectedLangDescStr;
277 		self->_vm->_audioPlayer->playAud(self->_vm->_gameInfo->getSfxTrack(kSfxELECBP1), 90, 0, 0, 50, 0);
278 		self->_ddlLineSelectedCallback(self->_callbackData, self, lineData, mouseButton);
279 		self->showSelectionDropdown(false);
280 		//debug("text selected: %s", selectedLangDescStr.c_str());
281 	}
282 }
283 
284 // Callback from _lineDropdownBtn items
mouseDownLDBCallback(int buttonId,void * callbackData)285 void UIDropDown::mouseDownLDBCallback(int buttonId, void *callbackData) {
286 	UIDropDown *self = (UIDropDown *)callbackData;
287 	self->onButtonPressed(buttonId);
288 }
289 
onButtonPressed(int buttonId)290 void UIDropDown::onButtonPressed(int buttonId) {
291 	switch (buttonId) {
292 	case 0:
293 		// Pressed DDL dropdown button (0)
294 		// fall through
295 	case 1:
296 //		if (buttonId == 1) {
297 //			// Pressed DDL clickable area (1)
298 //			debug("Pressed DDL clickable area (1)");
299 //		}
300 		_ddlTopFrameClickCallback(_callbackData, this);
301 		showSelectionDropdown(!_lineSelectorScrollBox->isVisible());
302 		break;
303 	default:
304 		return;
305 	}
306 }
307 
showSelectionDropdown(bool showToggle)308 void UIDropDown::showSelectionDropdown(bool showToggle) {
309 	int prevDropdownBtnLeft = _lineDropdownBtn->getImageLeft(0);
310 	if (showToggle) {
311 		_lineSelectorScrollBox->setBoxTop(_lineSelectorFrameRect.bottom);
312 		_lineSelectorScrollBox->setBoxLeft(_lineDropdownBtn->getImageLeft(1));
313 		// TODO width should be retrieved from the maximum width of a language description in SUBTITLES.MIX (or a max width to clip to)
314 		_lineSelectorScrollBox->setBoxWidth(MAX(_lineDropdownBtn->getImageWidth(1), _lineSelectorScrollBoxMaxLineWidth + _vm->_mainFont->getCharWidth(' ')));
315 
316 		if (_lineDropdownBtn->getImageLeft(0) < kFurthestLeftForScrollBar) {
317 			// CLIP expects the first boundary argument to be the min of the two.
318 			_lineSelectorScrollBox->setScrollbarLeft(CLIP( _lineSelectorScrollBox->getBoxLeft() + _lineSelectorScrollBox->getBoxWidth(), _lineDropdownBtn->getImageLeft(0), kFurthestLeftForScrollBar));
319 		} else {
320 			_lineSelectorScrollBox->setScrollbarLeft(MAX( _lineSelectorScrollBox->getBoxLeft() + _lineSelectorScrollBox->getBoxWidth(), kFurthestLeftForScrollBar));
321 		}
322 
323 		_lineSelectorScrollBox->setScrollbarTop(_lineSelectorFrameRect.bottom);
324 		_lineSelectorScrollBox->setScrollbarWidth(kDropDownButtonShapeWidth);
325 		_lineSelectorScrollBox->show();
326 		// change dropdown button icon too
327 		_lineDropdownBtn->resetActiveImage(0);
328 		_lineDropdownBtn->defineImage(0, Common::Rect(prevDropdownBtnLeft, _lineSelectorFrameRect.top + 1, prevDropdownBtnLeft + kDropDownButtonShapeWidth - 1, _lineSelectorFrameRect.bottom - 1), _vm->_kia->_shapes->get(70), _vm->_kia->_shapes->get(71), _vm->_kia->_shapes->get(72), nullptr);
329 		_lineSelectorFrameRectColor = 10;
330 	} else {
331 		// hide scrollable area
332 		_lineSelectorScrollBox->hide();
333 		// change dropdown button icon too
334 		_lineDropdownBtn->resetActiveImage(0);
335 		_lineDropdownBtn->defineImage(0, Common::Rect(prevDropdownBtnLeft, _lineSelectorFrameRect.top + 1, prevDropdownBtnLeft + kDropDownButtonShapeWidth - 1, _lineSelectorFrameRect.bottom - 1), _vm->_kia->_shapes->get(73), _vm->_kia->_shapes->get(74), _vm->_kia->_shapes->get(75), nullptr);
336 		_lineSelectorFrameRectColor = 0;
337 	}
338 }
339 
setLabelStr(Common::String newLabel)340 void UIDropDown::setLabelStr(Common::String newLabel) {
341 	_labelStr = newLabel;
342 }
343 
setControlLeft(int controlLeftX)344 void UIDropDown::setControlLeft(int controlLeftX) {
345 	_controlLeftX = controlLeftX;
346 }
347 
getLineSelectedStr()348 Common::String UIDropDown::getLineSelectedStr() {
349 	return _lineSelectedStr;
350 }
351 
352 }
353