Changeset View
Changeset View
Standalone View
Standalone View
source/gui/CMultiList.cpp
/* Copyright (C) 2019 Wildfire Games. | |||||
* This file is part of 0 A.D. | |||||
* | |||||
* 0 A.D. is free software: you can redistribute it and/or modify | |||||
* it under the terms of the GNU General Public License as published by | |||||
* the Free Software Foundation, either version 2 of the License, or | |||||
* (at your option) any later version. | |||||
* | |||||
* 0 A.D. is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* You should have received a copy of the GNU General Public License | |||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#include "precompiled.h" | |||||
#include <algorithm> | |||||
#include "CMultiList.h" | |||||
#include "i18n/L10n.h" | |||||
#include "ps/CLogger.h" | |||||
#include "soundmanager/ISoundManager.h" | |||||
const float SORT_SPRITE_DIM = 16.0f; | |||||
CMultiList::CMultiList() : CList() | |||||
{ | |||||
AddSetting(GUIST_CGUISpriteInstance, "sprite_heading"); | |||||
AddSetting(GUIST_bool, "sortable"); // The actual sorting is done in JS for more versatility | |||||
AddSetting(GUIST_CStr, "selected_column"); | |||||
AddSetting(GUIST_int, "selected_column_order"); | |||||
AddSetting(GUIST_CGUISpriteInstance, "sprite_asc"); // Show the order of sorting | |||||
AddSetting(GUIST_CGUISpriteInstance, "sprite_desc"); | |||||
AddSetting(GUIST_CGUISpriteInstance, "sprite_not_sorted"); | |||||
} | |||||
void CMultiList::SetupText() | |||||
{ | |||||
if (!GetGUI()) | |||||
return; | |||||
CGUIList* pList; | |||||
GUI<CGUIList>::GetSettingPointer(this, "list", pList); | |||||
m_ItemsYPositions.resize(pList->m_Items.size() + 1); | |||||
// Delete all generated texts. Some could probably be saved, | |||||
// but this is easier, and this function will never be called | |||||
// continuously, or even often, so it'll probably be okay. | |||||
for (SGUIText* const& t : m_GeneratedTexts) | |||||
delete t; | |||||
m_GeneratedTexts.clear(); | |||||
CStrW font; | |||||
if (GUI<CStrW>::GetSetting(this, "font", font) != PSRETURN_OK || font.empty()) | |||||
// Use the default if none is specified | |||||
// TODO Gee: (2004-08-14) Don't define standard like this. Do it with the default style. | |||||
font = L"default"; | |||||
float buffer_zone = 0.f; | |||||
GUI<float>::GetSetting(this, "buffer_zone", buffer_zone); | |||||
for (CMultiListColumn& column : m_Columns) | |||||
{ | |||||
SGUIText* text = new SGUIText(); | |||||
CGUIString gui_string; | |||||
gui_string.SetValue(column.m_Heading); | |||||
*text = GetGUI()->GenerateText(gui_string, font, column.m_Width, buffer_zone, this); | |||||
AddText(text); | |||||
column.m_ChosenHeight = std::max(text->m_Size.cy, SORT_SPRITE_DIM); // the size of the sorting sprite, ofc | |||||
column.m_ChosenY = column.m_Rect.top; | |||||
} | |||||
// Rank from top to bottom. | |||||
std::sort( | |||||
m_Columns.begin(), | |||||
m_Columns.end(), | |||||
[] (const CMultiListColumn& a, const CMultiListColumn& b) -> bool { | |||||
return a.m_ChosenY < b.m_ChosenY; | |||||
} | |||||
); | |||||
SlideTillNoOverlaps( | |||||
[this] (size_t i) { return m_Columns[i].m_Rect.left; }, | |||||
[this] (size_t i) { return m_Columns[i].m_Rect.right; }, | |||||
[this] (size_t i) { return m_Columns[i].m_ChosenY; }, | |||||
[this] (size_t i) { return m_Columns[i].m_ChosenHeight; }, | |||||
[this] (size_t i, float shiftAmount) { m_Columns[i].m_ChosenY += shiftAmount; } | |||||
); | |||||
m_HeadingHeight = 0; | |||||
for (const CMultiListColumn& column : m_Columns) | |||||
{ | |||||
m_HeadingHeight = std::max(m_HeadingHeight, column.m_ChosenY + column.m_ChosenHeight - m_CachedActualSize.top); | |||||
} | |||||
// Generate texts | |||||
float bufferedY = 0.f; | |||||
m_RowColumnLocs.clear(); | |||||
for (size_t i = 0; i < pList->m_Items.size(); ++i) | |||||
{ | |||||
m_ItemsYPositions[i] = bufferedY; | |||||
size_t columnIndex = 0; | |||||
for (const CMultiListColumn& column : m_Columns) | |||||
{ | |||||
CRowColumnLocation rcLoc; | |||||
CGUIList* pList_c; | |||||
GUI<CGUIList>::GetSettingPointer(this, "list_" + column.m_Id, pList_c); | |||||
SGUIText* text = new SGUIText(); | |||||
if (!pList_c->m_Items[i].GetOriginalString().empty()) | |||||
*text = GetGUI()->GenerateText(pList_c->m_Items[i], font, column.m_Width, buffer_zone, this); | |||||
else | |||||
{ | |||||
// Minimum height of a space character of the current font size | |||||
CGUIString align_string; | |||||
align_string.SetValue(L" "); | |||||
*text = GetGUI()->GenerateText(align_string, font, column.m_Width, buffer_zone, this); | |||||
} | |||||
rcLoc.m_ChosenHeight = text->m_Size.cy; | |||||
rcLoc.m_ChosenY = column.m_ChosenY - m_CachedActualSize.top; | |||||
rcLoc.m_TextIndex = i * m_Columns.size() + columnIndex; | |||||
rcLoc.m_ColumnIndex = columnIndex; | |||||
m_RowColumnLocs.push_back(rcLoc); | |||||
++columnIndex; | |||||
AddText(text); | |||||
} | |||||
// Rank from top to bottom. | |||||
std::sort( | |||||
m_RowColumnLocs.end() - m_Columns.size(), | |||||
m_RowColumnLocs.end(), | |||||
[] (const CRowColumnLocation& a, const CRowColumnLocation& b) -> bool { | |||||
return a.m_ChosenY < b.m_ChosenY; | |||||
} | |||||
); | |||||
SlideTillNoOverlaps( | |||||
[this] (size_t i) | |||||
{ | |||||
return m_Columns[m_RowColumnLocs[m_RowColumnLocs.size() - m_Columns.size() + i].m_ColumnIndex].m_Rect.left; | |||||
}, | |||||
[this] (size_t i) | |||||
{ | |||||
return m_Columns[m_RowColumnLocs[m_RowColumnLocs.size() - m_Columns.size() + i].m_ColumnIndex].m_Rect.right; | |||||
}, | |||||
[this] (size_t i) { return m_RowColumnLocs[m_RowColumnLocs.size() - m_Columns.size() + i].m_ChosenY; }, | |||||
[this] (size_t i) { return m_RowColumnLocs[m_RowColumnLocs.size() - m_Columns.size() + i].m_ChosenHeight; }, | |||||
[this] (size_t i, float shiftAmount) | |||||
{ | |||||
m_RowColumnLocs[m_RowColumnLocs.size() - m_Columns.size() + i].m_ChosenY += shiftAmount; | |||||
} | |||||
); | |||||
// Rank by textIndex | |||||
std::sort( | |||||
m_RowColumnLocs.end() - m_Columns.size(), | |||||
m_RowColumnLocs.end(), | |||||
[] (const CRowColumnLocation& a, const CRowColumnLocation& b) -> bool { | |||||
return a.m_TextIndex < b.m_TextIndex; | |||||
} | |||||
); | |||||
float maxHeight = 0; | |||||
for (size_t j = m_RowColumnLocs.size() - m_Columns.size(); j < m_RowColumnLocs.size(); ++j) | |||||
{ | |||||
maxHeight = std::max(maxHeight, m_RowColumnLocs[j].m_ChosenY + m_RowColumnLocs[j].m_ChosenHeight); | |||||
} | |||||
bufferedY += maxHeight; | |||||
} | |||||
m_ItemsYPositions[pList->m_Items.size()] = bufferedY; | |||||
bool scrollbar; | |||||
GUI<bool>::GetSetting(this, "scrollbar", scrollbar); | |||||
if (scrollbar) | |||||
{ | |||||
CRect rect = GetListRect(); | |||||
GetScrollBar(0).SetScrollRange(m_ItemsYPositions.back()); | |||||
GetScrollBar(0).SetScrollSpace(rect.GetHeight()); | |||||
GetScrollBar(0).SetX(rect.right); | |||||
GetScrollBar(0).SetY(rect.top); | |||||
GetScrollBar(0).SetZ(GetBufferedZ()); | |||||
GetScrollBar(0).SetLength(rect.bottom - rect.top); | |||||
} | |||||
} | |||||
// A column directly shadows another if it's x-range overlaps with | |||||
// `xa..xe` and its lower down or equal to the other on it's Y axis. | |||||
// | |||||
// This function adds j and all columns under column j's shade to shadows. | |||||
// | |||||
// For example, in this case, c is directly under a's shade, and d and e | |||||
// are indirectly under a's shade via c: | |||||
// | a | | b | | |||||
// | c | | |||||
// | d | | e | | |||||
void CMultiList::GetShadows( | |||||
std::function<float(size_t)> xs_fn, | |||||
std::function<float(size_t)> xe_fn, | |||||
std::function<float(size_t)> ys_fn, | |||||
std::vector<size_t>& shadows, | |||||
size_t j, | |||||
float h, | |||||
float xs, | |||||
float xe | |||||
) | |||||
{ | |||||
if (std::find(shadows.begin(), shadows.end(), j) != shadows.end()) { | |||||
return; | |||||
} | |||||
shadows.push_back(j); | |||||
for (size_t k = 0; k < m_Columns.size(); ++k) | |||||
{ | |||||
if (ys_fn(k) >= h) | |||||
{ | |||||
if (LinesOverlap( | |||||
xs, xe, | |||||
xs_fn(k), xe_fn(k) | |||||
)) | |||||
{ | |||||
GetShadows( | |||||
xs_fn, | |||||
xe_fn, | |||||
ys_fn, | |||||
shadows, | |||||
k, | |||||
ys_fn(k), | |||||
xs_fn(k), | |||||
xe_fn(k) | |||||
); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
// If a square and a square earler on the list overlap, the one later on the | |||||
// list will be slid down and all its shadows. | |||||
// | |||||
// xs_fn: Gets the x start for a column | |||||
// xe_fn: Gets the x end for a column | |||||
// ys_fn: Gets the y start for a column | |||||
// yh_fn: Gets the y height for a column | |||||
// yshift_fn: Shifts the y start for a column | |||||
void CMultiList::SlideTillNoOverlaps( | |||||
std::function<float(size_t)> xs_fn, | |||||
std::function<float(size_t)> xe_fn, | |||||
std::function<float(size_t)> ys_fn, | |||||
std::function<float(size_t)> yh_fn, | |||||
std::function<void(size_t, float)> yshift_fn | |||||
) | |||||
{ | |||||
for (size_t i = 0; i < m_Columns.size(); ++i) | |||||
{ | |||||
for (size_t j = 0; j < i; ++j) | |||||
{ | |||||
if (LinesOverlap( | |||||
xs_fn(i), xe_fn(i), | |||||
xs_fn(j), xe_fn(j) | |||||
)) | |||||
{ | |||||
if (LinesOverlap( | |||||
ys_fn(i), ys_fn(i) + yh_fn(i), | |||||
ys_fn(j), ys_fn(j) + yh_fn(j) | |||||
)) | |||||
{ | |||||
std::vector<size_t> shadows; | |||||
GetShadows( | |||||
xs_fn, | |||||
xe_fn, | |||||
ys_fn, | |||||
shadows, | |||||
i, | |||||
ys_fn(i), | |||||
xs_fn(i), | |||||
xe_fn(i) | |||||
); | |||||
float shiftAmount = ys_fn(j) + yh_fn(j) - ys_fn(i); | |||||
for (size_t shadow : shadows) { | |||||
yshift_fn(shadow, shiftAmount); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
// Checks if the ranges as..ae and bs..be overlap. | |||||
bool CMultiList::LinesOverlap(float as, float ae, float bs, float be) { | |||||
return (be > as && bs < ae) || (ae > bs && as < be); | |||||
} | |||||
CRect CMultiList::GetListRect() const | |||||
{ | |||||
return m_CachedActualSize + CRect(0, m_HeadingHeight, 0, 0); | |||||
} | |||||
void CMultiList::HandleMessage(SGUIMessage& Message) | |||||
{ | |||||
CList::HandleMessage(Message); | |||||
switch (Message.type) | |||||
{ | |||||
// If somebody clicks on the column heading | |||||
case GUIM_MOUSE_PRESS_LEFT: | |||||
{ | |||||
bool sortable; | |||||
GUI<bool>::GetSetting(this, "sortable", sortable); | |||||
if (!sortable) | |||||
return; | |||||
CPos mouse = GetMousePos(); | |||||
if (!m_CachedActualSize.PointInside(mouse)) | |||||
return; | |||||
CStr selectedColumn; | |||||
GUI<CStr>::GetSetting(this, "selected_column", selectedColumn); | |||||
int selectedColumnOrder; | |||||
GUI<int>::GetSetting(this, "selected_column_order", selectedColumnOrder); | |||||
for (const CMultiListColumn& column : m_Columns) | |||||
{ | |||||
bool hidden = false; | |||||
GUI<bool>::GetSetting(this, "hidden_" + column.m_Id, hidden); | |||||
if (hidden) | |||||
continue; | |||||
CPos leftTopCorner = CPos(column.m_Rect.left, column.m_ChosenY); | |||||
if ( | |||||
mouse.x >= leftTopCorner.x && | |||||
mouse.x < leftTopCorner.x + column.m_Width && | |||||
mouse.y >= leftTopCorner.y && | |||||
mouse.y < leftTopCorner.y + column.m_ChosenHeight | |||||
) | |||||
{ | |||||
if (column.m_Id != selectedColumn) | |||||
{ | |||||
selectedColumnOrder = 1; | |||||
selectedColumn = column.m_Id; | |||||
} | |||||
else | |||||
selectedColumnOrder = -selectedColumnOrder; | |||||
GUI<CStr>::SetSetting(this, "selected_column", column.m_Id); | |||||
GUI<int>::SetSetting(this, "selected_column_order", selectedColumnOrder); | |||||
ScriptEvent("selectioncolumnchange"); | |||||
CStrW soundPath; | |||||
if (g_SoundManager && GUI<CStrW>::GetSetting(this, "sound_selected", soundPath) == PSRETURN_OK && !soundPath.empty()) | |||||
g_SoundManager->PlayAsUI(soundPath.c_str(), false); | |||||
return; | |||||
} | |||||
} | |||||
return; | |||||
} | |||||
default: | |||||
return; | |||||
} | |||||
} | |||||
bool CMultiList::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile) | |||||
{ | |||||
#define ELMT(x) int elmt_##x = pFile->GetElementID(#x) | |||||
#define ATTR(x) int attr_##x = pFile->GetAttributeID(#x) | |||||
ELMT(item); | |||||
ELMT(column); | |||||
ELMT(translatableAttribute); | |||||
ATTR(id); | |||||
ATTR(context); | |||||
if (child.GetNodeName() == elmt_item) | |||||
{ | |||||
AddItem(child.GetText().FromUTF8(), child.GetText().FromUTF8()); | |||||
return true; | |||||
} | |||||
else if (child.GetNodeName() == elmt_column) | |||||
{ | |||||
CMultiListColumn column; | |||||
bool hidden = false; | |||||
for (XMBAttribute attr : child.GetAttributes()) | |||||
{ | |||||
CStr attr_name(pFile->GetAttributeString(attr.Name)); | |||||
CStr attr_value(attr.Value); | |||||
if (attr_name == "color") | |||||
{ | |||||
CColor color; | |||||
if (!GUI<CColor>::ParseString(attr_value.FromUTF8(), color)) | |||||
LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); | |||||
else | |||||
column.m_TextColor = color; | |||||
} | |||||
else if (attr_name == "id") | |||||
{ | |||||
column.m_Id = attr_value; | |||||
} | |||||
else if (attr_name == "hidden") | |||||
{ | |||||
if (!GUI<bool>::ParseString(attr_value.FromUTF8(), hidden)) | |||||
LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); | |||||
} | |||||
else if (attr_name == "size") | |||||
{ | |||||
CClientArea size; | |||||
if (!size.SetClientArea(attr_value)) | |||||
{ | |||||
LOGERROR("GUI: Error parsing '%s' (\"%s\")", attr_name.c_str(), attr_value.c_str()); | |||||
} | |||||
else | |||||
{ | |||||
column.m_Size = size; | |||||
} | |||||
} | |||||
else if (attr_name == "heading") | |||||
{ | |||||
column.m_Heading = attr_value.FromUTF8(); | |||||
} | |||||
} | |||||
for (XMBElement grandchild : child.GetChildNodes()) | |||||
{ | |||||
if (grandchild.GetNodeName() != elmt_translatableAttribute) | |||||
continue; | |||||
CStr attributeName(grandchild.GetAttributes().GetNamedItem(attr_id)); | |||||
// only the heading is translatable for list column | |||||
if (attributeName.empty() || attributeName != "heading") | |||||
{ | |||||
LOGERROR("GUI: translatable attribute in multilist column that isn't a heading. (object: %s)", this->GetPresentableName().c_str()); | |||||
continue; | |||||
} | |||||
CStr value(grandchild.GetText()); | |||||
if (value.empty()) | |||||
continue; | |||||
CStr context(grandchild.GetAttributes().GetNamedItem(attr_context)); // Read the context if any. | |||||
if (!context.empty()) | |||||
{ | |||||
CStr translatedValue(g_L10n.TranslateWithContext(context, value)); | |||||
column.m_Heading = translatedValue.FromUTF8(); | |||||
} | |||||
else | |||||
{ | |||||
CStr translatedValue(g_L10n.Translate(value)); | |||||
column.m_Heading = translatedValue.FromUTF8(); | |||||
} | |||||
} | |||||
UpdateColumnSizes(); | |||||
m_Columns.push_back(column); | |||||
AddSetting(GUIST_CGUIList, "list_" + column.m_Id); | |||||
AddSetting(GUIST_bool, "hidden_" + column.m_Id); | |||||
GUI<bool>::SetSetting(this, "hidden_" + column.m_Id, hidden); | |||||
SetupText(); | |||||
return true; | |||||
} | |||||
else | |||||
{ | |||||
return false; | |||||
} | |||||
} | |||||
void CMultiList::UpdateCachedSize() | |||||
{ | |||||
// If an ancestor's size changed, this will let us intercept the change and | |||||
// update our positions | |||||
CList::UpdateCachedSize(); | |||||
UpdateColumnSizes(); | |||||
SetupText(); | |||||
} | |||||
void CMultiList::UpdateColumnSizes() | |||||
{ | |||||
CRect area = m_CachedActualSize; | |||||
bool scrollbar; | |||||
GUI<bool>::GetSetting(this, "scrollbar", scrollbar); | |||||
// remove scrollbar if applicable | |||||
if (scrollbar && GetScrollBar(0).GetStyle()) | |||||
{ | |||||
area -= CRect(0, 0, GetScrollBar(0).GetStyle()->m_Width, 0); | |||||
} | |||||
for (CMultiListColumn& column : m_Columns) | |||||
{ | |||||
column.m_Rect = column.m_Size.GetClientArea(area); | |||||
column.m_Width = column.m_Rect.GetWidth(); | |||||
} | |||||
} | |||||
void CMultiList::DrawList(const int& selected, const CStr& _sprite, const CStr& _sprite_selected, const CStr& _textcolor) | |||||
{ | |||||
float bz = GetBufferedZ(); | |||||
bool scrollbar; | |||||
GUI<bool>::GetSetting(this, "scrollbar", scrollbar); | |||||
if (scrollbar) | |||||
IGUIScrollBarOwner::Draw(); | |||||
if (!GetGUI()) | |||||
return; | |||||
CRect rect = GetListRect(); | |||||
CGUISpriteInstance* sprite = NULL; | |||||
CGUISpriteInstance* sprite_selectarea = NULL; | |||||
int cell_id; | |||||
GUI<CGUISpriteInstance>::GetSettingPointer(this, _sprite, sprite); | |||||
GUI<CGUISpriteInstance>::GetSettingPointer(this, _sprite_selected, sprite_selectarea); | |||||
GUI<int>::GetSetting(this, "cell_id", cell_id); | |||||
CGUIList* pList; | |||||
GUI<CGUIList>::GetSettingPointer(this, "list", pList); | |||||
GetGUI()->DrawSprite(*sprite, cell_id, bz, rect); | |||||
float scroll = 0.f; | |||||
if (scrollbar) | |||||
scroll = GetScrollBar(0).GetPos(); | |||||
// Draw item selection | |||||
if (selected != -1) | |||||
{ | |||||
ENSURE(selected >= 0 && selected+1 < (int)m_ItemsYPositions.size()); | |||||
// Get rectangle of selection: | |||||
CRect rect_sel(rect.left, rect.top + m_ItemsYPositions[selected] - scroll, | |||||
rect.right, rect.top + m_ItemsYPositions[selected+1] - scroll); | |||||
if (rect_sel.top <= rect.bottom && | |||||
rect_sel.bottom >= rect.top) | |||||
{ | |||||
if (rect_sel.bottom > rect.bottom) | |||||
rect_sel.bottom = rect.bottom; | |||||
if (rect_sel.top < rect.top) | |||||
rect_sel.top = rect.top; | |||||
if (scrollbar) | |||||
{ | |||||
// Remove any overlapping area of the scrollbar. | |||||
if (rect_sel.right > GetScrollBar(0).GetOuterRect().left && | |||||
rect_sel.right <= GetScrollBar(0).GetOuterRect().right) | |||||
rect_sel.right = GetScrollBar(0).GetOuterRect().left; | |||||
if (rect_sel.left >= GetScrollBar(0).GetOuterRect().left && | |||||
rect_sel.left < GetScrollBar(0).GetOuterRect().right) | |||||
rect_sel.left = GetScrollBar(0).GetOuterRect().right; | |||||
} | |||||
// Draw item selection | |||||
GetGUI()->DrawSprite(*sprite_selectarea, cell_id, bz+0.05f, rect_sel); | |||||
} | |||||
} | |||||
// Draw line above column header | |||||
CGUISpriteInstance* sprite_heading = NULL; | |||||
GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite_heading", sprite_heading); | |||||
CRect rect_head(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right, | |||||
m_CachedActualSize.top + m_HeadingHeight); | |||||
GetGUI()->DrawSprite(*sprite_heading, cell_id, bz, rect_head); | |||||
// Draw column headers | |||||
bool sortable; | |||||
GUI<bool>::GetSetting(this, "sortable", sortable); | |||||
CStr selectedColumn; | |||||
GUI<CStr>::GetSetting(this, "selected_column", selectedColumn); | |||||
int selectedColumnOrder; | |||||
GUI<int>::GetSetting(this, "selected_column_order", selectedColumnOrder); | |||||
CColor color; | |||||
GUI<CColor>::GetSetting(this, _textcolor, color); | |||||
for (size_t c = 0; c < m_Columns.size(); ++c) | |||||
{ | |||||
bool hidden = false; | |||||
GUI<bool>::GetSetting(this, "hidden_" + m_Columns[c].m_Id, hidden); | |||||
if (hidden) | |||||
continue; | |||||
CPos leftTopCorner = CPos(m_Columns[c].m_Rect.left, m_Columns[c].m_ChosenY); | |||||
// Draw sort arrows in colum header | |||||
if (sortable) | |||||
{ | |||||
CGUISpriteInstance* sprite; | |||||
if (selectedColumn == m_Columns[c].m_Id) | |||||
{ | |||||
if (selectedColumnOrder == 0) | |||||
LOGERROR("selected_column_order must not be 0"); | |||||
if (selectedColumnOrder != -1) | |||||
GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite_asc", sprite); | |||||
else | |||||
GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite_desc", sprite); | |||||
} | |||||
else | |||||
GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite_not_sorted", sprite); | |||||
GetGUI()->DrawSprite( | |||||
*sprite, | |||||
cell_id, | |||||
bz + 0.1f, | |||||
CRect( | |||||
leftTopCorner + CPos(m_Columns[c].m_Width - SORT_SPRITE_DIM, 0), | |||||
leftTopCorner + CPos(m_Columns[c].m_Width, SORT_SPRITE_DIM) | |||||
) | |||||
); | |||||
} | |||||
// Draw column header text | |||||
DrawText(c, color, leftTopCorner + CPos(0, 4), bz + 0.1f, rect_head); | |||||
} | |||||
// Draw list items for each column | |||||
const size_t objectsCount = m_Columns.size(); | |||||
for (size_t i = 0; i < pList->m_Items.size(); ++i) | |||||
{ | |||||
if (m_ItemsYPositions[i+1] - scroll < 0 || | |||||
m_ItemsYPositions[i] - scroll > rect.GetHeight()) | |||||
continue; | |||||
// Clipping area (we'll have to substract the scrollbar) | |||||
CRect cliparea = GetListRect(); | |||||
if (scrollbar) | |||||
{ | |||||
if (cliparea.right > GetScrollBar(0).GetOuterRect().left && | |||||
cliparea.right <= GetScrollBar(0).GetOuterRect().right) | |||||
cliparea.right = GetScrollBar(0).GetOuterRect().left; | |||||
if (cliparea.left >= GetScrollBar(0).GetOuterRect().left && | |||||
cliparea.left < GetScrollBar(0).GetOuterRect().right) | |||||
cliparea.left = GetScrollBar(0).GetOuterRect().right; | |||||
} | |||||
// Draw all items for that column | |||||
for (size_t c = 0; c < objectsCount; ++c) | |||||
{ | |||||
bool hidden = false; | |||||
GUI<bool>::GetSetting(this, "hidden_" + m_Columns[c].m_Id, hidden); | |||||
if (hidden) | |||||
continue; | |||||
// Determine text position and width | |||||
const CPos textPos = | |||||
CPos(0, rect.TopLeft().y) | |||||
+ CPos(m_Columns[c].m_Rect.left, -scroll + m_ItemsYPositions[i]) | |||||
+ CPos(0, m_RowColumnLocs[objectsCount * i + c].m_ChosenY); | |||||
// Clip text to the column (to prevent drawing text into the neighboring column) | |||||
CRect cliparea2 = cliparea; | |||||
cliparea2.right = std::min(cliparea2.right, textPos.x + m_Columns[c].m_Width); | |||||
cliparea2.bottom = std::min(cliparea2.bottom, textPos.y + m_RowColumnLocs[objectsCount * i + c].m_ChosenHeight); | |||||
cliparea2.top = std::min(cliparea2.bottom, cliparea2.top); | |||||
// Draw list item | |||||
DrawText(objectsCount * (i +/*Heading*/1) + c, m_Columns[c].m_TextColor, textPos, bz + 0.1f, cliparea2); | |||||
} | |||||
} | |||||
} |
Wildfire Games · Phabricator