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/COList.cpp

Last change on this file was 28135, checked in by Stan, 6 months ago

Finish implementing property "textcolor_selected" for list GUI objects
Fixes #6920
Patch by: @Vantha
Differential Revision: https://code.wildfiregames.com/D5269

  • Property svn:eol-style set to native
File size: 13.2 KB
RevLine 
[28120]1/* Copyright (C) 2024 Wildfire Games.
[27965]2 * This file is part of 0 A.D.
[15300]3 *
[27965]4 * 0 A.D. is free software: you can redistribute it and/or modify
[15300]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,
[15300]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/>.
[15300]16 */
[16931]17
[14098]18#include "precompiled.h"
[16931]19
[14098]20#include "COList.h"
[16931]21
[22931]22#include "gui/CGUI.h"
[22941]23#include "gui/IGUIScrollBar.h"
[23028]24#include "gui/SettingTypes/CGUIColor.h"
25#include "gui/SettingTypes/CGUIList.h"
[14953]26#include "i18n/L10n.h"
[14098]27#include "ps/CLogger.h"
28
[22066]29const float SORT_SPRITE_DIM = 16.0f;
[25152]30const CVector2D COLUMN_SHIFT = CVector2D(0, 4);
[22066]31
[23403]32const CStr COList::EventNameSelectionColumnChange = "SelectionColumnChange";
33
[22741]34COList::COList(CGUI& pGUI)
[23005]35 : CList(pGUI),
[25392]36 m_SpriteHeading(this, "sprite_heading"),
37 m_Sortable(this, "sortable"), // The actual sorting is done in JS for more versatility
38 m_SelectedColumn(this, "selected_column"),
39 m_SelectedColumnOrder(this, "selected_column_order"),
40 m_SpriteAsc(this, "sprite_asc"), // Show the order of sorting
41 m_SpriteDesc(this, "sprite_desc"),
42 m_SpriteNotSorted(this, "sprite_not_sorted")
[14098]43{
44}
45
46void COList::SetupText()
47{
[25392]48 m_ItemsYPositions.resize(m_List->m_Items.size() + 1);
[14098]49
50 // Delete all generated texts. Some could probably be saved,
51 // but this is easier, and this function will never be called
52 // continuously, or even often, so it'll probably be okay.
53 m_GeneratedTexts.clear();
54
[22066]55 m_TotalAvailableColumnWidth = GetListRect().GetWidth();
[14098]56 // remove scrollbar if applicable
[23005]57 if (m_ScrollBar && GetScrollBar(0).GetStyle())
[22066]58 m_TotalAvailableColumnWidth -= GetScrollBar(0).GetStyle()->m_Width;
[14098]59
[22066]60 m_HeadingHeight = SORT_SPRITE_DIM; // At least the size of the sorting sprite
61
62 for (const COListColumn& column : m_Columns)
[14098]63 {
[22066]64 float width = column.m_Width;
65 if (column.m_Width > 0 && column.m_Width < 1)
66 width *= m_TotalAvailableColumnWidth;
67
[14098]68 CGUIString gui_string;
[18652]69 gui_string.SetValue(column.m_Heading);
[22679]70
[23009]71 const CGUIText& text = AddText(gui_string, m_Font, width, m_BufferZone);
[25152]72 m_HeadingHeight = std::max(m_HeadingHeight, text.GetSize().Height + COLUMN_SHIFT.Y);
[14098]73 }
74
75 // Generate texts
76 float buffered_y = 0.f;
77
[25392]78 for (size_t i = 0; i < m_List->m_Items.size(); ++i)
[14098]79 {
80 m_ItemsYPositions[i] = buffered_y;
[19306]81 float shift = 0.0f;
[22066]82 for (const COListColumn& column : m_Columns)
[14098]83 {
[22066]84 float width = column.m_Width;
85 if (column.m_Width > 0 && column.m_Width < 1)
86 width *= m_TotalAvailableColumnWidth;
87
[22679]88 CGUIText* text;
[25392]89 if (!column.m_List->m_Items[i].GetOriginalString().empty())
90 text = &AddText(column.m_List->m_Items[i], m_Font, width, m_BufferZone);
[19306]91 else
92 {
93 // Minimum height of a space character of the current font size
94 CGUIString align_string;
95 align_string.SetValue(L" ");
[23009]96 text = &AddText(align_string, m_Font, width, m_BufferZone);
[19306]97 }
[25143]98 shift = std::max(shift, text->GetSize().Height);
[14098]99 }
[19306]100 buffered_y += shift;
[14098]101 }
102
[25392]103 m_ItemsYPositions[m_List->m_Items.size()] = buffered_y;
[14098]104
[23005]105 if (m_ScrollBar)
[14098]106 {
[18652]107 CRect rect = GetListRect();
[16931]108 GetScrollBar(0).SetScrollRange(m_ItemsYPositions.back());
[18652]109 GetScrollBar(0).SetScrollSpace(rect.GetHeight());
[14098]110
[16931]111 GetScrollBar(0).SetX(rect.right);
112 GetScrollBar(0).SetY(rect.top);
113 GetScrollBar(0).SetZ(GetBufferedZ());
114 GetScrollBar(0).SetLength(rect.bottom - rect.top);
[14098]115 }
116}
117
118CRect COList::GetListRect() const
119{
[22066]120 return m_CachedActualSize + CRect(0, m_HeadingHeight, 0, 0);
[14098]121}
122
[16931]123void COList::HandleMessage(SGUIMessage& Message)
[14098]124{
125 CList::HandleMessage(Message);
[16781]126
127 switch (Message.type)
128 {
[27384]129
130 case GUIM_SETTINGS_UPDATED:
131 {
132 if (Message.value.find("heading_") == 0)
133 SetupText();
134 break;
135 }
136
[16781]137 // If somebody clicks on the column heading
138 case GUIM_MOUSE_PRESS_LEFT:
139 {
[23005]140 if (!m_Sortable)
[16781]141 return;
142
[25152]143 const CVector2D& mouse = m_pGUI.GetMousePos();
[16781]144 if (!m_CachedActualSize.PointInside(mouse))
145 return;
[16931]146
[16781]147 float xpos = 0;
[22066]148 for (const COListColumn& column : m_Columns)
[16781]149 {
[23005]150 if (column.m_Hidden)
[19353]151 continue;
152
[18652]153 float width = column.m_Width;
[16781]154 // Check if it's a decimal value, and if so, assume relative positioning.
[18652]155 if (column.m_Width < 1 && column.m_Width > 0)
156 width *= m_TotalAvailableColumnWidth;
[25152]157 CVector2D leftTopCorner = m_CachedActualSize.TopLeft() + CVector2D(xpos, 0);
158 if (mouse.X >= leftTopCorner.X &&
159 mouse.X < leftTopCorner.X + width &&
160 mouse.Y < leftTopCorner.Y + m_HeadingHeight)
[16781]161 {
[25392]162 if (column.m_Id != static_cast<CStr>(m_SelectedColumn))
[16781]163 {
[27398]164 m_SelectedColumnOrder.Set(column.m_SortOrder, true);
[23005]165 CStr selected_column = column.m_Id;
[25392]166 m_SelectedColumn.Set(selected_column, true);
[16781]167 }
168 else
[25392]169 m_SelectedColumnOrder.Set(-m_SelectedColumnOrder, true);
[22765]170
[23403]171 ScriptEvent(EventNameSelectionColumnChange);
[23005]172 PlaySound(m_SoundSelected);
[16781]173 return;
174 }
175 xpos += width;
176 }
177 return;
178 }
179 default:
180 return;
181 }
[14098]182}
183
[25378]184bool COList::HandleAdditionalChildren(const XMBData& xmb, const XMBElement& child)
[14098]185{
[25378]186 #define ELMT(x) int elmt_##x = xmb.GetElementID(#x)
187 #define ATTR(x) int attr_##x = xmb.GetAttributeID(#x)
[14953]188 ELMT(item);
[18652]189 ELMT(column);
[14953]190 ELMT(translatableAttribute);
191 ATTR(id);
[15078]192 ATTR(context);
[14098]193
194 if (child.GetNodeName() == elmt_item)
195 {
[24306]196 CGUIString vlist;
197 vlist.SetValue(child.GetText().FromUTF8());
198 AddItem(vlist, vlist);
[14098]199 return true;
200 }
[18652]201 else if (child.GetNodeName() == elmt_column)
[14098]202 {
[25392]203 CStr id;
204 XERO_ITER_ATTR(child, attr)
205 {
206 if (attr.Name == attr_id)
207 id = attr.Value;
208 }
[14098]209
[25392]210 COListColumn column(this, id);
211
[16704]212 for (XMBAttribute attr : child.GetAttributes())
[14098]213 {
[25378]214 std::string_view attr_name(xmb.GetAttributeStringView(attr.Name));
[16931]215 CStr attr_value(attr.Value);
[14098]216
[28120]217 if (attr_name == "textcolor")
[14098]218 {
[22801]219 if (!CGUI::ParseString<CGUIColor>(&m_pGUI, attr_value.FromUTF8(), column.m_TextColor))
[25375]220 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.data(), attr_value.c_str());
[14098]221 }
[28135]222 else if (attr_name == "textcolor_selected")
223 {
224 if (!CGUI::ParseString<CGUIColor>(&m_pGUI, attr_value.FromUTF8(), column.m_TextColorSelected))
225 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.data(), attr_value.c_str());
226 }
[19353]227 else if (attr_name == "hidden")
228 {
[23005]229 bool hidden = false;
[22801]230 if (!CGUI::ParseString<bool>(&m_pGUI, attr_value.FromUTF8(), hidden))
[25375]231 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.data(), attr_value.c_str());
[23005]232 else
[25392]233 column.m_Hidden.Set(hidden, false);
[19353]234 }
[14098]235 else if (attr_name == "width")
236 {
237 float width;
[22801]238 if (!CGUI::ParseString<float>(&m_pGUI, attr_value.FromUTF8(), width))
[25375]239 LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.data(), attr_value.c_str());
[14098]240 else
241 {
242 // Check if it's a relative value, and save as decimal if so.
243 if (attr_value.find("%") != std::string::npos)
244 width = width / 100.f;
[18652]245 column.m_Width = width;
[14098]246 }
247 }
248 else if (attr_name == "heading")
249 {
[27384]250 column.m_Heading.Set(attr_value.FromUTF8(), false);
[14098]251 }
[27398]252 else if (attr_name == "sort_order")
253 {
254 column.m_SortOrder.Set(attr_value == "desc" ? -1 : 1, false);
255 }
[14098]256 }
257
[16704]258 for (XMBElement grandchild : child.GetChildNodes())
[14953]259 {
[16931]260 if (grandchild.GetNodeName() != elmt_translatableAttribute)
261 continue;
262
263 CStr attributeName(grandchild.GetAttributes().GetNamedItem(attr_id));
[18652]264 // only the heading is translatable for list column
[16931]265 if (attributeName.empty() || attributeName != "heading")
[14953]266 {
[18652]267 LOGERROR("GUI: translatable attribute in olist column that isn't a heading. (object: %s)", this->GetPresentableName().c_str());
[16931]268 continue;
[14953]269 }
[16931]270
271 CStr value(grandchild.GetText());
272 if (value.empty())
273 continue;
274
275 CStr context(grandchild.GetAttributes().GetNamedItem(attr_context)); // Read the context if any.
276 if (!context.empty())
277 {
278 CStr translatedValue(g_L10n.TranslateWithContext(context, value));
[27384]279 column.m_Heading.Set(translatedValue.FromUTF8(), false);
[16931]280 }
281 else
282 {
283 CStr translatedValue(g_L10n.Translate(value));
[27384]284 column.m_Heading.Set(translatedValue.FromUTF8(), false);
[16931]285 }
[14953]286 }
287
[22638]288 m_Columns.emplace_back(std::move(column));
[14098]289 return true;
290 }
[22638]291
292 return false;
[14098]293}
294
[23005]295void COList::AdditionalChildrenHandled()
[14098]296{
[23005]297 SetupText();
298}
299
[25591]300void COList::DrawList(CCanvas2D& canvas, const int& selected, const CGUISpriteInstance& sprite, const CGUISpriteInstance& spriteOverlay,
[25587]301 const CGUISpriteInstance& spriteSelectArea, const CGUISpriteInstance& spriteSelectAreaOverlay, const CGUIColor& textColor)
[23005]302{
[16931]303 CRect rect = GetListRect();
[14098]304
[25591]305 m_pGUI.DrawSprite(sprite, canvas, rect);
[22693]306
[16931]307 float scroll = 0.f;
[23005]308 if (m_ScrollBar)
[16931]309 scroll = GetScrollBar(0).GetPos();
[14098]310
[25587]311 bool drawSelected = false;
312 CRect rectSel;
[16976]313 // Draw item selection
[16931]314 if (selected != -1)
315 {
316 ENSURE(selected >= 0 && selected+1 < (int)m_ItemsYPositions.size());
[14098]317
[16931]318 // Get rectangle of selection:
[25587]319 rectSel = CRect(
320 rect.left, rect.top + m_ItemsYPositions[selected] - scroll,
321 rect.right, rect.top + m_ItemsYPositions[selected+1] - scroll);
[14098]322
[25587]323 if (rectSel.top <= rect.bottom &&
324 rectSel.bottom >= rect.top)
[16931]325 {
[25587]326 if (rectSel.bottom > rect.bottom)
327 rectSel.bottom = rect.bottom;
328 if (rectSel.top < rect.top)
329 rectSel.top = rect.top;
[14098]330
[23005]331 if (m_ScrollBar)
[16931]332 {
333 // Remove any overlapping area of the scrollbar.
[25587]334 if (rectSel.right > GetScrollBar(0).GetOuterRect().left &&
335 rectSel.right <= GetScrollBar(0).GetOuterRect().right)
336 rectSel.right = GetScrollBar(0).GetOuterRect().left;
[14098]337
[25587]338 if (rectSel.left >= GetScrollBar(0).GetOuterRect().left &&
339 rectSel.left < GetScrollBar(0).GetOuterRect().right)
340 rectSel.left = GetScrollBar(0).GetOuterRect().right;
[14098]341 }
[16931]342
[16976]343 // Draw item selection
[25591]344 m_pGUI.DrawSprite(spriteSelectArea, canvas, rectSel);
[25587]345 drawSelected = true;
[14098]346 }
[16931]347 }
[14098]348
[16976]349 // Draw line above column header
[16931]350 CRect rect_head(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right,
[22066]351 m_CachedActualSize.top + m_HeadingHeight);
[25591]352 m_pGUI.DrawSprite(m_SpriteHeading, canvas, rect_head);
[14098]353
[18694]354 // Draw column headers
[16931]355 float xpos = 0;
[23005]356 size_t col = 0;
357 for (const COListColumn& column : m_Columns)
[16931]358 {
[23005]359 if (column.m_Hidden)
360 {
361 ++col;
[19353]362 continue;
[23005]363 }
[19353]364
[16931]365 // Check if it's a decimal value, and if so, assume relative positioning.
[23005]366 float width = column.m_Width;
367 if (column.m_Width < 1 && column.m_Width > 0)
[18652]368 width *= m_TotalAvailableColumnWidth;
[16781]369
[25152]370 CVector2D leftTopCorner = m_CachedActualSize.TopLeft() + CVector2D(xpos, 0);
[16976]371
372 // Draw sort arrows in colum header
[23005]373 if (m_Sortable)
[18694]374 {
[24268]375 const CGUISpriteInstance* pSprite;
[25392]376 if (*m_SelectedColumn == column.m_Id)
[18694]377 {
[23005]378 if (m_SelectedColumnOrder == 0)
[18694]379 LOGERROR("selected_column_order must not be 0");
[14098]380
[23005]381 if (m_SelectedColumnOrder != -1)
[25392]382 pSprite = &*m_SpriteAsc;
[18694]383 else
[25392]384 pSprite = &*m_SpriteDesc;
[18694]385 }
386 else
[25392]387 pSprite = &*m_SpriteNotSorted;
[18694]388
[25591]389 m_pGUI.DrawSprite(*pSprite, canvas, CRect(leftTopCorner + CVector2D(width - SORT_SPRITE_DIM, 0), leftTopCorner + CVector2D(width, SORT_SPRITE_DIM)));
[18694]390 }
391
[16976]392 // Draw column header text
[25591]393 DrawText(canvas, col, textColor, leftTopCorner + COLUMN_SHIFT, rect_head);
[16931]394 xpos += width;
[23005]395 ++col;
[16931]396 }
[14098]397
[16976]398 // Draw list items for each column
[18652]399 const size_t objectsCount = m_Columns.size();
[25392]400 for (size_t i = 0; i < m_List->m_Items.size(); ++i)
[16931]401 {
402 if (m_ItemsYPositions[i+1] - scroll < 0 ||
403 m_ItemsYPositions[i] - scroll > rect.GetHeight())
404 continue;
[16895]405
[16931]406 const float rowHeight = m_ItemsYPositions[i+1] - m_ItemsYPositions[i];
[14098]407
[16931]408 // Clipping area (we'll have to substract the scrollbar)
409 CRect cliparea = GetListRect();
[14098]410
[23005]411 if (m_ScrollBar)
[16931]412 {
413 if (cliparea.right > GetScrollBar(0).GetOuterRect().left &&
414 cliparea.right <= GetScrollBar(0).GetOuterRect().right)
415 cliparea.right = GetScrollBar(0).GetOuterRect().left;
[14098]416
[16931]417 if (cliparea.left >= GetScrollBar(0).GetOuterRect().left &&
418 cliparea.left < GetScrollBar(0).GetOuterRect().right)
419 cliparea.left = GetScrollBar(0).GetOuterRect().right;
420 }
[16895]421
[16976]422 // Draw all items for that column
[16931]423 xpos = 0;
[24268]424 for (size_t colIdx = 0; colIdx < m_Columns.size(); ++colIdx)
[16931]425 {
[24276]426 const COListColumn& column = m_Columns[colIdx];
[23005]427 if (column.m_Hidden)
[19353]428 continue;
429
[16931]430 // Determine text position and width
[25152]431 const CVector2D textPos = rect.TopLeft() + CVector2D(xpos, -scroll + m_ItemsYPositions[i]);
[16895]432
[23005]433 float width = column.m_Width;
[16931]434 // Check if it's a decimal value, and if so, assume relative positioning.
[23005]435 if (column.m_Width < 1 && column.m_Width > 0)
[18652]436 width *= m_TotalAvailableColumnWidth;
[16895]437
[16931]438 // Clip text to the column (to prevent drawing text into the neighboring column)
439 CRect cliparea2 = cliparea;
[25152]440 cliparea2.right = std::min(cliparea2.right, textPos.X + width);
441 cliparea2.bottom = std::min(cliparea2.bottom, textPos.Y + rowHeight);
[16931]442
[28135]443 const CGUIColor& finalTextColor = (drawSelected && static_cast<size_t>(selected) == i && column.m_TextColorSelected) ? column.m_TextColorSelected : column.m_TextColor;
444
[16976]445 // Draw list item
[28135]446 DrawText(canvas, objectsCount * (i +/*Heading*/1) + colIdx, finalTextColor, textPos, cliparea2);
[16931]447 xpos += width;
[14098]448 }
449 }
[25587]450
451 // Draw scrollbars on top of the content
452 if (m_ScrollBar)
[25591]453 IGUIScrollBarOwner::Draw(canvas);
[25587]454
455 // Draw the overlays last
[25591]456 m_pGUI.DrawSprite(spriteOverlay, canvas, rect);
[25587]457 if (drawSelected)
[25591]458 m_pGUI.DrawSprite(spriteSelectAreaOverlay, canvas, rectSel);
[14098]459}
Note: See TracBrowser for help on using the repository browser.