1 ///////////////////////////////////////////////////////////////////////////////
2 // Copyright (C) 2004-2010 by The Allacrost Project
3 // All Rights Reserved
4 //
5 // This code is licensed under the GNU GPL version 2. It is free software
6 // and you may modify it and/or redistribute it under the terms of this license.
7 // See http://www.gnu.org/copyleft/gpl.html for details.
8 ///////////////////////////////////////////////////////////////////////////////
9
10 #include <sstream>
11
12 #include "option.h"
13 #include "video.h"
14
15 using namespace std;
16
17 using namespace hoa_utils;
18 using namespace hoa_video;
19 using namespace hoa_video::private_video;
20 using namespace hoa_input;
21 using namespace hoa_gui::private_gui;
22
23 namespace hoa_gui {
24
25 ////////////////////////////////////////////////////////////////////////////////
26 // Option class methods
27 ////////////////////////////////////////////////////////////////////////////////
28
Option()29 Option::Option() :
30 disabled(false),
31 image(NULL)
32 {}
33
34
35
~Option()36 Option::~Option() {
37 Clear();
38 }
39
40
41
Option(const Option & copy)42 Option::Option(const Option& copy) :
43 disabled(copy.disabled),
44 elements(copy.elements),
45 text(copy.text)
46 {
47 if (copy.image == NULL) {
48 image = NULL;
49 }
50 else {
51 image = new StillImage(*(copy.image));
52 }
53 }
54
55
56
operator =(const Option & copy)57 Option& Option::operator=(const Option& copy) {
58 // Handle the case were a dumbass assigns an object to itself
59 if (this == ©) {
60 return *this;
61 }
62
63 disabled = copy.disabled;
64 elements = copy.elements;
65 text = copy.text;
66 if (copy.image == NULL) {
67 image = NULL;
68 }
69 else {
70 image = new StillImage(*(copy.image));
71 }
72
73 return *this;
74 }
75
76
77
Clear()78 void Option::Clear() {
79 disabled = false;
80 elements.clear();
81 text.clear();
82 if (image != NULL) {
83 delete image;
84 image = NULL;
85 }
86 }
87
88 ////////////////////////////////////////////////////////////////////////////////
89 // OptionBox class methods
90 ////////////////////////////////////////////////////////////////////////////////
91
OptionBox()92 OptionBox::OptionBox() :
93 GUIControl(),
94 _initialized(false),
95 _number_rows(1),
96 _number_columns(1),
97 _number_cell_rows(1),
98 _number_cell_columns(1),
99 _cell_width(0.0f),
100 _cell_height(0.0f),
101 _selection_mode(VIDEO_SELECT_SINGLE),
102 _horizontal_wrap_mode(VIDEO_WRAP_MODE_NONE),
103 _vertical_wrap_mode(VIDEO_WRAP_MODE_NONE),
104 _enable_switching(false),
105 _draw_left_column(0),
106 _draw_top_row(0),
107 _cursor_xoffset(0.0f),
108 _cursor_yoffset(0.0f),
109 _scroll_offset(0.0f),
110 _option_xalign(VIDEO_X_LEFT),
111 _option_yalign(VIDEO_Y_CENTER),
112 _scissoring(false),
113 _scissoring_owner(false),
114 _draw_horizontal_arrows(false),
115 _draw_vertical_arrows(false),
116 _grey_up_arrow(false),
117 _grey_down_arrow(false),
118 _grey_left_arrow(false),
119 _grey_right_arrow(false),
120 _event(0),
121 _selection(0),
122 _first_selection(-1),
123 _cursor_state(VIDEO_CURSOR_STATE_VISIBLE),
124 _blink(false),
125 _blink_time(0),
126 _scrolling(false),
127 _scroll_time(0),
128 _scroll_direction(0),
129 _horizontal_arrows_position(H_POSITION_BOTTOM),
130 _vertical_arrows_position(V_POSITION_RIGHT)
131 {
132 // TEMP
133 _width = 1.0f;
134 _height = 1.0f;
135 }
136
Update(uint32 frame_time)137 void OptionBox::Update(uint32 frame_time) {
138 _event = 0; // Clear all events
139
140 _blink = ((_blink_time / VIDEO_CURSOR_BLINK_RATE) % 2) == 1;
141 _blink_time += frame_time;
142
143 if (_scrolling) {
144 _scroll_time += frame_time;
145
146 if (_scroll_time > VIDEO_OPTION_SCROLL_TIME) {
147 _scroll_time = 0;
148 _scrolling = false;
149 _scroll_offset = 0.0f;
150 }
151 else {
152 // [phuedx] Calculate the _scroll_offset independant of the coordinate system
153 _scroll_offset = (_scroll_time / static_cast<float>(VIDEO_OPTION_SCROLL_TIME)) * _cell_height;
154 if (_scroll_direction == -1 ) { // Up
155 _scroll_offset = _cell_height - _scroll_offset;
156 }
157 }
158 }
159 }
160
161
162
Draw()163 void OptionBox::Draw() {
164 // Do nothing if the option box is not properly initialized
165 if (!IsInitialized(_initialization_errors)) {
166 cout << "ERROR: Could not draw OptionBox" << endl;
167 return;
168 }
169
170 VideoManager->PushState();
171 VideoManager->SetDrawFlags(_xalign, _yalign, VIDEO_BLEND, 0);
172 VideoManager->DisableScissoring();
173
174 // TODO: This call is also made at the end of this function. It is made here because for some
175 // strange reason, only the option box outline is drawn and not the outline for the individual
176 // cells. I'm not sure what part of the code between here and the end of this function could
177 // affect that. This bug needs to be resolved and then this call to _DEBUG_DrawOutline() should
178 // be removed, leaving only the call at the bottom of the function
179 if (GUIManager->DEBUG_DrawOutlines() == true) {
180 //_DEBUG_DrawOutline();
181 }
182 float left, right, bottom, top;
183
184 // ---------- (1) Determine the edge dimensions of the option box
185 left = 0.0f;
186 right = _number_cell_columns * _cell_width;
187 bottom = 0.0f;
188 top = _number_cell_rows * _cell_height;
189 CalculateAlignedRect(left, right, bottom, top);
190
191 CoordSys &cs = VideoManager->_current_context.coordinate_system;
192
193 // ---------- (2) Determine the option cells to be drawn and any offsets needed for scrolling
194 VideoManager->SetDrawFlags(_option_xalign, _option_yalign, VIDEO_X_NOFLIP, VIDEO_Y_NOFLIP, VIDEO_BLEND, 0);
195
196 float xoff = _cell_width * cs.GetHorizontalDirection();
197 float yoff = -_cell_height * cs.GetVerticalDirection();
198 bool finished = false;
199
200 // [phuedx] Align the scroll offset with the current coordinate system
201 _scroll_offset *= cs.GetVerticalDirection();
202
203 OptionCellBounds bounds;
204 bounds.y_top = top + _scroll_offset;
205 bounds.y_center = bounds.y_top - 0.5f * _cell_height * cs.GetVerticalDirection();
206 bounds.y_bottom = (bounds.y_center * 2.0f) - bounds.y_top;
207
208
209 // ---------- (3) Iterate through all the visible option cells and draw them and the draw cursor
210 for (uint32 row = _draw_top_row; row < _draw_top_row + _number_cell_rows && finished == false; row++) {
211
212 bounds.x_left = left;
213 bounds.x_center = bounds.x_left + (0.5f * _cell_width * cs.GetHorizontalDirection());
214 bounds.x_right = (bounds.x_center * 2.0f) - bounds.x_left;
215
216 // Draw the columns of options
217 for (uint32 col = _draw_left_column; col < _draw_left_column + _number_cell_columns; ++col) {
218 uint32 index = row * _number_cell_columns + col;
219
220 // If there are more visible cells than there are options available we leave those cells empty
221 if (index >= GetNumberOptions()) {
222 finished = true;
223 break;
224 }
225
226 float left_edge = 999999.0f; // The x offset to where the visible option contents begin
227 _DrawOption(_options.at(index), bounds, _scroll_offset, left_edge);
228
229 // Draw the cursor if the previously drawn option was or is selected
230 if ((static_cast<int32>(index) == _selection || static_cast<int32>(index) == _first_selection) &&
231 _cursor_state != VIDEO_CURSOR_STATE_HIDDEN && (_cursor_state != VIDEO_CURSOR_STATE_BLINKING || _blink == true))
232 {
233 // If this option was the first selection, draw it darkened so that it has a different appearance
234 bool darken = (static_cast<int32>(index) == _first_selection) ? true : false;
235 _DrawCursor(bounds, _scroll_offset, left_edge, darken);
236 }
237
238 bounds.x_left += xoff;
239 bounds.x_center += xoff;
240 bounds.x_right += xoff;
241 } // for (int32 col = 0; col < _number_columns; ++col)
242
243 bounds.y_top += yoff;
244 bounds.y_center += yoff;
245 bounds.y_bottom += yoff;
246 } // for (int32 row = row_min; row < row_max; row++)
247
248 // ---------- (4) Draw scroll arrows where appropriate
249 _DetermineScrollArrows();
250 std::vector<StillImage>* arrows = GUIManager->GetScrollArrows();
251
252
253 float w, h;
254 this->GetDimensions(w, h);
255
256 if (_draw_vertical_arrows) {
257 VideoManager->SetDrawFlags(VIDEO_X_RIGHT, VIDEO_Y_BOTTOM, VIDEO_BLEND, 0);
258 VideoManager->Move(right, top);
259 if (_grey_up_arrow)
260 arrows->at(4).Draw();
261 else
262 arrows->at(0).Draw();
263
264 VideoManager->SetDrawFlags(VIDEO_X_RIGHT, VIDEO_Y_TOP, VIDEO_BLEND, 0);
265 VideoManager->Move(right, bottom);
266 if (_grey_down_arrow)
267 arrows->at(5).Draw();
268 else
269 arrows->at(1).Draw();
270 }
271
272 if (_draw_horizontal_arrows) {
273 VideoManager->SetDrawFlags(VIDEO_X_RIGHT, VIDEO_Y_BOTTOM, VIDEO_BLEND, 0);
274 VideoManager->Move(left, bottom);
275 if (_grey_left_arrow)
276 arrows->at(7).Draw();
277 else
278 arrows->at(3).Draw();
279
280 VideoManager->SetDrawFlags(VIDEO_X_LEFT, VIDEO_Y_BOTTOM, VIDEO_BLEND, 0);
281 VideoManager->Move(right, bottom);
282 if (_grey_right_arrow)
283 arrows->at(6).Draw();
284 else
285 arrows->at(2).Draw();
286 }
287
288 VideoManager->SetDrawFlags(_xalign, _yalign, VIDEO_BLEND, 0);
289
290 if (GUIManager->DEBUG_DrawOutlines() == true)
291 GUIControl::_DEBUG_DrawOutline();
292
293 VideoManager->PopState();
294 } // void OptionBox::Draw()
295
296
297
SetDimensions(float width,float height,uint8 num_cols,uint8 num_rows,uint8 cell_cols,uint8 cell_rows)298 void OptionBox::SetDimensions(float width, float height, uint8 num_cols, uint8 num_rows, uint8 cell_cols, uint8 cell_rows) {
299 if (num_rows == 0 || num_cols == 0) {
300 IF_PRINT_WARNING(VIDEO_DEBUG) << "num_rows/num_cols argument was zero" << endl;
301 return;
302 }
303
304 if (cell_rows == 0 || cell_cols == 0) {
305 IF_PRINT_WARNING(VIDEO_DEBUG) << "cell_rows/cell_cols argument was zero" << endl;
306 return;
307 }
308
309 if (num_rows < cell_rows || num_cols < cell_cols) {
310 IF_PRINT_WARNING(VIDEO_DEBUG) << "num_rows/num_cols was less than cell_rows/cell_cols" << endl;
311 return;
312 }
313
314 _width = width;
315 _height = height;
316 _number_columns = num_cols;
317 _number_rows = num_rows;
318 _number_cell_columns = cell_cols;
319 _number_cell_rows = cell_rows;
320 _cell_width = _width / cell_cols;
321 _cell_height = _height / cell_rows;
322 }
323
324
325
SetOptions(const vector<ustring> & option_text)326 void OptionBox::SetOptions(const vector<ustring>& option_text) {
327 ClearOptions();
328 for (vector<ustring>::const_iterator i = option_text.begin(); i != option_text.end(); i++) {
329 const ustring& str = *i;
330 Option option;
331
332 if (_ConstructOption(str, option) == false) {
333 ClearOptions();
334 IF_PRINT_WARNING(VIDEO_DEBUG) << "an option contained an invalid formatted string: " << MakeStandardString(*i) << endl;
335 return;
336 }
337 _options.push_back(option);
338 }
339 }
340
341
342
ClearOptions()343 void OptionBox::ClearOptions() {
344 _options.clear();
345 }
346
347
348
AddOption()349 void OptionBox::AddOption() {
350 Option option;
351 if (_ConstructOption(ustring(), option) == false) {
352 IF_PRINT_WARNING(VIDEO_DEBUG) << "failed to construct option using an empty string" << endl;
353 return;
354 }
355
356 _options.push_back(option);
357 }
358
359
360
AddOption(const hoa_utils::ustring & text)361 void OptionBox::AddOption(const hoa_utils::ustring& text) {
362 Option option;
363 if (_ConstructOption(text, option) == false) {
364 IF_PRINT_WARNING(VIDEO_DEBUG) << "argument contained an invalid formatted string: " << MakeStandardString(text) << endl;
365 return;
366 }
367
368 _options.push_back(option);
369 }
370
371
372
AddOptionElementText(uint32 option_index,const ustring & text)373 void OptionBox::AddOptionElementText(uint32 option_index, const ustring& text) {
374 if (option_index >= GetNumberOptions()) {
375 IF_PRINT_WARNING(VIDEO_DEBUG) << "out-of-range option_index argument: " << option_index << endl;
376 return;
377 }
378
379 Option& this_option = _options[option_index];
380 OptionElement new_element;
381
382 new_element.type = VIDEO_OPTION_ELEMENT_TEXT;
383 new_element.value = static_cast<int32>(this_option.text.size());
384 this_option.text.push_back(text);
385 this_option.elements.push_back(new_element);
386 }
387
388
389
AddOptionElementImage(uint32 option_index,string & image_filename)390 void OptionBox::AddOptionElementImage(uint32 option_index, string& image_filename) {
391 if (option_index >= GetNumberOptions()) {
392 IF_PRINT_WARNING(VIDEO_DEBUG) << "out-of-range option_index argument: " << option_index << endl;
393 return;
394 }
395
396 Option& this_option = _options[option_index];
397 OptionElement new_element;
398
399 new_element.type = VIDEO_OPTION_ELEMENT_IMAGE;
400 new_element.value = 0;
401
402 this_option.image = new StillImage();
403 if (this_option.image->Load(image_filename) == false) {
404 IF_PRINT_WARNING(VIDEO_DEBUG) << "failed to add image element because image file load failed" << image_filename << endl;
405 delete this_option.image;
406 this_option.image = NULL;
407 return;
408 }
409
410 this_option.elements.push_back(new_element);
411 }
412
413
414
AddOptionElementImage(uint32 option_index,const StillImage * image)415 void OptionBox::AddOptionElementImage(uint32 option_index, const StillImage* image) {
416 if (option_index >= GetNumberOptions()) {
417 IF_PRINT_WARNING(VIDEO_DEBUG) << "out-of-range option_index argument: " << option_index << endl;
418 return;
419 }
420 if (image == NULL) {
421 IF_PRINT_WARNING(VIDEO_DEBUG) << "image argument was NULL" << endl;
422 return;
423 }
424
425 Option& this_option = _options[option_index];
426 OptionElement new_element;
427
428 new_element.type = VIDEO_OPTION_ELEMENT_IMAGE;
429 new_element.value = 0;
430
431 this_option.image = new StillImage(*image);
432 this_option.elements.push_back(new_element);
433 }
434
435
436
AddOptionElementAlignment(uint32 option_index,OptionElementType position_type)437 void OptionBox::AddOptionElementAlignment(uint32 option_index, OptionElementType position_type) {
438 if (option_index >= GetNumberOptions()) {
439 IF_PRINT_WARNING(VIDEO_DEBUG) << "out-of-range option_index argument: " << option_index << endl;
440 return;
441 }
442 if ((position_type != VIDEO_OPTION_ELEMENT_LEFT_ALIGN) &&
443 (position_type != VIDEO_OPTION_ELEMENT_CENTER_ALIGN) &&
444 (position_type != VIDEO_OPTION_ELEMENT_RIGHT_ALIGN))
445 {
446 IF_PRINT_WARNING(VIDEO_DEBUG) << "invalid position_type argument" << position_type << endl;
447 }
448
449 Option& this_option = _options[option_index];
450 OptionElement new_element;
451
452 new_element.type = position_type;
453 new_element.value = 0;
454 this_option.elements.push_back(new_element);
455 }
456
457
458
SetOptionText(uint32 index,const hoa_utils::ustring & text)459 bool OptionBox::SetOptionText(uint32 index, const hoa_utils::ustring &text) {
460 if (index >= GetNumberOptions()) {
461 IF_PRINT_WARNING(VIDEO_DEBUG) << "argument was invalid (out of bounds): " << index << endl;
462 return false;
463 }
464
465 _ConstructOption(text, _options[index]);
466 return true;
467 }
468
469
470
SetSelection(uint32 index)471 void OptionBox::SetSelection(uint32 index) {
472 if (index >= GetNumberOptions()) {
473 IF_PRINT_WARNING(VIDEO_DEBUG) << "argument was invalid (out of bounds): " << index << endl;
474 return;
475 }
476
477 _selection = index;
478 int32 select_row = _selection / _number_columns;
479
480 // If the new selection isn't currently being displayed, instantly scroll to it
481 if (select_row < _scroll_offset || select_row > (_scroll_offset + _number_rows - 1)) {
482 _scroll_offset = select_row - _number_rows + 1;
483
484 int32 total_num_rows = (GetNumberOptions() + _number_columns - 1) / _number_columns;
485
486 if (_scroll_offset + _number_rows >= total_num_rows) {
487 _scroll_offset = total_num_rows - _number_rows;
488 }
489 }
490 }
491
492
493
EnableOption(uint32 index,bool enable)494 void OptionBox::EnableOption(uint32 index, bool enable) {
495 if (index >= GetNumberOptions()) {
496 IF_PRINT_WARNING(VIDEO_DEBUG) << "argument index was invalid: " << index << endl;
497 return;
498 }
499
500 _options[index].disabled = !enable;
501 }
502
503
504
IsOptionEnabled(uint32 index)505 bool OptionBox::IsOptionEnabled(uint32 index) {
506 if (index >= GetNumberOptions()) {
507 IF_PRINT_WARNING(VIDEO_DEBUG) << "argument index was invalid: " << index << endl;
508 return false;
509 }
510
511 return (!_options[index].disabled);
512 }
513
514
515
IsEnabled(uint32 index) const516 bool OptionBox::IsEnabled(uint32 index) const {
517 if (index >= GetNumberOptions()) {
518 IF_PRINT_WARNING(VIDEO_DEBUG) << "argument index was invalid: " << index << endl;
519 return false;
520 }
521
522 return !_options[index].disabled;
523 }
524
525
526
GetEmbeddedImage(uint32 index) const527 StillImage* OptionBox::GetEmbeddedImage(uint32 index) const {
528 if (index >= GetNumberOptions()) {
529 IF_PRINT_WARNING(VIDEO_DEBUG) << "argument index was invalid: " << index << endl;
530 return nullptr;
531 }
532
533 return _options[index].image;
534 }
535
536
537
IsInitialized(string & error_messages)538 bool OptionBox::IsInitialized(string& error_messages) {
539 ostringstream s;
540 error_messages.clear();
541
542 if (_width <= 0.0f)
543 s << "* Invalid width (" << _width << ")" << endl;
544
545 if (_height <= 0.0f)
546 s << "* Invalid height (" << _height << ")" << endl;
547
548 if (_number_rows <= 0)
549 s << "* Invalid number of rows (" << _number_rows << ")" << endl;
550
551 if (_number_columns <= 0)
552 s << "* Invalid number of columns (" << _number_columns << ")" << endl;
553
554 if (_cell_width <= 0.0f && _number_columns > 1)
555 s << "* Invalid horizontal spacing (" << _cell_width << ")" << endl;
556
557 if (_cell_height <= 0.0f && _number_rows > 1)
558 s << "* Invalid vertical spacing (" << _cell_height << ")" << endl;
559
560 if (_option_xalign < VIDEO_X_LEFT || _option_xalign > VIDEO_X_RIGHT)
561 s << "* Invalid x align (" << _option_xalign << ")" << endl;
562
563 if (_option_yalign < VIDEO_Y_TOP || _option_yalign > VIDEO_Y_BOTTOM)
564 s << "* Invalid y align (" << _option_yalign << ")" << endl;
565
566 if (_text_style.font.empty())
567 s << "* Invalid font (none has been set)" << endl;
568
569 if (_selection_mode <= VIDEO_SELECT_INVALID || _selection_mode >= VIDEO_SELECT_TOTAL)
570 s << "* Invalid selection mode (" << _selection_mode << ")" << endl;
571
572 error_messages = s.str();
573
574 if (error_messages.empty())
575 _initialized = true;
576 else
577 _initialized = false;
578
579 return _initialized;
580 }
581
582 // -----------------------------------------------------------------------------
583 // Input Handling Methods
584 // -----------------------------------------------------------------------------
585
InputConfirm()586 void OptionBox::InputConfirm() {
587 // Abort if an invalid option is selected
588 if (_selection < 0 || _selection >= static_cast<int32>(GetNumberOptions())) {
589 IF_PRINT_WARNING(VIDEO_DEBUG) << "an invalid (out of bounds) option was selected: " << _selection << endl;
590 return;
591 }
592
593 // Ignore input while scrolling, or if an event has already been logged
594 if (_scrolling || _event || _options[_selection].disabled)
595 return;
596
597 // Case #1: switch the position of two different options
598 if (_enable_switching && _first_selection >= 0 && _selection != _first_selection) {
599 Option temp = _options[_selection];
600 _options[_selection] = _options[_first_selection];
601 _options[_first_selection] = temp;
602 _first_selection = -1; // Done so that we know we're not in switching mode any more
603 _event = VIDEO_OPTION_SWITCH;
604 }
605
606 // Case #2: partial confirm (confirming the first element in a double confirm)
607 else if (_selection_mode == VIDEO_SELECT_DOUBLE && _first_selection == -1) {
608 _first_selection = _selection;
609 }
610
611 // Case #3: standard confirm
612 else {
613 if (_options[_selection].disabled) {
614 // TODO: do something to tell player they confirmed on a disabled option?
615 return;
616 }
617 _event = VIDEO_OPTION_CONFIRM;
618 // Get out of switch mode
619 _first_selection = -1;
620 }
621 }
622
623
624
InputCancel()625 void OptionBox::InputCancel() {
626 // Ignore input while scrolling, or if an event has already been logged
627 if (_scrolling || _event)
628 return;
629
630 // If we're in switching mode unselect the first selection
631 if (_first_selection >= 0)
632 _first_selection = -1;
633 else
634 _event = VIDEO_OPTION_CANCEL;
635 }
636
637
638
InputUp()639 void OptionBox::InputUp() {
640 // Ignore input while scrolling, or if an event has already been logged
641 if (_scrolling || _event)
642 return;
643
644 if (_ChangeSelection(-1, false) == false)
645 _event = VIDEO_OPTION_BOUNDS_UP;
646 }
647
648
649
InputDown()650 void OptionBox::InputDown() {
651 // Ignore input while scrolling, or if an event has already been logged
652 if (_scrolling || _event)
653 return;
654
655 if(_ChangeSelection(1, false) == false)
656 _event = VIDEO_OPTION_BOUNDS_DOWN;
657 }
658
659
660
InputLeft()661 void OptionBox::InputLeft() {
662 // Ignore input while scrolling, or if an event has already been logged
663 if (_scrolling || _event)
664 return;
665
666 if (_ChangeSelection(-1, true) == false)
667 _event = VIDEO_OPTION_BOUNDS_LEFT;
668 }
669
670
671
InputRight()672 void OptionBox::InputRight() {
673 // Ignore input while scrolling, or if an event has already been logged
674 if (_scrolling || _event)
675 return;
676
677 if (_ChangeSelection(1, true) == false)
678 _event = VIDEO_OPTION_BOUNDS_RIGHT;
679 }
680
681 // -----------------------------------------------------------------------------
682 // Member Access Functions
683 // -----------------------------------------------------------------------------
684
SetTextStyle(const TextStyle & style)685 void OptionBox::SetTextStyle(const TextStyle& style) {
686 if (TextManager->GetFontProperties(style.font) == NULL) {
687 IF_PRINT_WARNING(VIDEO_DEBUG) << "text style references an invalid font name: " << style.font << endl;
688 return;
689 }
690
691 _text_style = style;
692 _initialized = IsInitialized(_initialization_errors);
693 }
694
695
696
SetCursorState(CursorState state)697 void OptionBox::SetCursorState(CursorState state) {
698 if (state <= VIDEO_CURSOR_STATE_INVALID || state >= VIDEO_CURSOR_STATE_TOTAL) {
699 IF_PRINT_WARNING(VIDEO_DEBUG) << "invalid function argument : " << state << endl;
700 return;
701 }
702
703 _cursor_state = state;
704 }
705
706
707
SetHorizontalArrowsPosition(HORIZONTAL_ARROWS_POSITION position)708 void OptionBox::SetHorizontalArrowsPosition(HORIZONTAL_ARROWS_POSITION position) {
709 _horizontal_arrows_position = position;
710 }
711
712
713
SetVerticalArrowsPosition(VERTICAL_ARROWS_POSITION position)714 void OptionBox::SetVerticalArrowsPosition(VERTICAL_ARROWS_POSITION position) {
715 _vertical_arrows_position = position;
716 }
717
718 // -----------------------------------------------------------------------------
719 // Private Methods
720 // -----------------------------------------------------------------------------
721
_ConstructOption(const ustring & format_string,Option & op)722 bool OptionBox::_ConstructOption(const ustring& format_string, Option& op) {
723 op.Clear();
724
725 // This is a valid case. It simply means we add an option with no tags, text, or other data.
726 if (format_string.empty()) {
727 return true;
728 }
729
730 // Copy the format_string into a temporary string that we can manipulate
731 ustring tmp = format_string;
732
733 while (tmp.empty() == false) {
734 OptionElement new_element;
735
736 if (tmp[0] == OPEN_TAG) { // Process a new tag
737 size_t length = tmp.length();
738
739 if (length < 3) {
740 // All formatting tags are at least 3 characters long because you need the opening (<)
741 // and close (>) plus stuff in the middle. So anything less than 3 characters is a problem.
742
743 IF_PRINT_WARNING(VIDEO_DEBUG) << "failed because a tag opening was detected with an inadequate "
744 << "number of remaining characters to construct a full tag: " << MakeStandardString(format_string) << endl;
745 return false;
746 }
747
748 size_t end_position = tmp.find(END_TAG);
749
750 if (end_position == ustring::npos) { // Did not find the end of the tag
751 IF_PRINT_WARNING(VIDEO_DEBUG) << "failed because a matching end tag could not be found for an open tag: "
752 << MakeStandardString(format_string) << endl;
753 return false;
754 }
755
756 if (tmp[2] == END_TAG) { // First check if the tag is a 1-character alignment tag
757 if (tmp[1] == CENTER_TAG1 || tmp[1] == CENTER_TAG2) {
758 new_element.type = VIDEO_OPTION_ELEMENT_CENTER_ALIGN;
759 }
760 else if (tmp[1] == RIGHT_TAG1 || tmp[1] == RIGHT_TAG2) {
761 new_element.type = VIDEO_OPTION_ELEMENT_RIGHT_ALIGN;
762 }
763 else if (tmp[1] == LEFT_TAG1 || tmp[1] == LEFT_TAG2) {
764 new_element.type = VIDEO_OPTION_ELEMENT_LEFT_ALIGN;
765 }
766 }
767 else { // The tag contains more than 1-character
768 // Extract the tag string
769 string tag_text = MakeStandardString(tmp.substr(1, end_position - 1));
770
771 if (IsStringNumeric(tag_text)) { // Then this must be a positioning tag
772 new_element.type = VIDEO_OPTION_ELEMENT_POSITION;
773 new_element.value = atoi(tag_text.c_str());
774 }
775 else { // Then this must be an image tag
776 if (op.image != NULL) {
777 IF_PRINT_WARNING(VIDEO_DEBUG) << "failed because two image tags were embedded within a single option"
778 << MakeStandardString(format_string) << endl;
779 return false;
780 }
781 op.image = new StillImage();
782 if (op.image->Load(tag_text) == false) {
783 IF_PRINT_WARNING(VIDEO_DEBUG) << "failed because of an invalid image tag: "
784 << MakeStandardString(format_string) << endl;
785 return false;
786 }
787 new_element.type = VIDEO_OPTION_ELEMENT_IMAGE;
788 new_element.value = 0;
789 }
790 }
791
792 // Finished processing the tag so update the tmp string
793 if (end_position == length - 1) { // End of string
794 tmp.clear();
795 }
796 else {
797 tmp = tmp.substr(end_position + 1, length - end_position - 1);
798 }
799 } // if (tmp[0] == OPEN_TAG)
800
801 else { // If this isn't a tag, then it is raw text that should be added to the option
802 new_element.type = VIDEO_OPTION_ELEMENT_TEXT;
803 new_element.value = static_cast<int32>(op.text.size());
804
805 // find the distance until the next tag
806 size_t tag_begin = tmp.find(OPEN_TAG);
807
808 if (tag_begin == ustring::npos) { // There are no more tags remaining, so extract the entire string
809 op.text.push_back(tmp);
810 tmp.clear();
811 }
812 else { // Another tag remains to be processed, so extract the text substring
813 op.text.push_back(tmp.substr(0, tag_begin));
814 tmp = tmp.substr(tag_begin, tmp.length() - tag_begin);
815 }
816 }
817
818 op.elements.push_back(new_element);
819 } // while (tmp.empty() == false)
820
821 return true;
822 } // bool _ConstructOption(const ustring& format_string, Option& option)
823
824
825
_ChangeSelection(int32 offset,bool horizontal)826 bool OptionBox::_ChangeSelection(int32 offset, bool horizontal) {
827 // Do nothing if the movement is horizontal and there is only one column with no horizontal wrap shifting
828 if (horizontal == true && _number_cell_columns == 1 && _horizontal_wrap_mode != VIDEO_WRAP_MODE_SHIFTED)
829 return false;
830
831 // Do nothing if the movement is vertical and there is only one row with no vertical wrap shifting
832 if (horizontal == false && _number_cell_rows == 1 && _vertical_wrap_mode != VIDEO_WRAP_MODE_SHIFTED)
833 return false;
834
835 // Get the row, column coordinates for the current selection
836 int32 row = _selection / _number_columns;
837 int32 col = _selection % _number_columns;
838 bool bounds_exceeded = false;
839
840 // Determine if the movement selection will exceed a column or row bondary
841 int new_row = (row + offset) * _number_columns;
842 if ((horizontal == true && ((col + offset < 0) || (col + offset >= _number_columns) ||
843 (col + offset >= static_cast<int32>(GetNumberOptions())))) ||
844 (horizontal == false && ((new_row < 0) || (new_row >= _number_rows) ||
845 (new_row >= static_cast<int32>(GetNumberOptions())))))
846 {
847 bounds_exceeded = true;
848 }
849
850 // Case #1: movement selection is within bounds
851 if (bounds_exceeded == false) {
852 if (horizontal)
853 _selection += offset;
854 else
855 _selection += (offset * _number_columns);
856 }
857
858 // Case #2: movement exceeds bounds, no wrapping enabled
859 else if ((horizontal == true && _horizontal_wrap_mode == VIDEO_WRAP_MODE_NONE) ||
860 (horizontal == false && _vertical_wrap_mode == VIDEO_WRAP_MODE_NONE))
861 {
862 return false;
863 }
864
865 // Case #3: horizontal movement with wrapping enabled
866 else if (horizontal == true) {
867 if (col + offset <= 0) { // The left boundary was exceeded
868 if (_horizontal_wrap_mode == VIDEO_WRAP_MODE_STRAIGHT) {
869 offset = _number_columns - 1;
870 }
871 // Make sure vertical wrapping is allowed if horizontal wrap mode is shifting
872 else if (_horizontal_wrap_mode == VIDEO_WRAP_MODE_SHIFTED && _vertical_wrap_mode != VIDEO_WRAP_MODE_NONE) {
873 offset += GetNumberOptions();
874 }
875 else {
876 return false;
877 }
878 }
879 else { // The right boundary was exceeded
880 if (_horizontal_wrap_mode == VIDEO_WRAP_MODE_STRAIGHT)
881 offset -= _number_columns;
882 // Make sure vertical wrapping is allowed if horizontal wrap mode is shifting
883 else if (_horizontal_wrap_mode == VIDEO_WRAP_MODE_SHIFTED && _vertical_wrap_mode != VIDEO_WRAP_MODE_NONE) {
884 offset = 0;
885 _selection++;
886 }
887 else
888 return false;
889 }
890 _selection = (_selection + offset) % GetNumberOptions();
891 }
892
893 // Case #4: vertical movement with wrapping enabled
894 else {
895 if (row + offset <= 0) { // The top boundary was exceeded
896 if (_vertical_wrap_mode == VIDEO_WRAP_MODE_STRAIGHT)
897 offset += GetNumberOptions();
898 // Make sure horizontal wrapping is allowed if vertical wrap mode is shifting
899 else if (_vertical_wrap_mode == VIDEO_WRAP_MODE_SHIFTED && _horizontal_wrap_mode != VIDEO_WRAP_MODE_NONE)
900 offset += (_number_columns - 1);
901 else
902 return false;
903 }
904 else { // The bottom boundary was exceeded
905 if (_vertical_wrap_mode == VIDEO_WRAP_MODE_STRAIGHT) {
906 if (row + offset >= _number_rows)
907 offset -= GetNumberOptions();
908 }
909 // Make sure horizontal wrapping is allowed if vertical wrap mode is shifting
910 else if (_vertical_wrap_mode == VIDEO_WRAP_MODE_SHIFTED && _horizontal_wrap_mode != VIDEO_WRAP_MODE_NONE)
911 offset -= (_number_columns - 1);
912 else
913 return false;
914 }
915 _selection = (_selection + (offset * _number_columns)) % GetNumberOptions();
916 }
917
918 // Determine if the new selection is not displayed in any cells. If so, scroll it into view.
919 int32 selection_row = _selection / _number_columns;
920 int32 selection_col = _selection % _number_columns;
921
922 if ((static_cast<uint32>(selection_row) < _draw_top_row)) {
923 _scrolling = true;
924 _scroll_time = 0;
925 _draw_top_row = selection_row;
926
927 if (selection_row < _scroll_offset)
928 _scroll_direction = -1 * (_scroll_offset - row); // scroll up
929 else
930 _scroll_direction = 1 * (row - _number_rows - _scroll_offset + 1); // scroll down
931
932 _scroll_offset += _scroll_direction;
933 }
934
935 else if ((static_cast<uint32>(selection_row) >= (_draw_top_row + _number_cell_rows)) ) {
936 _scrolling = true;
937 _scroll_time = 0;
938 _draw_top_row = selection_row - _number_cell_rows + 1;
939
940 if (selection_row < _scroll_offset)
941 _scroll_direction = -1 * (_scroll_offset - row); // scroll up
942 else
943 _scroll_direction = 1 * (row - _number_rows - _scroll_offset + 1); // scroll down
944
945 _scroll_offset += _scroll_direction;
946 }
947
948 else if ((static_cast<uint32>(selection_col) < _draw_left_column)) {
949 _scrolling = true;
950 _scroll_time = 0;
951 _draw_left_column = selection_col;
952
953 if (selection_row < _scroll_offset)
954 _scroll_direction = -1 * (_scroll_offset - row); // scroll up
955 else
956 _scroll_direction = 1 * (row - _number_rows - _scroll_offset + 1); // scroll down
957
958 _scroll_offset += _scroll_direction;
959 }
960
961 else if ((static_cast<uint32>(selection_col) >= (_draw_left_column + _number_cell_columns))) {
962 _scrolling = true;
963 _scroll_time = 0;
964 _draw_left_column = selection_col - _number_cell_columns + 1;
965
966 if (selection_row < _scroll_offset)
967 _scroll_direction = -1 * (_scroll_offset - row); // scroll up
968 else
969 _scroll_direction = 1 * (row - _number_rows - _scroll_offset + 1); // scroll down
970
971 _scroll_offset += _scroll_direction;
972 }
973
974 // If the new selection isn't currently being displayed, scroll it into view
975 // row = _selection / _number_columns;
976 // if (row < _scroll_offset || row >= _scroll_offset + _number_rows) {
977 // _scrolling = true;
978 // _scroll_time = 0;
979 //
980 // if (row < _scroll_offset)
981 // _scroll_direction = -1 * (_scroll_offset - row); // scroll up
982 // else
983 // _scroll_direction = 1 * (row - _number_rows - _scroll_offset + 1); // scroll down
984 //
985 // _scroll_offset += _scroll_direction;
986 // }
987
988 _event = VIDEO_OPTION_SELECTION_CHANGE;
989 return true;
990 } // bool OptionBox::_ChangeSelection(int32 offset, bool horizontal)
991
992
993
_SetupAlignment(int32 xalign,int32 yalign,const OptionCellBounds & bounds,float & x,float & y)994 void OptionBox::_SetupAlignment(int32 xalign, int32 yalign, const OptionCellBounds& bounds, float& x, float& y) {
995 VideoManager->SetDrawFlags(xalign, yalign, 0);
996
997 switch (xalign) {
998 case VIDEO_X_LEFT:
999 x = bounds.x_left;
1000 break;
1001 case VIDEO_X_CENTER:
1002 x = bounds.x_center;
1003 break;
1004 default:
1005 x = bounds.x_right;
1006 break;
1007 }
1008
1009 switch (yalign) {
1010 case VIDEO_Y_TOP:
1011 y = bounds.y_top;
1012 break;
1013 case VIDEO_Y_CENTER:
1014 y = bounds.y_center;
1015 break;
1016 default:
1017 y = bounds.y_bottom;
1018 break;
1019 }
1020
1021 VideoManager->Move(x, y);
1022 } // void OptionBox::_SetupAlignment(int32 xalign, int32 yalign, const OptionCellBounds& bounds, float& x, float& y)
1023
1024
1025
_DetermineScrollArrows()1026 void OptionBox::_DetermineScrollArrows() {
1027 _grey_up_arrow = false;
1028 _grey_down_arrow = false;
1029 _grey_left_arrow = false;
1030 _grey_right_arrow = false;
1031
1032 _draw_horizontal_arrows = (_number_cell_columns < _number_columns) && (static_cast<int32>(GetNumberOptions()) > _number_cell_columns);
1033 _draw_vertical_arrows = (_number_cell_rows < _number_rows) && (static_cast<int32>(GetNumberOptions()) > _number_columns * _number_cell_rows);
1034
1035 if (_horizontal_wrap_mode == VIDEO_WRAP_MODE_NONE) {
1036 if (_draw_left_column == 0)
1037 _grey_left_arrow = true;
1038 if (static_cast<int32>(_draw_left_column + _number_cell_columns) >= _number_columns)
1039 _grey_right_arrow = true;
1040 if (_selection >= static_cast<int32>(_options.size() - 1))
1041 _grey_right_arrow = true;
1042 }
1043
1044 if (_vertical_wrap_mode == VIDEO_WRAP_MODE_NONE) {
1045 if (_draw_top_row == 0)
1046 _grey_up_arrow = true;
1047 if (static_cast<int32>(_draw_top_row + _number_cell_rows) > _number_rows)
1048 _grey_down_arrow = true;
1049 if (_selection + _number_cell_columns >= static_cast<int32>(_options.size()))
1050 _grey_down_arrow = true;
1051 }
1052 }
1053
1054
1055
_DrawOption(const Option & op,const OptionCellBounds & bounds,float scroll_offset,float & left_edge)1056 void OptionBox::_DrawOption(const Option& op, const OptionCellBounds &bounds, float scroll_offset, float& left_edge) {
1057 // TODO: this function doesn't make use of the scroll_offset parameter currently, but I'm pretty sure it is
1058 // needed somewhere to get scrolling full working. Once the scrolling feature has been enabled and verified
1059 // for correctness if this paramater is still unused, remove it.
1060
1061 float x, y;
1062 int32 xalign = _option_xalign;
1063 int32 yalign = _option_yalign;
1064 CoordSys &cs = VideoManager->_current_context.coordinate_system;
1065
1066 _SetupAlignment(xalign, yalign, bounds, x, y);
1067
1068 // Iterate through all option elements in the current option
1069 for (int32 element = 0; element < static_cast<int32>(op.elements.size()); element++) {
1070 switch (op.elements[element].type) {
1071 case VIDEO_OPTION_ELEMENT_LEFT_ALIGN:
1072 {
1073 xalign = VIDEO_X_LEFT;
1074 _SetupAlignment(xalign, _option_yalign, bounds, x, y);
1075 break;
1076 }
1077 case VIDEO_OPTION_ELEMENT_CENTER_ALIGN:
1078 {
1079 xalign = VIDEO_X_CENTER;
1080 _SetupAlignment(xalign, _option_yalign, bounds, x, y);
1081 break;
1082 }
1083 case VIDEO_OPTION_ELEMENT_RIGHT_ALIGN:
1084 {
1085 xalign = VIDEO_X_RIGHT;
1086 _SetupAlignment(xalign, _option_yalign, bounds, x, y);
1087 break;
1088 }
1089 case VIDEO_OPTION_ELEMENT_IMAGE:
1090 {
1091 if (op.disabled)
1092 op.image->Draw(Color::gray);
1093 else
1094 op.image->Draw(Color::white);
1095
1096 float width = op.image->GetWidth();
1097 float edge = x - bounds.x_left; // edge value for VIDEO_X_LEFT
1098 if (xalign == VIDEO_X_CENTER)
1099 edge -= width * 0.5f * cs.GetHorizontalDirection();
1100 else if (xalign == VIDEO_X_RIGHT)
1101 edge -= width * cs.GetHorizontalDirection();
1102 if (edge < left_edge)
1103 left_edge = edge;
1104 break;
1105 }
1106 case VIDEO_OPTION_ELEMENT_POSITION:
1107 {
1108 x = bounds.x_left + op.elements[element].value * cs.GetHorizontalDirection();
1109 VideoManager->Move(x, y);
1110 break;
1111 }
1112 case VIDEO_OPTION_ELEMENT_TEXT:
1113 {
1114 int32 text_index = op.elements[element].value;
1115
1116 if (text_index >= 0 && text_index < static_cast<int32>(op.text.size())) {
1117 const ustring& text = op.text[text_index];
1118 float width = static_cast<float>(VideoManager->Text()->CalculateTextWidth(_text_style.font, text));
1119 float edge = x - bounds.x_left; // edge value for VIDEO_X_LEFT
1120
1121 if (xalign == VIDEO_X_CENTER)
1122 edge -= width * 0.5f * cs.GetHorizontalDirection();
1123 else if (xalign == VIDEO_X_RIGHT)
1124 edge -= width * cs.GetHorizontalDirection();
1125
1126 if (edge < left_edge)
1127 left_edge = edge;
1128 if (op.disabled) {
1129 Color saved = _text_style.color;
1130 _text_style.color = Color::gray;
1131 TextManager->Draw(text, _text_style);
1132 _text_style.color = saved;
1133 }
1134 else {
1135 TextManager->Draw(text, _text_style);
1136 }
1137 }
1138
1139 break;
1140 }
1141 case VIDEO_OPTION_ELEMENT_INVALID:
1142 case VIDEO_OPTION_ELEMENT_TOTAL:
1143 default:
1144 {
1145 IF_PRINT_WARNING(VIDEO_DEBUG) << "invalid option element type was present" << endl;
1146 break;
1147 }
1148 } // switch (op.elements[element].type)
1149 } // for (int32 element = 0; element < static_cast<int32>(op.elements.size()); element++)
1150 } // void OptionBox::_DrawOption(const Option& op, const OptionCellBounds &bounds, float scroll_offset, float& left_edge)
1151
1152
1153
_DrawCursor(const OptionCellBounds & bounds,float scroll_offset,float left_edge,bool darken)1154 void OptionBox::_DrawCursor(const OptionCellBounds &bounds, float scroll_offset, float left_edge, bool darken) {
1155 // [phuedx] In this case the scroll offset is not used, however it should be.
1156 // The Draw() function (and all helper functions) should be able able to
1157 // render without knowledge of the private member variable _scroll_offset.
1158
1159 float x, y;
1160
1161 // Should never scissor the cursor
1162 VideoManager->DisableScissoring();
1163
1164 float cursor_offset = 0.0f;
1165
1166 // [phuedx] The scroll_offset has already been calculated and projected on to the current coordinate system
1167 if (_scrolling) {
1168 cursor_offset = -scroll_offset;
1169 }
1170
1171 _SetupAlignment(VIDEO_X_LEFT, _option_yalign, bounds, x, y);
1172 VideoManager->SetDrawFlags(VIDEO_BLEND, 0);
1173 VideoManager->MoveRelative(left_edge + _cursor_xoffset, _cursor_yoffset + cursor_offset);
1174
1175 StillImage *default_cursor = VideoManager->GetDefaultCursor();
1176
1177 if (default_cursor == NULL)
1178 IF_PRINT_WARNING(VIDEO_DEBUG) << "invalid (NULL) cursor image" << endl;
1179
1180 if (darken == false)
1181 default_cursor->Draw();
1182 else
1183 default_cursor->Draw(Color(1.0f, 1.0f, 1.0f, 0.5f));
1184 } // void OptionBox::_DrawCursor(const OptionCellBounds &bounds, float scroll_offset, float left_edge, bool darken)
1185
1186
1187
_DEBUG_DrawOutline()1188 void OptionBox::_DEBUG_DrawOutline() {
1189 float left = 0.0f;
1190 float right = _width;
1191 float bottom = 0.0f;
1192 float top = _height;
1193
1194 // Draw the outline of the option box area
1195 VideoManager->Move(0.0f, 0.0f);
1196 CalculateAlignedRect(left, right, bottom, top);
1197 VideoManager->DrawRectangleOutline(left, right, bottom, top, 3, alpha_black);
1198 VideoManager->DrawRectangleOutline(left, right, bottom, top, 1, alpha_white);
1199
1200 // Draw outline for inner cell rows
1201 float cell_row = top;
1202 for (int32 i = 1; i < _number_cell_rows; i++) {
1203 cell_row += _cell_height;
1204 VideoManager->DrawLine(left, cell_row, right, cell_row, 3, alpha_black);
1205 VideoManager->DrawLine(left, cell_row, right, cell_row, 1, alpha_white);
1206 }
1207
1208 // Draw outline for inner cell columns
1209 float cell_col = left;
1210 for (int32 i = 1; i < _number_cell_columns; i++) {
1211 cell_col += _cell_width;
1212 VideoManager->DrawLine(cell_col, bottom, cell_col, top, 3, alpha_black);
1213 VideoManager->DrawLine(cell_col, bottom, cell_col, top, 1, alpha_white);
1214 }
1215 }
1216
1217 } // namespace hoa_gui
1218