Skip to content

Instantly share code, notes, and snippets.

@db48x
Last active September 18, 2024 07:37
Show Gist options
  • Save db48x/26b3ecf3dd76b5669f883c00d78a6e5c to your computer and use it in GitHub Desktop.
Save db48x/26b3ecf3dd76b5669f883c00d78a6e5c to your computer and use it in GitHub Desktop.
wrapped text with styled spans
void main(void)
{
#if 0
ImGui::ShowDemoWindow();
#endif
ImGui::SetNextWindowSize( { 620, 900 }, ImGuiCond_Once );
if( ImGui::Begin( "test" ) ) {
static float wrap_width = 200.0f;
ImGui::SliderFloat("Wrap width", &wrap_width, -20, 600, "%.0f");
float marker_size = ImGui::GetTextLineHeight();
#define WRAP_START() \
ImDrawList *draw_list = ImGui::GetWindowDrawList(); \
ImVec2 pos = ImGui::GetCursorScreenPos(); \
ImVec2 marker_min = ImVec2(pos.x + wrap_width, pos.y); \
ImVec2 marker_max = \
ImVec2(pos.x + wrap_width + marker_size, pos.y + marker_size); \
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width);
#define WRAP_END() \
draw_list->AddRectFilled(marker_min, marker_max, \
IM_COL32(255, 0, 255, 255)); \
ImGui::PopTextWrapPos();
// three sentences in a nicely–wrapped paragraph
if (ImGui::CollapsingHeader("Plain wrapped text"))
{
WRAP_START();
ImGui::TextWrapped( "%s", "Some long text that will wrap around nicely. Some red text in the middle. Some long text that will wrap around nicely." );
WRAP_END();
ImGui::NewLine();
}
Paragraph stuff = cata_imgui::parse_colored_text( "Some long text that will wrap around nicely. <color_red>Some red text in the middle.</color> Some long text that will wrap around nicely.", c_white );
if (ImGui::CollapsingHeader("Styled Paragraph"))
{
WRAP_START();
cata_imgui::TextStyled( stuff, wrap_width );
WRAP_END();
ImGui::NewLine();
}
if (ImGui::CollapsingHeader("Unstyled Paragraph"))
{
WRAP_START();
cata_imgui::TextUnstyled( stuff, wrap_width );
WRAP_END();
ImGui::NewLine();
}
if (ImGui::CollapsingHeader("Styled Paragraph, no allocations"))
{
WRAP_START();
cata_imgui::TextParagraph( "Some long text that will wrap around nicely.", c_white, wrap_width );
cata_imgui::TextParagraph( " Some red text in the middle.", c_red, wrap_width );
cata_imgui::TextParagraph( " Some long text that will wrap around nicely.", c_white, wrap_width );
ImGui::NewLine();
WRAP_END();
ImGui::NewLine();
}
if (ImGui::CollapsingHeader("Naive attempt"))
{
WRAP_START();
// same three sentences, but the color breaks the wrapping
ImGui::TextUnformatted( "Some long text that will wrap around nicely." );
ImGui::SameLine();
ImGui::TextColored( c_red, "%s", "Some red text in the middle." );
ImGui::SameLine();
ImGui::TextUnformatted( "Some long text that will wrap around nicely." );
WRAP_END();
}
}
ImGui::End();
}
#define IMGUI_DEFINE_MATH_OPERATORS
#include "third-party/imgui/imgui.h"
#include "third-party/imgui/imgui_internal.h"
#undef IMGUI_DEFINE_MATH_OPERATORS
#include "color.h"
#include "text.h"
static uint32_t u32_from_color( nc_color color )
{
return ImGui::ColorConvertFloat4ToU32( color );
}
namespace cataimgui {
Segment::Segment( ) = default;
Segment::Segment( const std::string_view( sv ), uint32_t c )
: str( sv )
, color( c )
{}
Segment::Segment( const std::string_view( sv ), nc_color c )
: str( sv )
, color( u32_from_color( c ) )
{}
Paragraph *parse_colored_text( const std::string &str, nc_color default_color )
{
return (new cataimgui::Paragraph())->append_colored_text( str, default_color );
}
Paragraph::Paragraph( )
: segs( {} )
{}
Paragraph::Paragraph( std::vector<Segment> &s )
: segs( s )
{};
Paragraph::Paragraph( const std::string &str )
{
append_colored_text( str, 0 );
}
Paragraph *Paragraph::append( std::string_view str, uint32_t color )
{
segs.emplace_back( str, color );
return this;
}
Paragraph *Paragraph::append( std::string_view str, nc_color color )
{
return append( str, u32_from_color( color ) );
}
Paragraph *Paragraph::append_colored_text( std::string_view str, nc_color default_color ) {
return append_colored_text( str, u32_from_color( default_color ) );
}
Paragraph *Paragraph::append_colored_text( std::string_view str, uint32_t default_color ) {
if( str.empty() ) {
return this;
}
size_t s = 0;
size_t e = 0;
std::vector<uint32_t> colors = std::vector<uint32_t>( 1, default_color );
while( std::string::npos != ( e = str.find( '<', s ) ) ) {
append( str.substr( s, e - s ), colors.back() );
size_t ce = str.find( '>', e );
if( std::string::npos != ce ) {
color_tag_parse_result tag = get_color_from_tag( str.substr( e, ce - e + 1 ) );
switch( tag.type ) {
case color_tag_parse_result::open_color_tag:
colors.push_back( u32_from_color( tag.color ) );
break;
case color_tag_parse_result::close_color_tag:
if( colors.size() > 1 ) {
colors.pop_back();
}
break;
case color_tag_parse_result::non_color_tag:
append( str.substr( e, ce - e + 1 ), colors.back() );
break;
}
s = ce + 1;
} else {
break;
}
}
append( str.substr( s, std::string::npos ), colors.back() );
return this;
}
Paragraph *Paragraph::black( std::string_view str ) {
append( str, c_black );
return this;
}
Paragraph *Paragraph::white( std::string_view str ) {
append( str, c_white );
return this;
}
Paragraph *Paragraph::light_gray( std::string_view str ) {
append( str, c_light_gray );
return this;
}
Paragraph *Paragraph::dark_gray( std::string_view str ) {
append( str, c_dark_gray );
return this;
}
Paragraph *Paragraph::red( std::string_view str ) {
append( str, c_red );
return this;
}
Paragraph *Paragraph::green( std::string_view str ) {
append( str, c_green );
return this;
}
Paragraph *Paragraph::blue( std::string_view str ) {
append( str, c_blue );
return this;
}
Paragraph *Paragraph::cyan( std::string_view str ) {
append( str, c_cyan );
return this;
}
Paragraph *Paragraph::magenta( std::string_view str ) {
append( str, c_magenta );
return this;
}
Paragraph *Paragraph::brown( std::string_view str ) {
append( str, c_brown );
return this;
}
Paragraph *Paragraph::light_red( std::string_view str ) {
append( str, c_light_red );
return this;
}
Paragraph *Paragraph::light_green( std::string_view str ) {
append( str, c_light_green );
return this;
}
Paragraph *Paragraph::light_blue( std::string_view str ) {
append( str, c_light_blue );
return this;
}
Paragraph *Paragraph::light_cyan( std::string_view str ) {
append( str, c_light_cyan );
return this;
}
Paragraph *Paragraph::pink( std::string_view str ) {
append( str, c_pink );
return this;
}
Paragraph *Paragraph::yellow( std::string_view str ) {
append( str, c_yellow );
return this;
}
Paragraph *Paragraph::spaced() {
if( !segs.empty() && segs.back().str.back() != ' ' ) {
append( " ", 0 );
}
return this;
}
Paragraph *Paragraph::separated() {
separator_before = true;
return this;
}
static void TextEx( const std::string_view str, float wrap_width, uint32_t color ) {
if( str.empty() ) {
return;
}
ImFont *Font = ImGui::GetFont();
const char *textStart = str.data();
const char *textEnd = textStart + str.length();
do {
float widthRemaining = ImGui::CalcWrapWidthForPos(ImGui::GetCursorScreenPos(), wrap_width);
const char *drawEnd = Font->CalcWordWrapPositionA( 1.0f, textStart, textEnd, widthRemaining );
if( textStart == drawEnd ) {
ImGui::NewLine();
drawEnd = Font->CalcWordWrapPositionA( 1.0f, textStart, textEnd, widthRemaining );
}
if( color ) {
ImGui::PushStyleColor( ImGuiCol_Text, color );
}
ImGui::TextUnformatted( textStart, textStart == drawEnd ? nullptr : drawEnd );
if( color ) {
ImGui::PopStyleColor();
}
if( textStart == drawEnd || drawEnd == textEnd ) {
ImGui::SameLine( 0.0f, 0.0f );
break;
}
textStart = drawEnd;
while( textStart < textEnd ) {
const char c = *textStart;
if( ImCharIsBlankA( c ) ) {
textStart++;
} else if( c == '\n' ) {
textStart++;
break;
} else {
break;
}
}
} while( true );
}
static void TextSegmentEx( const Segment &seg, float wrap_width, bool styled) {
TextEx( seg.str, wrap_width, styled ? seg.color : 0 );
}
static void TextParagraphEx( const Paragraph &para, float wrap_width, bool styled )
{
for( const auto &seg : para.segs ) {
TextSegmentEx( seg, wrap_width, styled );
}
ImGui::NewLine();
}
void TextStyled( const Paragraph &para, float wrap_width )
{
TextParagraphEx( para, wrap_width, true );
}
void TextUnstyled( const Paragraph &para, float wrap_width )
{
TextParagraphEx( para, wrap_width, false );
}
void TextParagraph( nc_color color, const std::string &para, float wrap_width )
{
TextEx( para, wrap_width, u32_from_color( color ) );
}
void TextColoredParagraph( nc_color default_color, const std::string_view str, std::optional<Segment> value, float wrap_width ) {
if( str.empty() ) {
return;
}
size_t s = 0;
size_t e = 0;
std::vector<uint32_t> colors = std::vector<uint32_t>( 1, u32_from_color( default_color ) );
while( std::string::npos != ( e = str.find( '<', s ) ) ) {
TextEx( str.substr( s, e - s ), wrap_width, colors.back() );
size_t ce = str.find( '>', e );
if( std::string::npos != ce ) {
std::string_view tagname = str.substr( e, ce - e + 1 );
if( "<num>" == tagname && value.has_value() ) {
TextSegmentEx( value.value(), wrap_width, true );
} else {
color_tag_parse_result tag = get_color_from_tag( str.substr( e, ce - e + 1 ) );
switch( tag.type ) {
case color_tag_parse_result::open_color_tag:
colors.push_back( u32_from_color( tag.color ) );
break;
case color_tag_parse_result::close_color_tag:
if( colors.size() > 1 ) {
colors.pop_back();
}
break;
case color_tag_parse_result::non_color_tag:
TextEx( tagname, wrap_width, colors.back() );
break;
}
}
s = ce + 1;
} else {
break;
}
}
TextEx( str.substr( s, std::string::npos ), wrap_width, colors.back() );
}
} // namespace cataimgui
#pragma once
#ifndef CATA_SRC_TEXT_H
#define CATA_SRC_TEXT_H
#include <cmath>
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
#include "color.h"
// This is an experimental text API. Where possible, it is better to
// use TextParagraph or TextColoredParagraph to wrap text rather than
// allocating Segments and Paragraphs.
namespace cataimgui
{
struct Segment {
std::string str;
uint32_t color;
Segment( );
// NOLINTBEGIN(google-explicit-constructor)
Segment( const std::string_view( sv ), uint32_t c = 0 );
Segment( const std::string_view( sv ), nc_color c );
// NOLINTEND(google-explicit-constructor)
};
struct Paragraph {
std::vector<Segment> segs;
bool separator_before = false;
Paragraph( );
explicit Paragraph( std::vector<Segment> &s );
explicit Paragraph( const std::string &str );
Paragraph *append( std::string_view str, uint32_t color );
Paragraph *append( std::string_view str, nc_color color );
Paragraph *append_colored_text( std::string_view str, uint32_t default_color );
Paragraph *append_colored_text( std::string_view str, nc_color default_color );
Paragraph *black( std::string_view str );
Paragraph *white( std::string_view str );
Paragraph *light_gray( std::string_view str );
Paragraph *dark_gray( std::string_view str );
Paragraph *red( std::string_view str );
Paragraph *green( std::string_view str );
Paragraph *blue( std::string_view str );
Paragraph *cyan( std::string_view str );
Paragraph *magenta( std::string_view str );
Paragraph *brown( std::string_view str );
Paragraph *light_red( std::string_view str );
Paragraph *light_green( std::string_view str );
Paragraph *light_blue( std::string_view str );
Paragraph *light_cyan( std::string_view str );
Paragraph *pink( std::string_view str );
Paragraph *yellow( std::string_view str );
Paragraph *spaced();
Paragraph *separated();
};
Paragraph *parse_colored_text( const std::string &str, nc_color default_color );
void TextStyled( const Paragraph &para, float wrap_width = 0.0f );
void TextUnstyled( const Paragraph &para, float wrap_width = 0.0f );
void TextParagraph( nc_color color, const std::string &para, float wrap_width = 0.0f );
void TextColoredParagraph( nc_color color, std::string_view str, std::optional<Segment> value = std::nullopt, float wrap_width = 0.0f );
} // namespace cataimgui
#endif // CATA_SRC_TEXT_H
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment