Add RichText class to support string with colors.

This commit is contained in:
梦凌汐 2024-12-06 02:52:10 +08:00
parent 89b5045321
commit 6ba9a3e1eb
4 changed files with 332 additions and 0 deletions

BIN
utils/Color Normal file

Binary file not shown.

128
utils/Color.h Normal file
View File

@ -0,0 +1,128 @@
#ifndef CONSTS_H
#define CONSTS_H
#include <windows.h>
#include <wincon.h>
/*
FOREGROUND_BLUE
FOREGROUND_GREEN 绿
FOREGROUND_RED
FOREGROUND_INTENSITY
BACKGROUND_BLUE
BACKGROUND_GREEN 绿
BACKGROUND_RED
BACKGROUND_INTENSITY
*/
enum {
COLOR_BLACK = 0,
COLOR_BLUE = 1,
COLOR_GREEN = 2,
COLOR_CYAN = 3,
COLOR_RED = 4,
COLOR_PURPLE = 5,
COLOR_YELLOW = 6,
COLOR_WHITE = 7,
COLOR_GRAY = 8,
COLOR_LIGHTBLUE = 9,
COLOR_LIGHTGREEN = 10,
COLOR_LIGHTCYAN = 11,
COLOR_LIGHTRED = 12,
COLOR_LIGHTPURPLE = 13,
COLOR_LIGHTYELLOW = 14,
COLOR_BRIGHTWHITE = 15
};
typedef short MColor;
inline MColor getColor(int front_color, int back_color) {
return (back_color << 4) | front_color;
}
inline WORD FrontColorToWinColor(int color) {
switch (color) {
case COLOR_BLACK:
return 0;
case COLOR_BLUE:
return FOREGROUND_BLUE;
case COLOR_GREEN:
return FOREGROUND_GREEN;
case COLOR_CYAN:
return FOREGROUND_GREEN | FOREGROUND_BLUE;
case COLOR_RED:
return FOREGROUND_RED;
case COLOR_PURPLE:
return FOREGROUND_RED | FOREGROUND_BLUE;
case COLOR_YELLOW:
return FOREGROUND_RED | FOREGROUND_GREEN;
case COLOR_WHITE:
return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
case COLOR_GRAY:
return FOREGROUND_INTENSITY;
case COLOR_LIGHTBLUE:
return FOREGROUND_INTENSITY | FOREGROUND_BLUE;
case COLOR_LIGHTGREEN:
return FOREGROUND_INTENSITY | FOREGROUND_GREEN;
case COLOR_LIGHTCYAN:
return FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_BLUE;
case COLOR_LIGHTRED:
return FOREGROUND_INTENSITY | FOREGROUND_RED;
case COLOR_LIGHTPURPLE:
return FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_BLUE;
case COLOR_LIGHTYELLOW:
return FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN;
case COLOR_BRIGHTWHITE:
return FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
default:
return 0;
}
}
inline int getFrontColor(MColor color) {
return color & 0x0F;
}
inline WORD BackgroundColorToWinColor(int color) {
switch (color) {
case COLOR_BLACK:
return 0;
case COLOR_BLUE:
return BACKGROUND_BLUE;
case COLOR_GREEN:
return BACKGROUND_GREEN;
case COLOR_CYAN:
return BACKGROUND_GREEN | BACKGROUND_BLUE;
case COLOR_RED:
return BACKGROUND_RED;
case COLOR_PURPLE:
return BACKGROUND_RED | BACKGROUND_BLUE;
case COLOR_YELLOW:
return BACKGROUND_RED | BACKGROUND_GREEN;
case COLOR_WHITE:
return BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;
case COLOR_GRAY:
return BACKGROUND_INTENSITY;
case COLOR_LIGHTBLUE:
return BACKGROUND_INTENSITY | BACKGROUND_BLUE;
case COLOR_LIGHTGREEN:
return BACKGROUND_INTENSITY | BACKGROUND_GREEN;
case COLOR_LIGHTCYAN:
return BACKGROUND_INTENSITY | BACKGROUND_GREEN | BACKGROUND_BLUE;
case COLOR_LIGHTRED:
return BACKGROUND_INTENSITY | BACKGROUND_RED;
case COLOR_LIGHTPURPLE:
return BACKGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_BLUE;
case COLOR_LIGHTYELLOW:
return BACKGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_GREEN;
case COLOR_BRIGHTWHITE:
return BACKGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;
default:
return 0;
}
}
inline int getBackColor(MColor color) {
return color & 0xF0;
}
#endif // CONSTS_H

149
utils/RichText.h Normal file
View File

@ -0,0 +1,149 @@
#ifndef RICHTEXT_H
#define RICHTEXT_H
#include "Color.h"
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <windows.h>
struct StringPart {
std::string text;
MColor color;
StringPart(const std::string& t, const MColor& c) : text(t), color(c) {}
StringPart(const std::string& t) : text(t), color(getColor(COLOR_WHITE, COLOR_BLACK)) {}
};
class RichText {
private:
std::vector<StringPart> parts;
void print() const {
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
for(const auto& part : parts) {
std::cout << "[" << part.color << "] ";
SetConsoleTextAttribute(handle, BackgroundColorToWinColor(getBackColor(part.color)) | FrontColorToWinColor(getFrontColor(part.color)));
std::cout << part.text;
SetConsoleTextAttribute(handle, BackgroundColorToWinColor(COLOR_BLACK) | FrontColorToWinColor(COLOR_WHITE));
std::cout << std::endl;
}
}
void clearEmptyParts() {
parts.erase(std::remove_if(parts.begin(), parts.end(), [](const StringPart& part) {
return part.text.empty();
}), parts.end());
}
public:
// 默认构造函数,初始化为一段默认黑底白字的文本
RichText() {
parts.push_back({""});
}
// 重载运算符=
RichText& operator=(const RichText& other) {
parts = other.parts;
clearEmptyParts();
return *this;
}
RichText& operator=(const std::string& other) {
parts.clear();
parts.push_back({other});
clearEmptyParts();
return *this;
}
RichText& operator=(const StringPart& other) {
parts.clear();
parts.push_back(other);
clearEmptyParts();
return *this;
}
// 重载运算符+
RichText operator+(const RichText& other) const {
RichText result = *this;
result.parts.insert(result.parts.end(), other.parts.begin(), other.parts.end());
result.clearEmptyParts();
return result;
}
RichText operator+(const std::string& other) const {
RichText result = *this;
result.parts.push_back({other});
result.clearEmptyParts();
return result;
}
RichText operator+(const StringPart& other) const {
RichText result = *this;
result.parts.push_back(other);
result.clearEmptyParts();
return result;
}
// 重载运算符+=
RichText& operator+=(const RichText& other) {
parts.insert(parts.end(), other.parts.begin(), other.parts.end());
clearEmptyParts();
return *this;
}
RichText& operator+=(const std::string& other) {
parts.push_back({other});
clearEmptyParts();
return *this;
}
RichText& operator+=(const StringPart& other) {
parts.push_back(other);
clearEmptyParts();
return *this;
}
// substr()函数,保留原颜色
RichText substr(size_t start, size_t length) const {
RichText result;
int s = 0, i = 0;
while(s + parts[i].text.length() < start) {
s += parts[i].text.length();
i++;
}
int t_start = start - s;
int t_length = length;
while(t_length > 0 && i < parts.size()) {
if(t_start > 0) {
result += StringPart(parts[i].text.substr(t_start), parts[i].color);
t_length -= parts[i].text.length() - t_start;
t_start = 0;
} else {
if(t_length < parts[i].text.length()) {
result += StringPart(parts[i].text.substr(0, t_length), parts[i].color);
t_length = 0;
} else {
result += StringPart(parts[i].text, parts[i].color);
t_length -= parts[i].text.length();
}
}
i++;
}
result.clearEmptyParts();
// result.print();
return result;
}
std::vector<StringPart> getParts() const {
return parts;
}
// plainText()函数,返回连接后的文本内容,不包括颜色
std::string plainText() const {
std::string result;
for (const auto& part : parts) {
result += part.text;
}
return result;
}
};
#endif

55
utils/RichText_test.cpp Normal file
View File

@ -0,0 +1,55 @@
#include <iostream>
#include <cassert>
#include "RichText.h"
int main() {
// 测试默认构造函数
RichText rt1;
assert(rt1.plainText() == "");
// 测试赋值运算符=
RichText rt2;
rt2 = "Hello";
assert(rt2.plainText() == "Hello");
RichText rt3;
rt3 = StringPart{"World", COLOR_RED};
assert(rt3.plainText() == "World");
// 测试加法运算符+
RichText rt4 = rt2 + rt3;
assert(rt4.plainText() == "HelloWorld");
RichText rt5 = rt2 + "World";
assert(rt5.plainText() == "HelloWorld");
RichText rt6 = rt2 + StringPart{"World", COLOR_RED};
assert(rt6.plainText() == "HelloWorld");
// 测试加法赋值运算符+=
rt2 += rt3;
assert(rt2.plainText() == "HelloWorld");
rt2 += "World";
assert(rt2.plainText() == "HelloWorldWorld");
rt2 += StringPart{"World", COLOR_RED};
assert(rt2.plainText() == "HelloWorldWorldWorld");
// 测试substr()函数
RichText rt7 = rt2.substr(5, 5);
assert(rt7.plainText() == "World");
assert(rt7.getParts()[0].color == COLOR_RED);
// 测试text()函数
RichText rt8 = rt2;
assert(rt8.plainText() == "HelloWorldWorldWorld");
assert(rt8.getParts()[0].color == COLOR_WHITE);
// 测试plainText()函数
std::string plain = rt2.plainText();
assert(plain == "HelloWorldWorldWorld");
printf("All tests passed.\n");
return 0;
}