-
-
Notifications
You must be signed in to change notification settings - Fork 445
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Table truncates long strings #972
Comments
Hello @theqbz, See the namespace {
Elements Split(const std::string& the_text) {
Elements output;
std::stringstream ss(the_text);
std::string word;
while (std::getline(ss, word, ' ')) {
output.push_back(text(word));
}
return output;
}
} // namespace
/// @brief Return an element drawing the paragraph on multiple lines.
/// @ingroup dom
/// @see flexbox.
Element paragraph(const std::string& the_text) {
return paragraphAlignLeft(the_text);
}
/// @brief Return an element drawing the paragraph on multiple lines, aligned on
/// the left.
/// @ingroup dom
/// @see flexbox.
Element paragraphAlignLeft(const std::string& the_text) {
static const auto config = FlexboxConfig().SetGap(1, 0);
return flexbox(Split(the_text), config);
} You see we split the text into words, and put each word into a flexbox. As a result, you can't split elsewhere than inside a space. What you can try: Element SplitableText(const std::string& the_text) {
// Split every ~8 characters.
Elements miniwords;
for (size_t i = 0; i < the_text.size(); i += 8) {
miniwords.push_back(text(the_text.substr(i, 8)));
}
return flexbox(std::move(miniwords));
} I hope this will be working for you, but note that flexbox doesn't work flawlessly. |
Thank you very much for your answer! You helped me a lot to understand the operation of ftxui more precisely and based on this I was able to write a solution that flexibly adapts to the full width of the table and converts the contents of the cells into multiple rows if necessary at runtime. I'll include my solution here, which is not perfect, but may provide a good starting point for those who face a similar problem. //----------
// in MyTable class declaration:
//
// ftxui::Table m_table;
// static ftxui::FlexboxConfig m_flexboxConfig;
// std::vector<std::vector<std::string>> m_stringTable;
// std::vector<unsigned int> m_columnWidths;
//
// using ElementTableRow = ftxui::Elements;
// using ElementTable = std::vector<ElementTableRow>;
//
// enum class ColumnSeparators : bool
// {
// INCLUDED,
// NOT_INCLUDED
// };
//
//------------
// MyTable.cpp
//
/// There is obviously a better solution than this constrainTo function implementation!
template <typename TOutput, typename TInput>
requires std::is_integral_v<TOutput> && std::is_integral_v<TInput>
inline static const TOutput constrainTo(TInput p_value)
{
using CommonType = std::common_type_t<TOutput, TInput>;
constexpr CommonType outMin = static_cast<CommonType>(std::numeric_limits<TOutput>::min());
constexpr CommonType outMax = static_cast<CommonType>(std::numeric_limits<TOutput>::max());
if (p_value < outMin) {
return static_cast<TOutput>(outMin);
} else if (p_value > outMax) {
return static_cast<TOutput>(outMax);
}
return static_cast<TOutput>(p_value);
}
const ftxui::FlexboxConfig MyTable::m_flexboxConfig {
ftxui::FlexboxConfig()
.Set(ftxui::FlexboxConfig::Direction::Column)
.Set(ftxui::FlexboxConfig::Wrap::Wrap)
.Set(ftxui::FlexboxConfig::JustifyContent::FlexStart)
.Set(ftxui::FlexboxConfig::AlignItems::FlexStart)
.Set(ftxui::FlexboxConfig::AlignContent::FlexStart)
};
void MyTable::computeTheSpaceRequirementOfColumns()
{
std::vector<unsigned int> largestColumnWidths {};
for (const Result::Row& row : m_stringTable) {
for (unsigned int colIdx = 0U; colIdx < row.size(); ++colIdx) {
const unsigned int cellWidth { constrainTo<unsigned int>(row[colIdx].length()) };
if (colIdx >= largestColumnWidths.size()) {
largestColumnWidths.push_back(cellWidth);
} else if (cellWidth > largestColumnWidths[colIdx]) {
largestColumnWidths[colIdx] = cellWidth;
}
}
}
m_columnWidths = largestColumnWidths;
}
void MyTable::setTableContent(const std::vector<std::vector<std::string>>& p_tableContent)
{
m_stringTable.assign(p_tableContent.begin(), p_tableContent.end());
computeTheSpaceRequirementOfColumns();
}
static const unsigned int computeWrapPos(const std::string& p_content, const unsigned int& p_lastWrapPos, const unsigned int& p_cellWidthLimit)
{
const unsigned int length = constrainTo<unsigned int>(p_content.length());
if (length <= p_lastWrapPos || length <= p_cellWidthLimit) {
return length;
}
const unsigned int remainingLength = length - p_lastWrapPos;
if (remainingLength < p_cellWidthLimit) {
return length;
}
const unsigned int searchPos = p_lastWrapPos + p_cellWidthLimit;
const size_t lastSpacePosWithinLimit = p_content.rfind(' ', searchPos);
const bool noSpaceInTheCurrentPart = lastSpacePosWithinLimit == std::string::npos || lastSpacePosWithinLimit <= static_cast<size_t>(p_lastWrapPos);
const unsigned int wrapPos = noSpaceInTheCurrentPart ? searchPos : constrainTo<unsigned int>(lastSpacePosWithinLimit);
return wrapPos;
}
/// createCell function splits the incoming text into smaller units, if it is longer
/// than the specified cell width limit. The splitting is done with respect to
/// word boundaries, but if a word is longer than the limit, it will be
/// truncated at the limit. Finally, it puts the text pieces into a flexbox
/// element, which creates a single table cell.
const ftxui::Element MyTable::createCell(const std::string& p_content, const unsigned int& p_cellWidthLimit) const
{
if (p_cellWidthLimit < 1U) {
return ftxui::emptyElement();
}
ftxui::Elements result {};
unsigned int lastWrapPos { 0U };
while (lastWrapPos < p_content.length()) {
const unsigned int wrapPos { computeWrapPos(p_content, lastWrapPos, p_cellWidthLimit) };
if (wrapPos <= lastWrapPos) {
break;
}
const unsigned int wrapLength { wrapPos - lastWrapPos };
const std::string chunk { p_content.substr(lastWrapPos, wrapLength) };
if (!chunk.empty()) {
result.push_back(ftxui::text(chunk));
}
lastWrapPos = wrapPos; // I do not want to move past the current wrap position.
}
return ftxui::flexbox(result, m_flexboxConfig);
}
/// computeCellWidthLimit function calculates the maximum width that each column can have,
/// based on the specified maximum table width. It takes into account the
/// width of the column separators if they are included.
const unsigned int MyTable::computeCellWidthLimit(const unsigned int& p_maxTableWidth, const ColumnSeparators p_columnSeparators) const
{
if (m_columnWidths.empty()) {
return 0U;
}
const unsigned int columnCount = constrainTo<unsigned int>(m_columnWidths.size());
const unsigned int separatorWidth = p_columnSeparators == ColumnSeparators::INCLUDED ? (columnCount - 1U) : 0U;
if (p_maxTableWidth < (columnCount + separatorWidth)) {
return 0U;
}
const std::multiset<unsigned int> columnWidths(m_columnWidths.begin(), m_columnWidths.end());
const unsigned int availableSpace = p_maxTableWidth - separatorWidth;
const unsigned int upperBound = availableSpace / columnCount;
const std::multiset<unsigned int>::const_iterator firstOutOfBound { columnWidths.upper_bound(upperBound) };
if (firstOutOfBound == columnWidths.end()) {
return *columnWidths.crbegin();
}
const unsigned int widthBelowBound = std::accumulate(columnWidths.begin(), firstOutOfBound, 0U);
if (availableSpace <= widthBelowBound) {
return upperBound;
}
const unsigned int remainingWidth = availableSpace - widthBelowBound;
const unsigned int remainingColumns = constrainTo<unsigned int>(std::distance(firstOutOfBound, columnWidths.end()));
const unsigned int columnWidthLimit = remainingWidth / remainingColumns;
return columnWidthLimit;
}
const MyTable::ElementTable MyTable::createTable(const unsigned int& p_maxTableWidth, const ColumnSeparators p_columnSeparators) const
{
if (m_stringTable.empty()) {
return { { ftxui::emptyElement() } };
}
const unsigned int cellWidthLimit { computeCellWidthLimit(p_maxTableWidth, p_columnSeparators) };
if (cellWidthLimit < 1U) {
return { { ftxui::emptyElement() } };
}
MyTable::ElementTable result {};
for (const Result::Row& row : m_stringTable) {
MyTable::ElementTableRow tableRow {};
for (const std::string& cell : row) {
tableRow.push_back(ftxui::hbox(createCell(cell, cellWidthLimit), ftxui::filler()));
}
result.push_back(tableRow);
}
return result;
}
void MyTable::createNormalTable(const MyApp::data::Dimensions& p_dimensions)
{
if (!m_stringTable.empty()) {
m_table = createTable(p_dimensions.getWidth(), ColumnSeparators::INCLUDED);
m_table.SelectAll().SeparatorVertical(ftxui::LIGHT);
m_table.SelectRow(0).BorderBottom(ftxui::LIGHT);
m_table.SelectRow(0).DecorateCells(ftxui::bold);
}
}
ftxui::Element MyTable::getRenderer(const MyApp::data::Dimensions& p_dimensions)
{
createNormalTable(p_dimensions);
return m_table.Render();
} |
Sometimes I want to display a very long string in an ftxui::Table cell. In such a case, the table reaches the maximum possible size - for example, the edge of the screen - but the long string does not form a line break, even if I store it in a ftxui::paragraph instead of text. The end of the string is simply cut off. Can you suggest a method for how such long strings could be displayed properly in an ftxui::Table?
I think it is possible that perhaps the ftxui::paragraph does not know the boundaries within the table, within which it can extend, so the line break does not apply. If so, how can I pass this information to the paragraph?
Actual result:
Expected result:
Sorry for this poor illustration, I hope it helps you understand my idea.
I use ftxui::Table through a wrapper class, in two ways:
MyTable::print()
)MyTable::getRenderer()
)The text was updated successfully, but these errors were encountered: