This Trac instance is not used for development anymore!

We migrated our development workflow to git and Gitea.
To test the future redirection, replace trac by ariadne in the page URL.

source: ps/trunk/source/gui/ObjectTypes/CDropDown.cpp

Last change on this file was 27965, checked in by Vladislav Belov, 13 months ago

Revert non-ASCII characters from source and configuration files introduced in rP27786.

Fixes #6846

Differential Revision: https://code.wildfiregames.com/D5185

  • Property svn:eol-style set to native
File size: 14.1 KB
RevLine 
[27763]1/* Copyright (C) 2023 Wildfire Games.
[27965]2 * This file is part of 0 A.D.
[6830]3 *
[27965]4 * 0 A.D. is free software: you can redistribute it and/or modify
[6830]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 *
[27965]9 * 0 A.D. is distributed in the hope that it will be useful,
[6830]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
[27965]15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
[6830]16 */
17
[3802]18#include "precompiled.h"
[13521]19
[3802]20#include "CDropDown.h"
21
[22931]22#include "gui/CGUI.h"
[22976]23#include "gui/IGUIScrollBar.h"
[23028]24#include "gui/SettingTypes/CGUIColor.h"
25#include "gui/SettingTypes/CGUIList.h"
[13521]26#include "lib/external_libraries/libsdl.h"
[13556]27#include "lib/timer.h"
[22863]28#include "ps/Profile.h"
[3802]29
[22741]30CDropDown::CDropDown(CGUI& pGUI)
[23005]31 : CList(pGUI),
32 m_Open(),
33 m_HideScrollBar(),
34 m_ElementHighlight(-1),
[25392]35 m_ButtonWidth(this, "button_width"),
36 m_DropDownSize(this, "dropdown_size"),
37 m_DropDownBuffer(this, "dropdown_buffer"),
38 m_MinimumVisibleItems(this, "minimum_visible_items"),
39 m_SoundClosed(this, "sound_closed"),
40 m_SoundEnter(this, "sound_enter"),
41 m_SoundLeave(this, "sound_leave"),
42 m_SoundOpened(this, "sound_opened"),
43 // Setting "sprite" is registered by CList and used as the background
44 m_SpriteDisabled(this, "sprite_disabled"),
[25587]45 m_SpriteOverlayDisabled(this, "sprite_overlay_disabled"),
[25392]46 m_SpriteList(this, "sprite_list"), // Background of the drop down list
[25587]47 m_SpriteListOverlay(this, "sprite_list_overlay"), // Overlay above the drop down list
[25392]48 m_Sprite2(this, "sprite2"), // Button that sits to the right
49 m_Sprite2Over(this, "sprite2_over"),
50 m_Sprite2Pressed(this, "sprite2_pressed"),
51 m_Sprite2Disabled(this, "sprite2_disabled"),
52 m_TextColorDisabled(this, "textcolor_disabled")
53 // Add these in CList! And implement TODO
[27763]54 //m_TextColorOver("textcolor_over");
55 //m_TextColorPressed("textcolor_pressed");
[3802]56{
[25392]57 m_ScrollBar.Set(true, true);
[3802]58}
59
60CDropDown::~CDropDown()
61{
62}
63
64void CDropDown::SetupText()
65{
[7649]66 SetupListRect();
[3802]67 CList::SetupText();
68}
69
[19845]70void CDropDown::UpdateCachedSize()
71{
72 CList::UpdateCachedSize();
73 SetupText();
74}
75
[16931]76void CDropDown::HandleMessage(SGUIMessage& Message)
[3802]77{
[23020]78 // CList::HandleMessage(Message); placed after the switch!
[3802]79
80 switch (Message.type)
81 {
82 case GUIM_SETTINGS_UPDATED:
[7649]83 {
[3802]84 // Update cached list rect
[7649]85 if (Message.value == "size" ||
86 Message.value == "absolute" ||
87 Message.value == "dropdown_size" ||
88 Message.value == "dropdown_buffer" ||
[21379]89 Message.value == "minimum_visible_items" ||
[7649]90 Message.value == "scrollbar_style" ||
91 Message.value == "button_width")
[3802]92 {
93 SetupListRect();
[16931]94 }
[3802]95
96 break;
[7649]97 }
[3802]98
[7649]99 case GUIM_MOUSE_MOTION:
100 {
[16931]101 if (!m_Open)
102 break;
103
[25152]104 CVector2D mouse = m_pGUI.GetMousePos();
[16931]105
106 if (!GetListRect().PointInside(mouse))
107 break;
108
[23005]109 const float scroll = m_ScrollBar ? GetScrollBar(0).GetPos() : 0.f;
[16931]110
111 CRect rect = GetListRect();
[25152]112 mouse.Y += scroll;
[16931]113 int set = -1;
[25392]114 for (int i = 0; i < static_cast<int>(m_List->m_Items.size()); ++i)
[3802]115 {
[25152]116 if (mouse.Y >= rect.top + m_ItemsYPositions[i] &&
117 mouse.Y < rect.top + m_ItemsYPositions[i+1] &&
[16931]118 // mouse is not over scroll-bar
[17149]119 (m_HideScrollBar ||
[25152]120 mouse.X < GetScrollBar(0).GetOuterRect().left ||
121 mouse.X > GetScrollBar(0).GetOuterRect().right))
[3802]122 {
[16931]123 set = i;
[3802]124 }
125 }
126
[16931]127 if (set != -1)
128 {
129 m_ElementHighlight = set;
130 //UpdateAutoScroll();
131 }
132
[7649]133 break;
134 }
[3802]135
[13521]136 case GUIM_MOUSE_ENTER:
137 {
[23005]138 if (m_Enabled)
139 PlaySound(m_SoundEnter);
[13521]140 break;
141 }
142
[7649]143 case GUIM_MOUSE_LEAVE:
144 {
[23005]145 m_ElementHighlight = m_Selected;
[3802]146
[23005]147 if (m_Enabled)
148 PlaySound(m_SoundLeave);
[7649]149 break;
150 }
[3802]151
[7649]152 // We can't inherent this routine from CList, because we need to include
153 // a mouse click to open the dropdown, also the coordinates are changed.
[3802]154 case GUIM_MOUSE_PRESS_LEFT:
155 {
[23005]156 if (!m_Enabled)
[13521]157 {
[23005]158 PlaySound(m_SoundDisabled);
[7649]159 break;
[13521]160 }
[7649]161
[3802]162 if (!m_Open)
163 {
[25392]164 if (m_List->m_Items.empty())
[13936]165 return;
166
[3802]167 m_Open = true;
[7649]168 GetScrollBar(0).SetZ(GetBufferedZ());
[23005]169 m_ElementHighlight = m_Selected;
[12319]170
171 // Start at the position of the selected item, if possible.
[25830]172 if (m_ItemsYPositions.empty() || m_ElementHighlight < 0 || static_cast<size_t>(m_ElementHighlight) >= m_ItemsYPositions.size())
173 GetScrollBar(0).SetPos(0);
174 else
175 GetScrollBar(0).SetPos(m_ItemsYPositions[m_ElementHighlight] - 60);
[13521]176
[23005]177 PlaySound(m_SoundOpened);
[3802]178 return; // overshadow
179 }
180 else
181 {
[25152]182 const CVector2D& mouse = m_pGUI.GetMousePos();
[3802]183
184 // If the regular area is pressed, then abort, and close.
185 if (m_CachedActualSize.PointInside(mouse))
186 {
187 m_Open = false;
[7649]188 GetScrollBar(0).SetZ(GetBufferedZ());
[23005]189 PlaySound(m_SoundClosed);
[3802]190 return; // overshadow
191 }
192
[17149]193 if (m_HideScrollBar ||
[25152]194 mouse.X < GetScrollBar(0).GetOuterRect().left ||
195 mouse.X > GetScrollBar(0).GetOuterRect().right ||
196 mouse.Y < GetListRect().top)
[3802]197 {
198 m_Open = false;
[7649]199 GetScrollBar(0).SetZ(GetBufferedZ());
[3802]200 }
201 }
[7649]202 break;
203 }
[3802]204
[14458]205 case GUIM_MOUSE_WHEEL_DOWN:
206 {
[14460]207 // Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar.
[23005]208 if (m_Open || !m_Enabled)
[14458]209 break;
210
[23005]211 m_ElementHighlight = m_Selected;
[22765]212
[14458]213 if (m_ElementHighlight + 1 >= (int)m_ItemsYPositions.size() - 1)
214 break;
215
[16931]216 ++m_ElementHighlight;
[25392]217 m_Selected.Set(m_ElementHighlight, true);
[14458]218 break;
219 }
220
221 case GUIM_MOUSE_WHEEL_UP:
222 {
223 // Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar.
[23005]224 if (m_Open || !m_Enabled)
[14458]225 break;
226
[23005]227 m_ElementHighlight = m_Selected;
[14458]228 if (m_ElementHighlight - 1 < 0)
229 break;
230
[22796]231 --m_ElementHighlight;
[25392]232 m_Selected.Set(m_ElementHighlight, true);
[14458]233 break;
234 }
235
[3802]236 case GUIM_LOST_FOCUS:
[13521]237 {
238 if (m_Open)
[23005]239 PlaySound(m_SoundClosed);
[22756]240
[7649]241 m_Open = false;
[3802]242 break;
[13521]243 }
[3802]244
245 case GUIM_LOAD:
246 SetupListRect();
247 break;
248
249 default:
250 break;
251 }
252
253 // Important that this is after, so that overshadowed implementations aren't processed
254 CList::HandleMessage(Message);
[12244]255
256 // As HandleMessage functions return void, we need to manually verify
257 // whether the child list's items were modified.
258 if (CList::GetModified())
259 SetupText();
[3802]260}
261
[24215]262InReaction CDropDown::ManuallyHandleKeys(const SDL_Event_* ev)
[3802]263{
[15909]264 InReaction result = IN_PASS;
[3802]265 bool update_highlight = false;
266
[15909]267 if (ev->ev.type == SDL_KEYDOWN)
[3802]268 {
[15909]269 int szChar = ev->ev.key.keysym.sym;
[16931]270
[15909]271 switch (szChar)
272 {
273 case '\r':
274 m_Open = false;
275 result = IN_HANDLED;
276 break;
[3802]277
[15909]278 case SDLK_HOME:
279 case SDLK_END:
280 case SDLK_UP:
281 case SDLK_DOWN:
282 case SDLK_PAGEUP:
283 case SDLK_PAGEDOWN:
284 if (!m_Open)
285 return IN_PASS;
286 // Set current selected item to highlighted, before
[24215]287 // then really processing these in CList::ManuallyHandleKeys()
[25392]288 m_Selected.Set(m_ElementHighlight, true);
[15909]289 update_highlight = true;
290 break;
[3802]291
[15909]292 default:
[24488]293 // If we have typed a character try to get the closest element to it.
[15909]294 // TODO: not too nice and doesn't deal with dashes.
295 if (m_Open && ((szChar >= SDLK_a && szChar <= SDLK_z) || szChar == SDLK_SPACE
296 || (szChar >= SDLK_0 && szChar <= SDLK_9)
[24488]297 || (szChar >= SDLK_KP_1 && szChar <= SDLK_KP_0)))
[15909]298 {
299 // arbitrary 1 second limit to add to string or start fresh.
300 // maximal amount of characters is 100, which imo is far more than enough.
301 if (timer_Time() - m_TimeOfLastInput > 1.0 || m_InputBuffer.length() >= 100)
302 m_InputBuffer = szChar;
303 else
304 m_InputBuffer += szChar;
[16931]305
[15909]306 m_TimeOfLastInput = timer_Time();
[16931]307
[15909]308 // let's look for the closest element
309 // basically it's alphabetic order and "as many letters as we can get".
310 int closest = -1;
311 int bestIndex = -1;
312 int difference = 1250;
[25392]313 for (int i = 0; i < static_cast<int>(m_List->m_Items.size()); ++i)
[13556]314 {
[15909]315 int indexOfDifference = 0;
316 int diff = 0;
[16931]317 for (size_t j = 0; j < m_InputBuffer.length(); ++j)
[15909]318 {
[25392]319 diff = std::abs(static_cast<int>(m_List->m_Items[i].GetRawString().LowerCase()[j]) - static_cast<int>(m_InputBuffer[j]));
[15909]320 if (diff == 0)
321 indexOfDifference = j+1;
322 else
323 break;
324 }
325 if (indexOfDifference > bestIndex || (indexOfDifference >= bestIndex && diff < difference))
326 {
327 bestIndex = indexOfDifference;
328 closest = i;
329 difference = diff;
330 }
[13556]331 }
[15909]332 // let's select the closest element. There should basically always be one.
333 if (closest != -1)
[13556]334 {
[25392]335 m_Selected.Set(closest, true);
[15909]336 update_highlight = true;
337 GetScrollBar(0).SetPos(m_ItemsYPositions[closest] - 60);
[13556]338 }
[15909]339 result = IN_HANDLED;
[13556]340 }
[15909]341 break;
[13556]342 }
[3802]343 }
344
[24215]345 if (CList::ManuallyHandleKeys(ev) == IN_HANDLED)
[15909]346 result = IN_HANDLED;
[3802]347
348 if (update_highlight)
[23005]349 m_ElementHighlight = m_Selected;
[3802]350
[15909]351 return result;
[3802]352}
353
354void CDropDown::SetupListRect()
355{
[25577]356 const CSize2D windowSize = m_pGUI.GetWindowSize();
[3802]357
[21379]358 if (m_ItemsYPositions.empty())
[3802]359 {
[23005]360 m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + m_DropDownBuffer,
361 m_CachedActualSize.right, m_CachedActualSize.bottom + m_DropDownBuffer + m_DropDownSize);
[21379]362 m_HideScrollBar = false;
363 }
364 // Too many items so use a scrollbar
[23005]365 else if (m_ItemsYPositions.back() > m_DropDownSize)
[21379]366 {
367 // Place items below if at least some items can be placed below
[25577]368 if (m_CachedActualSize.bottom + m_DropDownBuffer + m_DropDownSize <= windowSize.Height)
[23005]369 m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + m_DropDownBuffer,
370 m_CachedActualSize.right, m_CachedActualSize.bottom + m_DropDownBuffer + m_DropDownSize);
[25577]371 else if ((m_ItemsYPositions.size() > m_MinimumVisibleItems && windowSize.Height - m_CachedActualSize.bottom - m_DropDownBuffer >= m_ItemsYPositions[m_MinimumVisibleItems]) ||
372 m_CachedActualSize.top < windowSize.Height - m_CachedActualSize.bottom)
[23005]373 m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + m_DropDownBuffer,
[25577]374 m_CachedActualSize.right, windowSize.Height);
[21379]375 // Not enough space below, thus place items above
376 else
[23005]377 m_CachedListRect = CRect(m_CachedActualSize.left, std::max(0.f, m_CachedActualSize.top - m_DropDownBuffer - m_DropDownSize),
378 m_CachedActualSize.right, m_CachedActualSize.top - m_DropDownBuffer);
[3802]379
380 m_HideScrollBar = false;
381 }
382 else
383 {
[21379]384 // Enough space below, no scrollbar needed
[25577]385 if (m_CachedActualSize.bottom + m_DropDownBuffer + m_ItemsYPositions.back() <= windowSize.Height)
[21379]386 {
[23005]387 m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + m_DropDownBuffer,
388 m_CachedActualSize.right, m_CachedActualSize.bottom + m_DropDownBuffer + m_ItemsYPositions.back());
[21379]389 m_HideScrollBar = true;
390 }
391 // Enough space below for some items, but not all, so place items below and use a scrollbar
[25577]392 else if ((m_ItemsYPositions.size() > m_MinimumVisibleItems && windowSize.Height - m_CachedActualSize.bottom - m_DropDownBuffer >= m_ItemsYPositions[m_MinimumVisibleItems]) ||
393 m_CachedActualSize.top < windowSize.Height - m_CachedActualSize.bottom)
[21379]394 {
[23005]395 m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom + m_DropDownBuffer,
[25577]396 m_CachedActualSize.right, windowSize.Height);
[21379]397 m_HideScrollBar = false;
398 }
399 // Not enough space below, thus place items above. Hide the scrollbar accordingly
400 else
401 {
[23005]402 m_CachedListRect = CRect(m_CachedActualSize.left, std::max(0.f, m_CachedActualSize.top - m_DropDownBuffer - m_ItemsYPositions.back()),
403 m_CachedActualSize.right, m_CachedActualSize.top - m_DropDownBuffer);
404 m_HideScrollBar = m_CachedActualSize.top > m_ItemsYPositions.back() + m_DropDownBuffer;
[21379]405 }
[3802]406 }
407}
408
409CRect CDropDown::GetListRect() const
410{
411 return m_CachedListRect;
412}
413
[22757]414bool CDropDown::IsMouseOver() const
[3802]415{
416 if (m_Open)
417 {
[21379]418 CRect rect(m_CachedActualSize.left, std::min(m_CachedActualSize.top, GetListRect().top),
419 m_CachedActualSize.right, std::max(m_CachedActualSize.bottom, GetListRect().bottom));
[22741]420 return rect.PointInside(m_pGUI.GetMousePos());
[3802]421 }
422 else
[22741]423 return m_CachedActualSize.PointInside(m_pGUI.GetMousePos());
[3802]424}
425
[25591]426void CDropDown::Draw(CCanvas2D& canvas)
[3802]427{
[23005]428 const CGUISpriteInstance& sprite = m_Enabled ? m_Sprite : m_SpriteDisabled;
[25587]429 const CGUISpriteInstance& spriteOverlay = m_Enabled ? m_SpriteOverlay : m_SpriteOverlayDisabled;
[22693]430
[25591]431 m_pGUI.DrawSprite(sprite, canvas, m_CachedActualSize);
[3802]432
[23005]433 if (m_ButtonWidth > 0.f)
[3802]434 {
[23005]435 CRect rect(m_CachedActualSize.right - m_ButtonWidth, m_CachedActualSize.top,
[3802]436 m_CachedActualSize.right, m_CachedActualSize.bottom);
437
[23005]438 if (!m_Enabled)
[3802]439 {
[25591]440 m_pGUI.DrawSprite(*m_Sprite2Disabled ? m_Sprite2Disabled : m_Sprite2, canvas, rect);
[3802]441 }
[16931]442 else if (m_Open)
[3802]443 {
[25591]444 m_pGUI.DrawSprite(*m_Sprite2Pressed ? m_Sprite2Pressed : m_Sprite2, canvas, rect);
[3802]445 }
[16931]446 else if (m_MouseHovering)
[3802]447 {
[25591]448 m_pGUI.DrawSprite(*m_Sprite2Over ? m_Sprite2Over : m_Sprite2, canvas, rect);
[3802]449 }
[16931]450 else
[25591]451 m_pGUI.DrawSprite(m_Sprite2, canvas, rect);
[3802]452 }
453
[23005]454 if (m_Selected != -1) // TODO: Maybe check validity completely?
[3802]455 {
456 CRect cliparea(m_CachedActualSize.left, m_CachedActualSize.top,
[23005]457 m_CachedActualSize.right - m_ButtonWidth, m_CachedActualSize.bottom);
[3802]458
[25152]459 CVector2D pos(m_CachedActualSize.left, m_CachedActualSize.top);
[25591]460 DrawText(canvas, m_Selected, m_Enabled ? m_TextColorSelected : m_TextColorDisabled, pos, cliparea);
[3802]461 }
462
463 if (m_Open)
464 {
[25303]465 // Disable scrollbar during drawing without sending a setting-changed message
466 const bool old = m_ScrollBar;
467
[23005]468 // TODO: drawScrollbar as an argument of DrawList?
[3802]469 if (m_HideScrollBar)
[25392]470 m_ScrollBar.Set(false, false);
[3802]471
[25591]472 DrawList(canvas, m_ElementHighlight, m_SpriteList, m_SpriteListOverlay, m_SpriteSelectArea, m_SpriteSelectAreaOverlay, m_TextColor);
[16931]473
[3802]474 if (m_HideScrollBar)
[25392]475 m_ScrollBar.Set(old, false);
[3802]476 }
[25591]477 m_pGUI.DrawSprite(spriteOverlay, canvas, m_CachedActualSize);
[3802]478}
479
480// When a dropdown list is opened, it needs to be visible above all the other
481// controls on the page. The only way I can think of to do this is to increase
482// its z value when opened, so that it's probably on top.
483float CDropDown::GetBufferedZ() const
484{
485 float bz = CList::GetBufferedZ();
486 if (m_Open)
[6226]487 return std::min(bz + 500.f, 1000.f); // TODO - don't use magic number for max z value
[3802]488 else
489 return bz;
490}
Note: See TracBrowser for help on using the repository browser.