1 /* Copyright (C) 2018 Wildfire Games.
2 * This file is part of 0 A.D.
3 *
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "precompiled.h"
19
20 #include "CList.h"
21
22 #include "CGUIScrollBarVertical.h"
23
24 #include "lib/external_libraries/libsdl.h"
25 #include "ps/CLogger.h"
26 #include "ps/Profile.h"
27 #include "soundmanager/ISoundManager.h"
28
29
CList()30 CList::CList()
31 : m_Modified(false), m_PrevSelectedItem(-1), m_LastItemClickTime(0)
32 {
33 // Add sprite_disabled! TODO
34
35 AddSetting(GUIST_float, "buffer_zone");
36 AddSetting(GUIST_CStrW, "font");
37 AddSetting(GUIST_bool, "scrollbar");
38 AddSetting(GUIST_CStr, "scrollbar_style");
39 AddSetting(GUIST_CStrW, "sound_disabled");
40 AddSetting(GUIST_CStrW, "sound_selected");
41 AddSetting(GUIST_CGUISpriteInstance, "sprite");
42 AddSetting(GUIST_CGUISpriteInstance, "sprite_selectarea");
43 AddSetting(GUIST_int, "cell_id");
44 AddSetting(GUIST_EAlign, "text_align");
45 AddSetting(GUIST_CColor, "textcolor");
46 AddSetting(GUIST_CColor, "textcolor_selected");
47 AddSetting(GUIST_int, "selected"); // Index selected. -1 is none.
48 AddSetting(GUIST_bool, "auto_scroll");
49 AddSetting(GUIST_int, "hovered");
50 AddSetting(GUIST_CStrW, "tooltip");
51 AddSetting(GUIST_CStr, "tooltip_style");
52
53 // Each list item has both a name (in 'list') and an associated data string (in 'list_data')
54 AddSetting(GUIST_CGUIList, "list");
55 AddSetting(GUIST_CGUIList, "list_data"); // TODO: this should be a list of raw strings, not of CGUIStrings
56
57 GUI<bool>::SetSetting(this, "scrollbar", false);
58 GUI<int>::SetSetting(this, "selected", -1);
59 GUI<int>::SetSetting(this, "hovered", -1);
60 GUI<bool>::SetSetting(this, "auto_scroll", false);
61
62 // Add scroll-bar
63 CGUIScrollBarVertical* bar = new CGUIScrollBarVertical();
64 bar->SetRightAligned(true);
65 AddScrollBar(bar);
66 }
67
~CList()68 CList::~CList()
69 {
70 }
71
SetupText()72 void CList::SetupText()
73 {
74 if (!GetGUI())
75 return;
76
77 m_Modified = true;
78 CGUIList* pList;
79 GUI<CGUIList>::GetSettingPointer(this, "list", pList);
80
81 //ENSURE(m_GeneratedTexts.size()>=1);
82
83 m_ItemsYPositions.resize(pList->m_Items.size()+1);
84
85 // Delete all generated texts. Some could probably be saved,
86 // but this is easier, and this function will never be called
87 // continuously, or even often, so it'll probably be okay.
88 for (SGUIText* const& t : m_GeneratedTexts)
89 delete t;
90 m_GeneratedTexts.clear();
91
92 CStrW font;
93 if (GUI<CStrW>::GetSetting(this, "font", font) != PSRETURN_OK || font.empty())
94 // Use the default if none is specified
95 // TODO Gee: (2004-08-14) Don't define standard like this. Do it with the default style.
96 font = L"default";
97
98 bool scrollbar;
99 GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
100
101 float width = GetListRect().GetWidth();
102 // remove scrollbar if applicable
103 if (scrollbar && GetScrollBar(0).GetStyle())
104 width -= GetScrollBar(0).GetStyle()->m_Width;
105
106 float buffer_zone = 0.f;
107 GUI<float>::GetSetting(this, "buffer_zone", buffer_zone);
108
109 // Generate texts
110 float buffered_y = 0.f;
111
112 for (size_t i = 0; i < pList->m_Items.size(); ++i)
113 {
114 // Create a new SGUIText. Later on, input it using AddText()
115 SGUIText* text = new SGUIText();
116
117 if (!pList->m_Items[i].GetOriginalString().empty())
118 *text = GetGUI()->GenerateText(pList->m_Items[i], font, width, buffer_zone, this);
119 else
120 {
121 // Minimum height of a space character of the current font size
122 CGUIString align_string;
123 align_string.SetValue(L" ");
124 *text = GetGUI()->GenerateText(align_string, font, width, buffer_zone, this);
125 }
126
127 m_ItemsYPositions[i] = buffered_y;
128 buffered_y += text->m_Size.cy;
129
130 AddText(text);
131 }
132
133 m_ItemsYPositions[pList->m_Items.size()] = buffered_y;
134
135 // Setup scrollbar
136 if (scrollbar)
137 {
138 GetScrollBar(0).SetScrollRange(m_ItemsYPositions.back());
139 GetScrollBar(0).SetScrollSpace(GetListRect().GetHeight());
140
141 CRect rect = GetListRect();
142 GetScrollBar(0).SetX(rect.right);
143 GetScrollBar(0).SetY(rect.top);
144 GetScrollBar(0).SetZ(GetBufferedZ());
145 GetScrollBar(0).SetLength(rect.bottom - rect.top);
146 }
147 }
148
HandleMessage(SGUIMessage & Message)149 void CList::HandleMessage(SGUIMessage& Message)
150 {
151 IGUIScrollBarOwner::HandleMessage(Message);
152 //IGUITextOwner::HandleMessage(Message); <== placed it after the switch instead!
153
154 m_Modified = false;
155 switch (Message.type)
156 {
157 case GUIM_SETTINGS_UPDATED:
158 if (Message.value == "list")
159 SetupText();
160
161 // If selected is changed, call "SelectionChange"
162 if (Message.value == "selected")
163 {
164 // TODO: Check range
165
166 bool auto_scroll;
167
168 GUI<bool>::GetSetting(this, "auto_scroll", auto_scroll);
169
170 if (auto_scroll)
171 UpdateAutoScroll();
172
173 // TODO only works if lower-case, shouldn't it be made case sensitive instead?
174 ScriptEvent("selectionchange");
175 }
176
177 if (Message.value == "scrollbar")
178 SetupText();
179
180 // Update scrollbar
181 if (Message.value == "scrollbar_style")
182 {
183 CStr scrollbar_style;
184 GUI<CStr>::GetSetting(this, Message.value, scrollbar_style);
185
186 GetScrollBar(0).SetScrollBarStyle(scrollbar_style);
187
188 SetupText();
189 }
190
191 break;
192
193 case GUIM_MOUSE_PRESS_LEFT:
194 {
195 bool enabled;
196 GUI<bool>::GetSetting(this, "enabled", enabled);
197 if (!enabled)
198 {
199 CStrW soundPath;
200 if (g_SoundManager && GUI<CStrW>::GetSetting(this, "sound_disabled", soundPath) == PSRETURN_OK && !soundPath.empty())
201 g_SoundManager->PlayAsUI(soundPath.c_str(), false);
202 break;
203 }
204
205 int hovered = GetHoveredItem();
206 if (hovered == -1)
207 break;
208 GUI<int>::SetSetting(this, "selected", hovered);
209 UpdateAutoScroll();
210
211 CStrW soundPath;
212 if (g_SoundManager && GUI<CStrW>::GetSetting(this, "sound_selected", soundPath) == PSRETURN_OK && !soundPath.empty())
213 g_SoundManager->PlayAsUI(soundPath.c_str(), false);
214
215 if (timer_Time() - m_LastItemClickTime < SELECT_DBLCLICK_RATE && hovered == m_PrevSelectedItem)
216 this->SendEvent(GUIM_MOUSE_DBLCLICK_LEFT_ITEM, "mouseleftdoubleclickitem");
217 else
218 this->SendEvent(GUIM_MOUSE_PRESS_LEFT_ITEM, "mouseleftclickitem");
219
220 m_LastItemClickTime = timer_Time();
221 m_PrevSelectedItem = hovered;
222 break;
223 }
224
225 case GUIM_MOUSE_LEAVE:
226 {
227 int hoveredSetting = -1;
228 GUI<int>::GetSetting(this, "hovered", hoveredSetting);
229 if (hoveredSetting == -1)
230 break;
231
232 GUI<int>::SetSetting(this, "hovered", -1);
233 ScriptEvent("hoverchange");
234 break;
235 }
236
237 case GUIM_MOUSE_OVER:
238 {
239 int hoveredSetting = -1;
240 GUI<int>::GetSetting(this, "hovered", hoveredSetting);
241
242 int hovered = GetHoveredItem();
243 if (hovered == hoveredSetting)
244 break;
245
246 GUI<int>::SetSetting(this, "hovered", hovered);
247 ScriptEvent("hoverchange");
248 break;
249 }
250
251 case GUIM_LOAD:
252 {
253 CStr scrollbar_style;
254 GUI<CStr>::GetSetting(this, "scrollbar_style", scrollbar_style);
255 GetScrollBar(0).SetScrollBarStyle(scrollbar_style);
256 break;
257 }
258
259 default:
260 break;
261 }
262
263 IGUITextOwner::HandleMessage(Message);
264 }
265
ManuallyHandleEvent(const SDL_Event_ * ev)266 InReaction CList::ManuallyHandleEvent(const SDL_Event_* ev)
267 {
268 InReaction result = IN_PASS;
269
270 if (ev->ev.type == SDL_KEYDOWN)
271 {
272 int szChar = ev->ev.key.keysym.sym;
273
274 switch (szChar)
275 {
276 case SDLK_HOME:
277 SelectFirstElement();
278 UpdateAutoScroll();
279 result = IN_HANDLED;
280 break;
281
282 case SDLK_END:
283 SelectLastElement();
284 UpdateAutoScroll();
285 result = IN_HANDLED;
286 break;
287
288 case SDLK_UP:
289 SelectPrevElement();
290 UpdateAutoScroll();
291 result = IN_HANDLED;
292 break;
293
294 case SDLK_DOWN:
295 SelectNextElement();
296 UpdateAutoScroll();
297 result = IN_HANDLED;
298 break;
299
300 case SDLK_PAGEUP:
301 GetScrollBar(0).ScrollMinusPlenty();
302 result = IN_HANDLED;
303 break;
304
305 case SDLK_PAGEDOWN:
306 GetScrollBar(0).ScrollPlusPlenty();
307 result = IN_HANDLED;
308 break;
309
310 default: // Do nothing
311 result = IN_PASS;
312 }
313 }
314
315 return result;
316 }
317
Draw()318 void CList::Draw()
319 {
320 int selected;
321 GUI<int>::GetSetting(this, "selected", selected);
322
323 DrawList(selected, "sprite", "sprite_selectarea", "textcolor");
324 }
325
DrawList(const int & selected,const CStr & _sprite,const CStr & _sprite_selected,const CStr & _textcolor)326 void CList::DrawList(const int& selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor)
327 {
328 float bz = GetBufferedZ();
329
330 // First call draw on ScrollBarOwner
331 bool scrollbar;
332 GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
333
334 if (scrollbar)
335 IGUIScrollBarOwner::Draw();
336
337 if (GetGUI())
338 {
339 CRect rect = GetListRect();
340
341 CGUISpriteInstance* sprite = NULL;
342 CGUISpriteInstance* sprite_selectarea = NULL;
343 int cell_id;
344 GUI<CGUISpriteInstance>::GetSettingPointer(this, _sprite, sprite);
345 GUI<CGUISpriteInstance>::GetSettingPointer(this, _sprite_selected, sprite_selectarea);
346 GUI<int>::GetSetting(this, "cell_id", cell_id);
347
348 CGUIList* pList;
349 GUI<CGUIList>::GetSettingPointer(this, "list", pList);
350
351 GetGUI()->DrawSprite(*sprite, cell_id, bz, rect);
352
353 float scroll = 0.f;
354 if (scrollbar)
355 scroll = GetScrollBar(0).GetPos();
356
357 if (selected >= 0 && selected+1 < (int)m_ItemsYPositions.size())
358 {
359 // Get rectangle of selection:
360 CRect rect_sel(rect.left, rect.top + m_ItemsYPositions[selected] - scroll,
361 rect.right, rect.top + m_ItemsYPositions[selected+1] - scroll);
362
363 if (rect_sel.top <= rect.bottom &&
364 rect_sel.bottom >= rect.top)
365 {
366 if (rect_sel.bottom > rect.bottom)
367 rect_sel.bottom = rect.bottom;
368 if (rect_sel.top < rect.top)
369 rect_sel.top = rect.top;
370
371 if (scrollbar)
372 {
373 // Remove any overlapping area of the scrollbar.
374 if (rect_sel.right > GetScrollBar(0).GetOuterRect().left &&
375 rect_sel.right <= GetScrollBar(0).GetOuterRect().right)
376 rect_sel.right = GetScrollBar(0).GetOuterRect().left;
377
378 if (rect_sel.left >= GetScrollBar(0).GetOuterRect().left &&
379 rect_sel.left < GetScrollBar(0).GetOuterRect().right)
380 rect_sel.left = GetScrollBar(0).GetOuterRect().right;
381 }
382
383 GetGUI()->DrawSprite(*sprite_selectarea, cell_id, bz+0.05f, rect_sel);
384 }
385 }
386
387 CColor color;
388 GUI<CColor>::GetSetting(this, _textcolor, color);
389
390 for (size_t i = 0; i < pList->m_Items.size(); ++i)
391 {
392 if (m_ItemsYPositions[i+1] - scroll < 0 ||
393 m_ItemsYPositions[i] - scroll > rect.GetHeight())
394 continue;
395
396 // Clipping area (we'll have to substract the scrollbar)
397 CRect cliparea = GetListRect();
398
399 if (scrollbar)
400 {
401 if (cliparea.right > GetScrollBar(0).GetOuterRect().left &&
402 cliparea.right <= GetScrollBar(0).GetOuterRect().right)
403 cliparea.right = GetScrollBar(0).GetOuterRect().left;
404
405 if (cliparea.left >= GetScrollBar(0).GetOuterRect().left &&
406 cliparea.left < GetScrollBar(0).GetOuterRect().right)
407 cliparea.left = GetScrollBar(0).GetOuterRect().right;
408 }
409
410 DrawText(i, color, rect.TopLeft() - CPos(0.f, scroll - m_ItemsYPositions[i]), bz+0.1f, cliparea);
411 }
412 }
413 }
414
AddItem(const CStrW & str,const CStrW & data)415 void CList::AddItem(const CStrW& str, const CStrW& data)
416 {
417 CGUIList* pList;
418 CGUIList* pListData;
419 GUI<CGUIList>::GetSettingPointer(this, "list", pList);
420 GUI<CGUIList>::GetSettingPointer(this, "list_data", pListData);
421
422 CGUIString gui_string;
423 gui_string.SetValue(str);
424 pList->m_Items.push_back(gui_string);
425
426 CGUIString data_string;
427 data_string.SetValue(data);
428 pListData->m_Items.push_back(data_string);
429
430 // TODO Temp
431 SetupText();
432 }
433
HandleAdditionalChildren(const XMBElement & child,CXeromyces * pFile)434 bool CList::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile)
435 {
436 int elmt_item = pFile->GetElementID("item");
437
438 if (child.GetNodeName() == elmt_item)
439 {
440 AddItem(child.GetText().FromUTF8(), child.GetText().FromUTF8());
441 return true;
442 }
443
444 return false;
445 }
446
SelectNextElement()447 void CList::SelectNextElement()
448 {
449 int selected;
450 GUI<int>::GetSetting(this, "selected", selected);
451
452 CGUIList* pList;
453 GUI<CGUIList>::GetSettingPointer(this, "list", pList);
454
455 if (selected != (int)pList->m_Items.size()-1)
456 {
457 ++selected;
458 GUI<int>::SetSetting(this, "selected", selected);
459
460 CStrW soundPath;
461 if (g_SoundManager && GUI<CStrW>::GetSetting(this, "sound_selected", soundPath) == PSRETURN_OK && !soundPath.empty())
462 g_SoundManager->PlayAsUI(soundPath.c_str(), false);
463 }
464 }
465
SelectPrevElement()466 void CList::SelectPrevElement()
467 {
468 int selected;
469 GUI<int>::GetSetting(this, "selected", selected);
470
471 if (selected > 0)
472 {
473 --selected;
474 GUI<int>::SetSetting(this, "selected", selected);
475
476 CStrW soundPath;
477 if (g_SoundManager && GUI<CStrW>::GetSetting(this, "sound_selected", soundPath) == PSRETURN_OK && !soundPath.empty())
478 g_SoundManager->PlayAsUI(soundPath.c_str(), false);
479 }
480 }
481
SelectFirstElement()482 void CList::SelectFirstElement()
483 {
484 int selected;
485 GUI<int>::GetSetting(this, "selected", selected);
486
487 if (selected >= 0)
488 GUI<int>::SetSetting(this, "selected", 0);
489 }
490
SelectLastElement()491 void CList::SelectLastElement()
492 {
493 int selected;
494 GUI<int>::GetSetting(this, "selected", selected);
495
496 CGUIList* pList;
497 GUI<CGUIList>::GetSettingPointer(this, "list", pList);
498
499 if (selected != (int)pList->m_Items.size()-1)
500 GUI<int>::SetSetting(this, "selected", (int)pList->m_Items.size()-1);
501 }
502
UpdateAutoScroll()503 void CList::UpdateAutoScroll()
504 {
505 int selected;
506 bool scrollbar;
507 float scroll;
508 GUI<int>::GetSetting(this, "selected", selected);
509 GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
510
511 // No scrollbar, no scrolling (at least it's not made to work properly).
512 if (!scrollbar || selected < 0 || (std::size_t) selected >= m_ItemsYPositions.size())
513 return;
514
515 scroll = GetScrollBar(0).GetPos();
516
517 // Check upper boundary
518 if (m_ItemsYPositions[selected] < scroll)
519 {
520 GetScrollBar(0).SetPos(m_ItemsYPositions[selected]);
521 return; // this means, if it wants to align both up and down at the same time
522 // this will have precedence.
523 }
524
525 // Check lower boundary
526 CRect rect = GetListRect();
527 if (m_ItemsYPositions[selected+1]-rect.GetHeight() > scroll)
528 GetScrollBar(0).SetPos(m_ItemsYPositions[selected+1]-rect.GetHeight());
529 }
530
GetHoveredItem()531 int CList::GetHoveredItem()
532 {
533 bool scrollbar;
534 CGUIList* pList;
535 GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
536 GUI<CGUIList>::GetSettingPointer(this, "list", pList);
537 float scroll = 0.f;
538 if (scrollbar)
539 scroll = GetScrollBar(0).GetPos();
540
541 CRect rect = GetListRect();
542 CPos mouse = GetMousePos();
543 mouse.y += scroll;
544
545 // Mouse is over scrollbar
546 if (scrollbar && GetScrollBar(0).IsVisible() &&
547 mouse.x >= GetScrollBar(0).GetOuterRect().left &&
548 mouse.x <= GetScrollBar(0).GetOuterRect().right)
549 return -1;
550
551 for (size_t i = 0; i < pList->m_Items.size(); ++i)
552 if (mouse.y >= rect.top + m_ItemsYPositions[i] &&
553 mouse.y < rect.top + m_ItemsYPositions[i + 1])
554 return i;
555
556 return -1;
557 }
558