Last active
September 18, 2024 07:37
-
-
Save db48x/26b3ecf3dd76b5669f883c00d78a6e5c to your computer and use it in GitHub Desktop.
wrapped text with styled spans
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 ¶, float wrap_width, bool styled ) | |
{ | |
for( const auto &seg : para.segs ) { | |
TextSegmentEx( seg, wrap_width, styled ); | |
} | |
ImGui::NewLine(); | |
} | |
void TextStyled( const Paragraph ¶, float wrap_width ) | |
{ | |
TextParagraphEx( para, wrap_width, true ); | |
} | |
void TextUnstyled( const Paragraph ¶, float wrap_width ) | |
{ | |
TextParagraphEx( para, wrap_width, false ); | |
} | |
void TextParagraph( nc_color color, const std::string ¶, 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 ¶, float wrap_width = 0.0f ); | |
void TextUnstyled( const Paragraph ¶, float wrap_width = 0.0f ); | |
void TextParagraph( nc_color color, const std::string ¶, 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