Skip to content

Commit

Permalink
Items in ListView can now have icons
Browse files Browse the repository at this point in the history
  • Loading branch information
texus committed Jan 27, 2019
1 parent 99dee39 commit 649755d
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 20 deletions.
22 changes: 22 additions & 0 deletions include/TGUI/Widgets/ListView.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ namespace tgui
struct Item
{
std::vector<Text> texts;
Sprite icon;
};

struct Column
Expand Down Expand Up @@ -292,6 +293,25 @@ namespace tgui
int getSelectedItemIndex() const;


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Sets a small icon in front of the item
///
/// @param index Index of the item
/// @param texture Texture of the item icon
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void setItemIcon(std::size_t index, const Texture& texture);


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Gets the icon displayed in front of the item
///
/// @param index Index of the item
///
/// @return Texture of the item icon
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Texture getItemIcon(std::size_t index) const;


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief Returns the amount of items in the list view
///
Expand Down Expand Up @@ -661,6 +681,8 @@ namespace tgui
unsigned int m_textSize = 0;
unsigned int m_headerTextSize = 0;
unsigned int m_separatorWidth = 1;
unsigned int m_iconCount = 0;
float m_maxIconWidth = 0;
bool m_headerVisible = true;

CopiedSharedPtr<ScrollbarChildWidget> m_horizontalScrollbar;
Expand Down
131 changes: 125 additions & 6 deletions src/TGUI/Widgets/ListView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ namespace tgui
{
Item item;
item.texts.push_back(createText(text));
item.icon.setOpacity(m_opacityCached);
m_items.push_back(std::move(item));

updateVerticalScrollbarMaximum();
Expand All @@ -289,6 +290,7 @@ namespace tgui
for (const auto& text : itemTexts)
item.texts.push_back(createText(text));

item.icon.setOpacity(m_opacityCached);
m_items.push_back(std::move(item));

updateVerticalScrollbarMaximum();
Expand Down Expand Up @@ -331,8 +333,30 @@ namespace tgui
if (index >= m_items.size())
return false;

const bool wasIconSet = m_items[index].icon.isSet();
m_items.erase(m_items.begin() + index);

if (wasIconSet)
{
--m_iconCount;

const float oldMaxIconWidth = m_maxIconWidth;
m_maxIconWidth = 0;
if (m_iconCount > 0)
{
// Rescan all items to find the largest icon
for (const auto& item : m_items)
{
if (!item.icon.isSet())
continue;

m_maxIconWidth = std::max(m_maxIconWidth, item.icon.getSize().x);
if (m_maxIconWidth == oldMaxIconWidth)
break;
}
}
}

updateVerticalScrollbarMaximum();
return true;
}
Expand All @@ -346,6 +370,9 @@ namespace tgui

m_items.clear();

m_iconCount = 0;
m_maxIconWidth = 0;

updateVerticalScrollbarMaximum();
}

Expand Down Expand Up @@ -384,6 +411,60 @@ namespace tgui

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void ListView::setItemIcon(std::size_t index, const Texture& texture)
{
if (index >= m_items.size())
{
TGUI_PRINT_WARNING("setItemIcon called with invalid index.");
return;
}

const bool wasIconSet = m_items[index].icon.isSet();
m_items[index].icon.setTexture(texture);

if (m_items[index].icon.isSet())
{
m_maxIconWidth = std::max(m_maxIconWidth, m_items[index].icon.getSize().x);
if (!wasIconSet)
++m_iconCount;
}
else if (wasIconSet)
{
--m_iconCount;

const float oldMaxIconWidth = m_maxIconWidth;
m_maxIconWidth = 0;
if (m_iconCount > 0)
{
// Rescan all items to find the largest icon
for (const auto& item : m_items)
{
if (!item.icon.isSet())
continue;

m_maxIconWidth = std::max(m_maxIconWidth, item.icon.getSize().x);
if (m_maxIconWidth == oldMaxIconWidth)
break;
}
}
}
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Texture ListView::getItemIcon(std::size_t index) const
{
if (index < m_items.size())
return m_items[index].icon.getTexture();
else
{
TGUI_PRINT_WARNING("getItemIcon called with invalid index.");
return {};
}
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

std::size_t ListView::getItemCount() const
{
return m_items.size();
Expand Down Expand Up @@ -914,6 +995,8 @@ namespace tgui
{
for (auto& text : item.texts)
text.setOpacity(m_opacityCached);

item.icon.setOpacity(m_opacityCached);
}
}
else if (property == "font")
Expand Down Expand Up @@ -1270,6 +1353,7 @@ namespace tgui

void ListView::updateScrollbars()
{
const bool verticalScrollbarAtBottom = (m_verticalScrollbar->getValue() + m_verticalScrollbar->getViewportSize() >= m_verticalScrollbar->getMaximum());
const float headerHeight = (m_headerVisible && !m_columns.empty()) ? getHeaderHeight() : 0.f;
const Vector2f innerSize = {std::max(0.f, getInnerSize().x - m_paddingCached.getLeft() - m_paddingCached.getRight()),
std::max(0.f, getInnerSize().y - m_paddingCached.getTop() - m_paddingCached.getBottom() - headerHeight)};
Expand All @@ -1295,6 +1379,10 @@ namespace tgui
m_horizontalScrollbar->setSize({getInnerSize().x, m_horizontalScrollbar->getSize().y});
m_horizontalScrollbar->setViewportSize(static_cast<unsigned int>(innerSize.x));
}

// If the scrollbar was at the bottom then keep it at the bottom if it changes due to a different viewport size
if (verticalScrollbarAtBottom && (m_verticalScrollbar->getValue() + m_verticalScrollbar->getViewportSize() < m_verticalScrollbar->getMaximum()))
m_verticalScrollbar->setValue(m_verticalScrollbar->getMaximum() - m_verticalScrollbar->getViewportSize());
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -1356,15 +1444,46 @@ namespace tgui
if (firstItem == lastItem)
return;

const float verticalTextOffset = (m_itemHeight - Text::getLineHeight(m_fontCached, m_textSize)) / 2.0f;
const float headerHeight = (m_headerVisible && !m_columns.empty()) ? getHeaderHeight() : 0.f;
const float textPadding = Text::getExtraHorizontalOffset(m_fontCached, m_textSize);
const float columnHeight = getInnerSize().y - m_paddingCached.getTop() - m_paddingCached.getBottom()
- headerHeight - (m_horizontalScrollbar->isShown() ? m_horizontalScrollbar->getSize().y : 0);
const Clipping clipping{target, states, {textPadding, 0}, {columnWidth - (2 * textPadding), columnHeight}};

states.transform.translate({0, -static_cast<float>(m_verticalScrollbar->getValue())});
states.transform.translate({0, (m_itemHeight * firstItem) + (m_itemHeight - Text::getLineHeight(m_fontCached, m_textSize)) / 2.0f});
// Draw the icons.
// If at least one icon is set then all items in the first column have to be shifted to make room for the icon.
if ((column == 0) && (m_iconCount > 0))
{
const sf::Transform transformBeforeIcons = states.transform;
const Clipping clipping{target, states, {textPadding, 0}, {columnWidth - (2 * textPadding), columnHeight}};

states.transform.translate({0, (m_itemHeight * firstItem) - static_cast<float>(m_verticalScrollbar->getValue())});

for (std::size_t i = firstItem; i < lastItem; ++i)
{
if (!m_items[i].icon.isSet())
{
states.transform.translate({0, static_cast<float>(m_itemHeight)});
continue;
}

const float verticalIconOffset = (m_itemHeight - m_items[i].icon.getSize().y) / 2.f;

states.transform.translate({textPadding, verticalIconOffset});
m_items[i].icon.draw(target, states);
states.transform.translate({-textPadding, static_cast<float>(m_itemHeight) - verticalIconOffset});
}

states.transform = transformBeforeIcons;

const float extraIconSpace = m_maxIconWidth + textPadding;
columnWidth -= extraIconSpace;
states.transform.translate({extraIconSpace, 0});
}

const Clipping clipping{target, states, {textPadding, 0}, {columnWidth - (2 * textPadding), columnHeight}};

states.transform.translate({0, (m_itemHeight * firstItem) - static_cast<float>(m_verticalScrollbar->getValue())});
for (std::size_t i = firstItem; i < lastItem; ++i)
{
if (column >= m_items[i].texts.size())
Expand All @@ -1374,16 +1493,16 @@ namespace tgui
}

float translateX;
if ((m_columns[column].alignment == ColumnAlignment::Left) || (column >= m_columns.size()))
if ((column >= m_columns.size()) || (m_columns[column].alignment == ColumnAlignment::Left))
translateX = textPadding;
else if (m_columns[column].alignment == ColumnAlignment::Center)
translateX = (columnWidth - m_items[i].texts[column].getSize().x) / 2.f;
else // if (m_columns[column].alignment == ColumnAlignment::Right)
translateX = columnWidth - textPadding - m_items[i].texts[column].getSize().x;

states.transform.translate({translateX, 0});
states.transform.translate({translateX, verticalTextOffset});
m_items[i].texts[column].draw(target, states);
states.transform.translate({-translateX, static_cast<float>(m_itemHeight)});
states.transform.translate({-translateX, static_cast<float>(m_itemHeight) - verticalTextOffset});
}
}

Expand Down
45 changes: 31 additions & 14 deletions tests/Widgets/ListView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,14 @@ TEST_CASE("[ListView]")
REQUIRE(listView->getTextSize() == 20);

listView->setTextSize(0);
REQUIRE(listView->getTextSize() > 0);
REQUIRE(listView->getTextSize() != 20);
REQUIRE(listView->getTextSize() < 50);
const unsigned int textSize = listView->getTextSize();
REQUIRE(textSize > 0);
REQUIRE(textSize != 20);
REQUIRE(textSize < 50);

listView->setItemHeight(60);
REQUIRE(listView->getTextSize() > textSize);
REQUIRE(listView->getTextSize() < 60);
}

SECTION("HeaderTextSize")
Expand Down Expand Up @@ -380,20 +385,20 @@ TEST_CASE("[ListView]")
}
}

SECTION("Header changes item positions")
SECTION("Click on header")
{
listView->setHeaderHeight(30);
listView->addColumn("Col 1", 50);
listView->addColumn("Col 2", 50);

mousePressed({40, 68});
mouseReleased({40, 68});
REQUIRE(listView->getSelectedItemIndex() == 0);
mousePressed({40, 35});
mouseReleased({40, 35});
REQUIRE(listView->getSelectedItemIndex() == -1);

listView->setHeaderVisible(false);
mousePressed({40, 68});
mouseReleased({40, 68});
REQUIRE(listView->getSelectedItemIndex() == 2);
mousePressed({40, 35});
mouseReleased({40, 35});
REQUIRE(listView->getSelectedItemIndex() == 0);
}

SECTION("Vertical scrollbar interaction")
Expand Down Expand Up @@ -623,10 +628,6 @@ TEST_CASE("[ListView]")
renderer.setSelectedTextColorHover("#808080");
};

listView->addColumn("C1", 40);
listView->addColumn("C2", 70);
listView->addColumn("C3", 70);

listView->addItem({"1", "1.2"});
listView->addItem("2");
listView->addItem({"3", "3.2"});
Expand All @@ -638,6 +639,15 @@ TEST_CASE("[ListView]")
const sf::Vector2f mousePos2{30, 50};
const sf::Vector2f mousePos3{30, 45};

SECTION("No columns")
{
TEST_DRAW("ListView_NoColumns.png")
}

listView->addColumn("C1", 40);
listView->addColumn("C2", 70);
listView->addColumn("C3", 70);

SECTION("No selected item")
{
SECTION("No hover")
Expand Down Expand Up @@ -782,5 +792,12 @@ TEST_CASE("[ListView]")
}
}
}

SECTION("Icons")
{
listView->setItemIcon(3, {"resources/Texture6.png", {0, 0, 20, 14}});
listView->setItemIcon(4, {"resources/Texture7.png", {0, 0, 14, 14}});
TEST_DRAW("ListView_Icons.png")
}
}
}
Binary file added tests/expected/ListView_Icons.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/expected/ListView_NoColumns.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 649755d

Please sign in to comment.