Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
70 KB
Referenced Files
None
Subscribers
None
diff --git a/src/GUITextArea.cpp b/src/GUITextArea.cpp
index b582eb6..0110d9c 100644
--- a/src/GUITextArea.cpp
+++ b/src/GUITextArea.cpp
@@ -1,1077 +1,1101 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Functions.h"
#include "UTF8Functions.h"
#include "GUITextArea.h"
#include "ThemeManager.h"
#include "WordWrapper.h"
#include <cmath>
#include <algorithm>
#include <assert.h>
#include <ctype.h>
#include <SDL_ttf_fontfallback.h>
using namespace std;
#define SPACE_PER_TAB 2
GUITextArea::GUITextArea(ImageManager& imageManager, SDL_Renderer& renderer,int left,int top,int width,int height,bool enabled,bool visible):
GUIObject(imageManager,renderer,left,top,width,height,NULL,-1,enabled,visible),editable(true){
//Set some default values.
state=0;
setFont(fontText);
highlightLineStart=highlightLineEnd=0;
highlightStartX=highlightEndX=0;
highlightStart=highlightEnd=0;
//Add empty text.
lines.push_back("");
linesCache.push_back(nullptr);
//Create scrollbar widget.
scrollBar=new GUIScrollBar(imageManager,renderer,width-16,0,16,height,1,0,0,0);
childControls.push_back(scrollBar);
scrollBarH=new GUIScrollBar(imageManager,renderer,0,height-16,width-16,16,0,0,0,0,100,500,true,false);
childControls.push_back(scrollBarH);
}
void GUITextArea::setFont(TTF_Font* font){
//NOTE: This fuction shouldn't be called after adding items, so no need to update the whole cache.
widgetFont=font;
fontHeight=TTF_FontHeight(font)+1;
}
void GUITextArea::inputText(SDL_Renderer &renderer, const char* s) {
if (s && s[0]) {
//Split into lines.
vector<string> newLines;
newLines.push_back(std::string());
for (int i = 0; s[i]; i++) {
if (s[i] == '\r') continue;
if (s[i] == '\n') {
newLines.push_back(std::string());
continue;
}
if (s[i] == '\t') {
// Replace tabs by spaces.
newLines.back() += std::string(SPACE_PER_TAB, ' ');
continue;
}
newLines.back().push_back(s[i]);
}
const int m = newLines.size();
if (m == 1 && newLines[0].empty()) return;
//Remove selected text.
removeHighlight(renderer);
//Calculate the width of the last line.
int advance = 0;
{
const char* lastLine = newLines[m - 1].c_str();
for (int i = 0;;) {
int a = 0;
int ch = utf8ReadForward(lastLine, i);
if (ch <= 0) break;
TTF_GlyphMetrics(widgetFont, ch, NULL, NULL, NULL, NULL, &a);
advance += a;
}
}
if (m > 1) {
//Multiple lines.
highlightEnd = newLines[m - 1].size();
highlightStartX = highlightEndX = advance;
newLines[m - 1] += lines[highlightLineStart].substr(highlightStart);
lines[highlightLineStart] = lines[highlightLineStart].substr(0, highlightStart) + newLines[0];
lines.insert(lines.begin() + (highlightLineStart + 1), newLines.begin() + 1, newLines.end());
for (int i = 0; i < m - 1; i++) {
linesCache.insert(linesCache.begin() + (highlightLineStart + 1), nullptr);
}
highlightStart = highlightEnd;
} else {
//Single line.
highlightEnd = highlightStart + newLines[0].size();
lines[highlightLineStart].insert(highlightStart, newLines[0]);
highlightStart = highlightEnd;
highlightStartX = highlightEndX = highlightStartX + advance;
}
//Update cache.
highlightLineEnd = highlightLineStart + m - 1;
for (int i = highlightLineStart; i <= highlightLineEnd; i++) {
linesCache[i] = textureFromText(renderer, *widgetFont, lines[i].c_str(), objThemes.getTextColor(true));
}
highlightLineStart = highlightLineEnd;
//Update view if needed.
adjustView();
//If there is an event callback then call it.
if (eventCallback){
GUIEvent e = { eventCallback, name, this, GUIEventChange };
GUIEventQueue.push_back(e);
}
}
}
void GUITextArea::scrollScrollbar(int dx, int dy) {
if (dx && scrollBarH->visible){
scrollBarH->value = clamp(scrollBarH->value + dx, 0, scrollBarH->maxValue);
}
if (dy) {
scrollBar->value = clamp(scrollBar->value + dy, 0, scrollBar->maxValue);
}
}
bool GUITextArea::handleEvents(SDL_Renderer& renderer,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The GUIObject is only enabled when he and his parent are enabled.
enabled=enabled && this->enabled;
//The GUIObject is only enabled when he and his parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left;
y+=top;
//Update the scrollbars.
b = b || scrollBar->handleEvents(renderer, x, y, enabled, visible, b);
b = b || scrollBarH->handleEvents(renderer, x, y, enabled, visible, b);
//NOTE: We don't reset the state to have a "focus" effect.
//Only check for events when the object is both enabled and visible.
if(enabled&&visible){
//Check if there's a key press and the event hasn't been already processed.
if(state==2 && event.type==SDL_KEYDOWN && !b && editable){
if ((event.key.keysym.mod & KMOD_CTRL) == 0) {
//Check if the key is supported.
if (event.key.keysym.sym == SDLK_BACKSPACE){
//Delete one character direct to prevent a lag.
backspaceChar(renderer);
} else if (event.key.keysym.sym == SDLK_DELETE){
//Delete one character direct to prevent a lag.
deleteChar(renderer);
} else if (event.key.keysym.sym == SDLK_RETURN){
removeHighlight(renderer);
//Split the current line and update.
string str2 = lines.at(highlightLineEnd).substr(highlightStart);
lines.at(highlightLineStart) = lines.at(highlightLineStart).substr(0, highlightStart);
linesCache.at(highlightLineStart) =
textureFromText(renderer, *widgetFont, lines.at(highlightLineStart).c_str(), objThemes.getTextColor(true));
//Calculate indentation.
int indent = 0;
for (int i = 0; i < (int)lines.at(highlightLineStart).length(); i++){
if (isspace(lines.at(highlightLineStart)[i]))
indent++;
else
break;
}
str2.insert(0, indent, ' ');
//Add the rest in a new line.
highlightLineStart++;
highlightStart = indent;
highlightEnd = highlightStart;
highlightLineEnd++;
highlightStartX = 0;
for (int i = 0; i < indent; i++){
int advance;
TTF_GlyphMetrics(widgetFont, str2.at(i), NULL, NULL, NULL, NULL, &advance);
highlightStartX += advance;
}
highlightEndX = highlightStartX;
lines.insert(lines.begin() + highlightLineStart, str2);
auto tex = textureFromText(renderer, *widgetFont, str2.c_str(), objThemes.getTextColor(true));
linesCache.insert(linesCache.begin() + highlightLineStart, std::move(tex));
adjustView();
//If there is an event callback then call it.
if (eventCallback){
GUIEvent e = { eventCallback, name, this, GUIEventChange };
GUIEventQueue.push_back(e);
}
} else if (event.key.keysym.sym == SDLK_TAB){
//Calculate the width of a space.
int advance;
TTF_GlyphMetrics(widgetFont, ' ', NULL, NULL, NULL, NULL, &advance);
int start = highlightLineStart, end = highlightLineEnd;
if (start > end) std::swap(start, end);
for (int line = start; line <= end; line++) {
int count = 0;
std::string &s = lines[line];
if (event.key.keysym.mod & KMOD_SHIFT) {
// remove spaces
for (; count < SPACE_PER_TAB; count++) {
if (s.c_str()[count] != ' ') break;
}
if (count > 0) {
s.erase(0, count);
count = -count;
}
} else {
// add spaces
count = SPACE_PER_TAB;
s.insert(0, count, ' ');
}
//Update cache.
if (count) {
linesCache.at(line) = textureFromText(renderer, *widgetFont, s.c_str(), objThemes.getTextColor(true));
}
//Update selection.
if (line == highlightLineStart) {
highlightStart += count;
highlightStartX += count*advance;
if (highlightStart <= 0) {
highlightStart = 0;
highlightStartX = 0;
}
}
if (line == highlightLineEnd) {
highlightEnd += count;
highlightEndX += count*advance;
if (highlightEnd <= 0) {
highlightEnd = 0;
highlightEndX = 0;
}
}
}
adjustView();
} else if (event.key.keysym.sym == SDLK_RIGHT){
//Move the carrot once to prevent a lag.
moveCarrotRight();
} else if (event.key.keysym.sym == SDLK_LEFT){
//Move the carrot once to prevent a lag.
moveCarrotLeft();
} else if (event.key.keysym.sym == SDLK_DOWN){
//Move the carrot once to prevent a lag.
moveCarrotDown();
} else if (event.key.keysym.sym == SDLK_UP){
//Move the carrot once to prevent a lag.
moveCarrotUp();
}
} else {
//Check hotkey.
if (event.key.keysym.sym == SDLK_a) {
//Select all.
highlightLineStart = 0;
highlightStart = 0;
highlightStartX = 0;
highlightLineEnd = lines.size() - 1;
highlightEnd = lines.back().size();
highlightEndX = 0;
if (highlightEnd > 0) {
TTF_SizeUTF8(widgetFont, lines.back().c_str(), &highlightEndX, NULL);
}
} else if (event.key.keysym.sym == SDLK_x || event.key.keysym.sym == SDLK_c) {
//Cut or copy.
int startLine = highlightLineStart, endLine = highlightLineEnd;
int start = highlightStart, end = highlightEnd;
if (startLine > endLine || (startLine == endLine && start > end)) {
std::swap(startLine, endLine);
std::swap(start, end);
}
std::string s;
if (startLine < endLine) {
//Multiple lines.
s = lines[startLine].substr(start);
s.push_back('\n');
for (int i = startLine + 1; i < endLine; i++) {
s += lines[i];
s.push_back('\n');
}
s += lines[endLine].substr(0, end);
} else {
//Single line.
s = lines[startLine].substr(start, end - start);
}
if (!s.empty()) {
SDL_SetClipboardText(s.c_str());
if (event.key.keysym.sym == SDLK_x) {
//Cut.
removeHighlight(renderer);
//If there is an event callback then call it.
if (eventCallback){
GUIEvent e = { eventCallback, name, this, GUIEventChange };
GUIEventQueue.push_back(e);
}
}
}
} else if (event.key.keysym.sym == SDLK_v) {
//Paste.
if (SDL_HasClipboardText()) {
char *s = SDL_GetClipboardText();
inputText(renderer, s);
SDL_free(s);
}
}
}
//The event has been processed.
b=true;
} else if (state == 2 && event.type == SDL_TEXTINPUT && !b && editable){
inputText(renderer, event.text.text);
} else if (state == 2 && event.type == SDL_TEXTEDITING && !b && editable){
// TODO: process SDL_TEXTEDITING event
}
if (event.type == SDL_MOUSEMOTION || event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEWHEEL) {
//The mouse location (x=i, y=j) and the mouse button (k).
int i, j, k;
k = SDL_GetMouseState(&i, &j);
//Check if the mouse is inside the GUIObject.
if (i >= x && i < x + width && j >= y && j < y + height && !b){
//We can only increase our state. (nothing->hover->focus).
if (state != 2){
state = 1;
}
//Check for mouse wheel scrolling.
//Scroll horizontally if mouse is over the horizontal scrollbar.
//Otherwise scroll vertically.
if (event.type == SDL_MOUSEWHEEL && event.wheel.y) {
if (j >= y + height - 16 && scrollBarH->visible){
scrollScrollbar(event.wheel.y < 0 ? 20 : -20, 0);
} else{
scrollScrollbar(0, event.wheel.y < 0 ? 1 : -1);
}
}
//When mouse is not over the scrollbar.
if (i < x + width - 16 && j < (scrollBarH->visible ? y + height - 16 : y + height)){
if (editable) {
//Update the cursor type.
currentCursor = CURSOR_CARROT;
if (((event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) && event.button.button == 1)
|| (event.type == SDL_MOUSEMOTION && (k & SDL_BUTTON(1))))
{
//Move carrot to the place clicked.
const int mouseLine = clamp((int)floor(float(j - y) / float(fontHeight)) + scrollBar->value, 0, lines.size() - 1);
string* str = &lines.at(mouseLine);
value = str->length();
const int clickX = i - x + scrollBarH->value;
int finalX = 0;
int finalPos = str->length();
for (int i = 0;;){
int advance = 0;
int i0 = i;
int ch = utf8ReadForward(str->c_str(), i);
if (ch <= 0) break;
TTF_GlyphMetrics(widgetFont, ch, NULL, NULL, NULL, NULL, &advance);
finalX += advance;
if (clickX < finalX - advance / 2){
finalPos = i0;
finalX -= advance;
break;
}
}
if (event.type == SDL_MOUSEBUTTONUP){
state = 2;
highlightEnd = finalPos;
highlightEndX = finalX;
highlightLineEnd = mouseLine;
} else if (event.type == SDL_MOUSEBUTTONDOWN){
state = 2;
highlightStart = highlightEnd = finalPos;
highlightStartX = highlightEndX = finalX;
highlightLineStart = highlightLineEnd = mouseLine;
} else if (event.type == SDL_MOUSEMOTION){
state = 2;
highlightEnd = finalPos;
highlightEndX = finalX;
highlightLineEnd = mouseLine;
}
}
} else {
const int mouseLine = (int)floor(float(j - y) / float(fontHeight)) + scrollBar->value;
if (mouseLine >= 0 && mouseLine < (int)hyperlinks.size()) {
const int clickX = i - x + scrollBarH->value;
for (const Hyperlink& lnk : hyperlinks[mouseLine]) {
if (clickX >= lnk.startX && clickX < lnk.endX) {
currentCursor = CURSOR_POINTING_HAND;
if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == 1) {
- openWebsite(lnk.url);
+ if (lnk.url.size() >= 8 && (lnk.url.substr(0, 7) == "http://" || lnk.url.substr(0, 8) == "https://")) {
+ openWebsite(lnk.url);
+ } else if (eventCallback) {
+ clickedHyperlink = lnk.url;
+ GUIEvent e = { eventCallback, name, this, GUIEventClick };
+ GUIEventQueue.push_back(e);
+ }
}
break;
}
}
}
}
}
//Event has been processed as long as this is a mouse event and the mouse is inside the widget.
b = true;
} else{
//The mouse is outside the TextBox.
//If we don't have focus but only hover we lose it.
if (state == 1){
state = 0;
}
//If it's a click event outside the textbox then we blur.
if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT){
//Set state to 0.
state = 0;
}
}
}
}
if(!editable)
highlightLineStart=scrollBar->value;
//Process child controls event except for the scrollbars.
//That's why i ends at 2.
for (int i = childControls.size() - 1; i >= 2; i--) {
bool b1 = childControls[i]->handleEvents(renderer, x, y, enabled, visible, b);
//The event is processed when either our or the childs is true (or both).
b=b||b1;
}
return b;
}
void GUITextArea::removeHighlight(SDL_Renderer& renderer){
if (highlightLineStart==highlightLineEnd) {
if (highlightStart == highlightEnd) return;
int start=highlightStart, end=highlightEnd, startx=highlightStartX;
if(highlightStart>highlightEnd){
start=highlightEnd;
end=highlightStart;
startx=highlightEndX;
}
std::string& str=lines.at(highlightLineStart);
str.erase(start,end-start);
highlightStart=highlightEnd=start;
highlightStartX=highlightEndX=startx;
// Update cache.
linesCache.at(highlightLineStart) = textureFromText(renderer,*widgetFont,str.c_str(),objThemes.getTextColor(true));
}else{
int startLine=highlightLineStart, endLine=highlightLineEnd,
start=highlightStart, end=highlightEnd, startx=highlightStartX;
if(startLine>endLine){
startLine=highlightLineEnd;
endLine=highlightLineStart;
start=highlightEnd;
end=highlightStart;
startx=highlightEndX;
}
lines[startLine] = lines[startLine].substr(0, start) + lines[endLine].substr(end);
lines.erase(lines.begin() + startLine + 1, lines.begin() + endLine + 1);
linesCache.erase(linesCache.begin() + startLine + 1, linesCache.begin() + endLine + 1);
highlightLineStart=highlightLineEnd=startLine;
highlightStart=highlightEnd=start;
highlightStartX=highlightEndX=startx;
// Update cache.
linesCache.at(startLine) = textureFromText(renderer, *widgetFont, lines[startLine].c_str(), objThemes.getTextColor(true));
}
adjustView();
}
void GUITextArea::deleteChar(SDL_Renderer& renderer){
if (highlightLineStart==highlightLineEnd && highlightStart==highlightEnd){
if(highlightEnd>=(int)lines.at(highlightLineEnd).length()){
if(highlightLineEnd<(int)lines.size()-1){
highlightLineEnd++;
highlightEnd=0;
}
} else {
utf8ReadForward(lines.at(highlightLineEnd).c_str(), highlightEnd);
}
}
removeHighlight(renderer);
//If there is an event callback.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
void GUITextArea::backspaceChar(SDL_Renderer& renderer){
if(highlightLineStart==highlightLineEnd && highlightStart==highlightEnd){
if(highlightStart<=0){
if(highlightLineStart==0){
highlightStart=0;
}else{
highlightLineStart--;
highlightStart=lines.at(highlightLineStart).length();
highlightStartX=0;
if (highlightStart > 0) {
TexturePtr& t = linesCache.at(highlightLineStart);
if (t) highlightStartX = textureWidth(*t);
}
}
}else{
int advance = 0;
int ch = utf8ReadBackward(lines.at(highlightLineStart).c_str(), highlightStart);
if (ch > 0) TTF_GlyphMetrics(widgetFont, ch, NULL, NULL, NULL, NULL, &advance);
highlightStartX -= advance;
}
}
removeHighlight(renderer);
//If there is an event callback.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
void GUITextArea::moveCarrotRight(){
if (highlightEnd>=(int)lines.at(highlightLineEnd).length()){
if (highlightLineEnd<(int)lines.size()-1){
highlightEnd=0;
highlightEndX=0;
highlightLineEnd++;
}
}else{
int advance = 0;
int ch = utf8ReadForward(lines.at(highlightLineEnd).c_str(), highlightEnd);
if (ch > 0) TTF_GlyphMetrics(widgetFont, ch, NULL, NULL, NULL, NULL, &advance);
highlightEndX += advance;
}
if((SDL_GetModState()&KMOD_SHIFT)==0){
highlightLineStart=highlightLineEnd;
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
}
adjustView();
}
void GUITextArea::moveCarrotLeft(){
if (highlightEnd<=0){
if (highlightLineEnd==0){
highlightEnd=0;
}else{
highlightLineEnd--;
highlightEnd=lines.at(highlightLineEnd).length();
highlightEndX=0;
if (highlightEnd > 0) {
TexturePtr& t = linesCache.at(highlightLineEnd);
if (t) highlightEndX = textureWidth(*t);
}
}
}else{
int advance = 0;
int ch = utf8ReadBackward(lines.at(highlightLineEnd).c_str(), highlightEnd);
if (ch > 0) TTF_GlyphMetrics(widgetFont, ch, NULL, NULL, NULL, NULL, &advance);
highlightEndX -= advance;
}
if((SDL_GetModState()&KMOD_SHIFT)==0){
highlightLineStart=highlightLineEnd;
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
}
adjustView();
}
void GUITextArea::moveCarrotUp(){
if(highlightLineEnd==0){
highlightEnd=0;
highlightEndX=0;
}else{
highlightLineEnd--;
const std::string& str=lines.at(highlightLineEnd);
//Find out closest match.
int xPos=0;
int i=0;
for (;;){
int advance = 0;
int i0 = i;
int ch = utf8ReadForward(str.c_str(), i);
if (ch <= 0) break;
TTF_GlyphMetrics(widgetFont, ch, NULL, NULL, NULL, NULL, &advance);
xPos += advance;
if(highlightEndX<xPos-advance/2){
highlightEnd=i=i0;
highlightEndX=xPos-advance;
break;
}
}
if (i == 0) {
highlightEnd = highlightEndX = 0;
} else if (i == str.length()){
highlightEnd=str.length();
highlightEndX=0;
if (highlightEnd > 0) {
TexturePtr& t = linesCache.at(highlightLineEnd);
if (t) highlightEndX = textureWidth(*t);
}
}
}
if((SDL_GetModState()&KMOD_SHIFT)==0){
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
highlightLineStart=highlightLineEnd;
}
adjustView();
}
void GUITextArea::moveCarrotDown(){
if(highlightLineEnd==lines.size()-1){
highlightEnd=lines.at(highlightLineEnd).length();
highlightEndX=0;
if (highlightEnd > 0) {
TexturePtr& t = linesCache.at(highlightLineEnd);
if (t) highlightEndX = textureWidth(*t);
}
}else{
highlightLineEnd++;
string* str=&lines.at(highlightLineEnd);
//Find out closest match.
int xPos=0;
int i = 0;
for (;;){
int advance = 0;
int i0 = i;
int ch = utf8ReadForward(str->c_str(), i);
if (ch <= 0) break;
TTF_GlyphMetrics(widgetFont, ch, NULL, NULL, NULL, NULL, &advance);
xPos += advance;
if (highlightEndX<xPos - advance / 2){
highlightEnd = i = i0;
highlightEndX = xPos - advance;
break;
}
}
if (i == 0) {
highlightEnd = highlightEndX = 0;
} else if (i == str->length()){
highlightEnd=str->length();
highlightEndX=0;
TexturePtr& t = linesCache.at(highlightLineEnd);
if(t) highlightEndX=textureWidth(*t);
}
}
if((SDL_GetModState()&KMOD_SHIFT)==0){
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
highlightLineStart=highlightLineEnd;
}
adjustView();
}
void GUITextArea::adjustView(){
//Adjust view to current line.
if(fontHeight*(highlightLineEnd-scrollBar->value)+4>height-4)
scrollBar->value=highlightLineEnd-3;
else if(highlightLineEnd-scrollBar->value<0)
scrollBar->value=highlightLineEnd;
//Find out the lenght of the longest line.
int maxWidth=0;
for(const TexturePtr& tex: linesCache){
if(tex) {
const int texWidth = textureWidth(*tex.get());
if(texWidth>width-16&&texWidth>maxWidth)
maxWidth=texWidth;
}
}
//We need the horizontal scrollbar if any line is too long.
if(maxWidth>0){
scrollBar->height=height-16;
scrollBarH->visible=true;
scrollBarH->maxValue=maxWidth-width+24;
}else{
scrollBar->height=height;
scrollBarH->visible=false;
scrollBarH->value=0;
scrollBarH->maxValue=0;
}
//Adjust the horizontal view.
int carrotX=0;
for(int n=0;n<highlightEnd;){
int advance = 0;
int ch = utf8ReadForward(lines.at(highlightLineEnd).c_str(), n);
if (ch <= 0) break;
TTF_GlyphMetrics(widgetFont, ch, NULL, NULL, NULL, NULL, &advance);
carrotX += advance;
}
if(carrotX>width-24)
scrollBarH->value=scrollBarH->maxValue;
else
scrollBarH->value=0;
//Update vertical scrollbar.
int rh=height-(scrollBarH->visible?16:0);
int m=lines.size(),n=(int)floor((float)rh/(float)fontHeight);
if(m>n){
scrollBar->maxValue=m-n;
scrollBar->smallChange=1;
scrollBar->largeChange=n;
}else{
scrollBar->value=0;
scrollBar->maxValue=0;
}
}
void GUITextArea::drawHighlight(SDL_Renderer& renderer, int x,int y,SDL_Rect r,SDL_Color color){
if(r.x<x) {
int tmp_w = r.w - x + r.x;
if(tmp_w<=0) return;
r.w = tmp_w;
r.x = x;
}
if(r.x+r.w > x+width){
int tmp_w=width-r.x+x;
if(tmp_w<=0) return;
r.w=tmp_w;
}
if(r.y<y){
int tmp_h=r.h - y + r.y;
if(tmp_h<=0) return;
r.h=tmp_h;
r.y = y;
}
if(r.y+r.h > y+height){
int tmp_h=height-r.y+y;
if(tmp_h<=0) return;
r.h=tmp_h;
}
SDL_SetRenderDrawColor(&renderer, color.r, color.g, color.b, color.a);
SDL_RenderFillRect(&renderer, &r);
}
void GUITextArea::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the GUIObject when it's invisible.
if(!visible||!draw)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
{
//Draw the box.
const Uint32 color=0xFFFFFFFF;
drawGUIBox(x,y,width,height,renderer,color);
}
//Place the highlighted area.
SDL_Rect r;
const SDL_Color color{128,128,128,255};
if (editable) {
if (highlightLineStart == highlightLineEnd){
r.x = x - scrollBarH->value;
r.y = y + ((highlightLineStart - scrollBar->value)*fontHeight);
r.h = fontHeight;
if (highlightStart < highlightEnd){
r.x += highlightStartX;
r.w = highlightEndX - highlightStartX;
} else{
r.x += highlightEndX;
r.w = highlightStartX - highlightEndX;
}
drawHighlight(renderer, x, y, r, color);
} else if (highlightLineStart < highlightLineEnd){
int lnc = highlightLineEnd - highlightLineStart;
for (int i = 0; i <= lnc; i++){
r.x = x - scrollBarH->value;
r.y = y + ((i + highlightLineStart - scrollBar->value)*fontHeight);
r.w = width + scrollBarH->maxValue;
r.h = fontHeight;
if (i == 0){
r.x += highlightStartX;
r.w -= highlightStartX;
} else if (i == lnc){
r.w = highlightEndX;
}
if (lines.at(i + highlightLineStart).empty()){
r.w = fontHeight / 4;
}
drawHighlight(renderer, x, y, r, color);
}
} else{
int lnc = highlightLineStart - highlightLineEnd;
for (int i = 0; i <= lnc; i++){
r.x = x - scrollBarH->value;
r.y = y + ((i + highlightLineEnd - scrollBar->value)*fontHeight);
r.w = width + scrollBarH->maxValue;
r.h = fontHeight;
if (i == 0){
r.x += highlightEndX;
r.w -= highlightEndX;
} else if (i == lnc){
r.w = highlightStartX;
}
if (lines.at(i + highlightLineEnd).empty()){
r.w = fontHeight / 4;
}
drawHighlight(renderer, x, y, r, color);
}
}
}
const int underlineOffset = TTF_FontAscent(widgetFont) + int(float(0.5f - 0.25f * TTF_FontDescent(widgetFont)));
//Draw text.
int lineY=0;
for(int line=scrollBar->value;line<(int)linesCache.size();line++){
TexturePtr& it = linesCache[line];
if(it){
if(lineY<height){
SDL_Rect r = { scrollBarH->value, 0, std::min(width - 17, textureWidth(*it.get()) - scrollBarH->value), textureHeight(*it.get()) };
int over=-height+lineY+fontHeight;
if(over>0) r.h-=over;
const SDL_Rect dstRect={x+1,y+1+lineY,r.w,r.h};
if(r.w>0 && r.h>0) SDL_RenderCopy(&renderer,it.get(),&r,&dstRect);
// draw hyperlinks
if (!editable && line<(int)hyperlinks.size()) {
r.y = lineY + underlineOffset;
if (r.y < height){
r.y += y + 1;
r.h = 1;
for (const Hyperlink& lnk : hyperlinks[line]) {
r.x = clamp(lnk.startX - scrollBarH->value, 0, width - 17);
r.w = clamp(lnk.endX - scrollBarH->value, 0, width - 17);
if (r.w > r.x) {
r.w -= r.x;
r.x += x + 1;
SDL_SetRenderDrawColor(&renderer, 0, 0, 0, 255);
SDL_RenderFillRect(&renderer, &r);
}
}
}
}
}else{
break;
}
}
lineY+=fontHeight;
}
//Only draw the carrot when focus.
if(state==2&&editable){
r.x=x-scrollBarH->value+highlightEndX;
r.y=y+4+fontHeight*(highlightLineEnd-scrollBar->value);
r.w=2;
r.h=fontHeight-4;
//Make sure that the carrot is inside the textbox.
if((r.y<y+height-4)&&(r.y>y)&&(r.x>x-1)&&(r.x<x+width-16)){
drawHighlight(renderer,x,y,r,SDL_Color{0,0,0,127});
}
}
//We now need to draw all the children of the GUIObject.
for(unsigned int i=0;i<childControls.size();i++){
childControls[i]->render(renderer,x,y,draw);
}
}
void GUITextArea::setString(SDL_Renderer& renderer, const std::string& input, bool wordWrap) {
WordWrapper wrapper;
wrapper.wordWrap = wordWrap;
setString(renderer, input, wrapper);
}
void GUITextArea::setString(SDL_Renderer& renderer, const std::string& input, WordWrapper& wrapper) {
//Clear previous content if any.
//Delete every line.
lines.clear();
linesCache.clear();
//Copy values.
wrapper.maxWidth = width - 16;
wrapper.font = widgetFont;
wrapper.addString(lines, input);
//Render and cache text.
for (const std::string& s : lines) {
linesCache.push_back(textureFromText(renderer, *widgetFont, s.c_str(), objThemes.getTextColor(true)));
}
adjustView();
}
void GUITextArea::setStringArray(SDL_Renderer& renderer, const std::vector<std::string>& input, bool wordWrap) {
WordWrapper wrapper;
wrapper.wordWrap = wordWrap;
setStringArray(renderer, input, wrapper);
}
void GUITextArea::setStringArray(SDL_Renderer& renderer, const std::vector<std::string>& input, WordWrapper& wrapper) {
//Free cached images.
linesCache.clear();
lines.clear();
//Copy values.
wrapper.maxWidth = width - 16;
wrapper.font = widgetFont;
wrapper.addLines(lines, input);
//Render and cache text.
for(const std::string& s: lines) {
linesCache.push_back(textureFromText(renderer,*widgetFont,s.c_str(),objThemes.getTextColor(true)));
}
adjustView();
}
void GUITextArea::setStringArray(SDL_Renderer &renderer, std::vector<SurfacePtr>& surfaces) {
//Free cached images.
linesCache.clear();
lines.clear();
//Copy values.
lines.resize(surfaces.size());
for (SurfacePtr& surface : surfaces) {
linesCache.emplace_back(SDL_CreateTextureFromSurface(&renderer, surface.get()));
}
adjustView();
}
void GUITextArea::extractHyperlinks() {
const int lm = lines.size();
hyperlinks.clear();
if (lm <= 0) return;
hyperlinks.resize(lm);
for (int l = 0; l < lm; l++) {
const char* s = lines[l].c_str();
for (int i = 0, m = lines[l].size(); i < m; i++) {
const int lps = i;
std::string url;
// we only support http or https
if ((s[i] == 'H' || s[i] == 'h')
&& (s[i + 1] == 'T' || s[i + 1] == 't')
&& (s[i + 2] == 'T' || s[i + 2] == 't')
&& (s[i + 3] == 'P' || s[i + 3] == 'p'))
{
if (s[i + 4] == ':' && s[i + 5] == '/' && s[i + 6] == '/') {
// http
i += 7;
url = "http://";
} else if ((s[i + 4] == 'S' || s[i + 4] == 's') && s[i + 5] == ':' && s[i + 6] == '/' && s[i + 7] == '/') {
// https
i += 8;
url = "https://";
} else {
continue;
}
for (; i < m; i++) {
char c = s[i];
// url ends with following character
if (c == '\0' || c == ' ' || c == ')' || c == ']' || c == '}' || c == '>' || c == '\r' || c == '\n' || c == '\t') {
break;
}
url.push_back(c);
}
} else {
continue;
}
const int lpe = i;
Hyperlink hyperlink = {};
TTF_SizeUTF8(widgetFont, lines[l].substr(0, lps).c_str(), &hyperlink.startX, NULL);
TTF_SizeUTF8(widgetFont, lines[l].substr(0, lpe).c_str(), &hyperlink.endX, NULL);
hyperlink.url = lines[l].substr(lps, lpe - lps);
hyperlinks[l].push_back(hyperlink);
}
}
}
+void GUITextArea::setHyperlinks(const std::vector<Hyperlink2>& links) {
+ hyperlinks.clear();
+ addHyperlinks(links);
+}
+
+void GUITextArea::addHyperlinks(const std::vector<Hyperlink2>& links) {
+ const int lm = lines.size();
+
+ if (lm <= 0) return;
+ hyperlinks.resize(lm);
+
+ for (const Hyperlink2& link : links) {
+ if (link.line >= 0 && link.line < lm) {
+ hyperlinks[link.line].push_back(Hyperlink{ link.startX, link.endX, link.url });
+ }
+ }
+}
+
string GUITextArea::getString(){
string tmp;
for(vector<string>::iterator it=lines.begin();it!=lines.end();++it){
//Append a newline only if not the first line.
if(it!=lines.begin())
tmp.append(1,'\n');
//Append the line.
tmp.append(*it);
}
return tmp;
}
void GUITextArea::onResize(){
scrollBar->left=width-16;
scrollBar->height=height;
if(scrollBarH->visible)
scrollBar->height-=16;
scrollBarH->top=height-16;
scrollBarH->width=width-16;
adjustView();
}
diff --git a/src/GUITextArea.h b/src/GUITextArea.h
index 85fd293..a0174a8 100644
--- a/src/GUITextArea.h
+++ b/src/GUITextArea.h
@@ -1,141 +1,159 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GUITEXTAREA_H
#define GUITEXTAREA_H
#include "GUIObject.h"
#include "GUIScrollBar.h"
class WordWrapper;
//Widget for multiline text input.
class GUITextArea:public GUIObject{
private:
//Method that will remove the last character of the text.
void backspaceChar(SDL_Renderer &renderer);
void deleteChar(SDL_Renderer &renderer);
//Methods to move the carrot by one character/line.
void moveCarrotLeft();
void moveCarrotRight();
void moveCarrotUp();
void moveCarrotDown();
// Remove all highlighted text.
void removeHighlight(SDL_Renderer &renderer);
// Input new text.
void inputText(SDL_Renderer &renderer, const char* s);
//Method to adjust view so carrot stays visible.
void adjustView();
//Pointer to the font used in the widget.
TTF_Font* widgetFont;
//Widget's text.
//One line per vector element.
std::vector<std::string> lines;
//Cache for rendered lines.
//Will be updated alongside with variable text.
std::vector<TexturePtr> linesCache;
//Variable for carrot position.
int highlightLineStart;
int highlightLineEnd;
int highlightStart;
int highlightStartX;
int highlightEnd;
int highlightEndX;
//Height of the font.
int fontHeight;
//Scrollbar widget.
GUIScrollBar* scrollBar;
GUIScrollBar* scrollBarH;
//A struct to save hyperlink.
struct Hyperlink {
int startX, endX;
std::string url;
};
//Hyperlinks.
std::vector<std::vector<Hyperlink> > hyperlinks;
void drawHighlight(SDL_Renderer& renderer, int x, int y, SDL_Rect r, SDL_Color color);
public:
+ //Another struct to save hyperlink.
+ struct Hyperlink2 {
+ int line, startX, endX;
+ std::string url;
+ };
+
//Constructor.
//left: The relative x location of the GUITextArea.
//top: The relative y location of the GUITextArea.
//witdh: The width of the GUITextArea.
//height: The height of the GUITextArea.
//enabled: Boolean if the GUITextArea is enabled or not.
//visible: Boolean if the GUITextArea is visisble or not.
GUITextArea(ImageManager& imageManager, SDL_Renderer& renderer,int left=0,int top=0,int width=0,int height=0,bool enabled=true,bool visible=true);
//Method used to change the font.
//font: Pointer to the font
void setFont(TTF_Font* font);
//Method used to reposition scrollbars after a resize.
void onResize() override;
//Method used to get widget's text in a single string.
std::string getString();
//Method used to set widget's text.
//NOTE: wordWrap will actually change the text saved in the text area!
void setString(SDL_Renderer& renderer, const std::string& input, bool wordWrap = false);
void setStringArray(SDL_Renderer &renderer, const std::vector<std::string>& input, bool wordWrap = false);
void setString(SDL_Renderer& renderer, const std::string& input, WordWrapper& wrapper);
void setStringArray(SDL_Renderer &renderer, const std::vector<std::string>& input, WordWrapper& wrapper);
void setStringArray(SDL_Renderer &renderer, std::vector<SurfacePtr>& surfaces);
//Extract hyperlinks from text.
//Currently only http and https links are extracted.
void extractHyperlinks();
-
+
+ //Set hyperlinks from a list.
+ void setHyperlinks(const std::vector<Hyperlink2>& links);
+
+ //Add hyperlinks from a list.
+ void addHyperlinks(const std::vector<Hyperlink2>& links);
+
//Bool if user can edit text in the widget.
bool editable;
//Method used to handle mouse and/or key events.
//x: The x mouse location.
//y: The y mouse location.
//enabled: Boolean if the parent is enabled or not.
//visible: Boolean if the parent is visible or not.
//processed: Boolean if the event has been processed (by the parent) or not.
//Returns: Boolean if the event is processed by the child.
virtual bool handleEvents(SDL_Renderer&renderer, int x=0, int y=0, bool enabled=true, bool visible=true, bool processed=false);
//Method that will render the GUITextArea.
//x: The x location to draw the GUITextArea. (x+left)
//y: The y location to draw the GUITextArea. (y+top)
virtual void render(SDL_Renderer &renderer, int x=0, int y=0, bool draw=true);
//Scroll the scrollbar.
//dx: horizontal scroll (in pixels), typically multiple of 20
//dy: vertical scroll (in lines)
void scrollScrollbar(int dx, int dy);
+
+ //The URL of clicked hyperlink.
+ //This only makes sense when you received a GUIEventClick event.
+ //NOTE: the widgets will process URL begins with http:// or https:// (NOTE: case sensitive) automatically
+ //and you will not receive the event in these cases.
+ std::string clickedHyperlink;
};
#endif
diff --git a/src/HelpManager.cpp b/src/HelpManager.cpp
index 9d58e16..3ccbbdc 100644
--- a/src/HelpManager.cpp
+++ b/src/HelpManager.cpp
@@ -1,1005 +1,1051 @@
/*
* Copyright (C) 2019 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "HelpManager.h"
#include "Functions.h"
#include "GUIWindow.h"
#include "GUIListBox.h"
#include "GUITextArea.h"
#include "WordWrapper.h"
#include "ThemeManager.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <stdio.h>
#include "SDL_ttf_fontfallback.h"
class Chunk {
public:
int cachedMaxWidth, cachedWidth, cachedNumberOfLines;
public:
Chunk() : cachedMaxWidth(-1), cachedWidth(-1), cachedNumberOfLines(-1) {}
virtual ~Chunk() {}
void updateSize(int maxWidth) {
if (maxWidth != cachedMaxWidth) updateSizeForced(maxWidth);
}
virtual void updateSizeForced(int maxWidth) = 0;
- virtual void createSurfaces(std::vector<SurfacePtr>& surfaces) = 0;
+ virtual void createSurfaces(std::vector<SurfacePtr>& surfaces, std::vector<GUITextArea::Hyperlink2>& links) = 0;
};
class ParagraphChunk : public Chunk {
public:
std::string text;
std::vector<std::string> cachedLines;
virtual void updateSizeForced(int maxWidth) override {
WordWrapper wrapper;
wrapper.font = fontText;
wrapper.wordWrap = true;
wrapper.maxWidth = maxWidth;
wrapper.hyphen = "-";
wrapper.hyphenatorLanguage = "en";
+ wrapper.reservedFragments.insert("/");
//TODO: verbatim support
cachedLines.clear();
cachedMaxWidth = maxWidth;
cachedWidth = wrapper.addString(cachedLines, text);
cachedNumberOfLines = cachedLines.size();
}
- virtual void createSurfaces(std::vector<SurfacePtr>& surfaces) override {
+ virtual void createSurfaces(std::vector<SurfacePtr>& surfaces, std::vector<GUITextArea::Hyperlink2>& links) override {
SDL_Color fg = objThemes.getTextColor(true);
for (const std::string& s : cachedLines) {
surfaces.emplace_back(TTF_RenderUTF8_Blended(fontText, s.c_str(), fg));
}
+
+ //TODO: extract hyperlinks
}
};
class ItemizeChunk : public Chunk {
public:
std::vector<Chunk*> items;
virtual ~ItemizeChunk() {
for (auto item : items) {
delete item;
}
}
virtual void updateSizeForced(int maxWidth) override {
cachedMaxWidth = maxWidth;
cachedWidth = 0;
cachedNumberOfLines = 0;
for (auto item : items) {
if (item) {
item->updateSize(maxWidth - 16);
cachedWidth = std::max(item->cachedWidth, cachedWidth);
cachedNumberOfLines += item->cachedNumberOfLines;
}
}
cachedWidth += 16;
}
- virtual void createSurfaces(std::vector<SurfacePtr>& surfaces) override {
+ virtual void createSurfaces(std::vector<SurfacePtr>& surfaces, std::vector<GUITextArea::Hyperlink2>& links) override {
auto bmGUI = getImageManager().loadImage(getDataPath() + "gfx/gui.png");
for (auto item : items) {
if (item) {
std::vector<SurfacePtr> tempSurfaces;
- item->createSurfaces(tempSurfaces);
+ const size_t oldLineCount = surfaces.size();
+ const size_t oldLinkSize = links.size();
+ item->createSurfaces(tempSurfaces, links);
for (int i = 0, m = tempSurfaces.size(); i < m; i++) {
SDL_Surface *src = tempSurfaces[i].get();
SurfacePtr surface = createSurface(src->w + 16, src->h);
SDL_Rect srcrect = { 0, 0, src->w, src->h };
SDL_Rect dstrect = { 16, 0, src->w, src->h };
SDL_BlitSurface(src, &srcrect, surface.get(), &dstrect);
if (i == 0) {
srcrect = SDL_Rect{ 80, 64, 16, 16 };
dstrect = SDL_Rect{ 0, (src->h - 16) / 2, 16, 16 };
SDL_BlitSurface(bmGUI, &srcrect, surface.get(), &dstrect);
}
surfaces.push_back(std::move(surface));
}
+
+ for (size_t i = oldLinkSize; i < links.size(); i++) {
+ GUITextArea::Hyperlink2 &link = links[i];
+ link.line += oldLineCount;
+ link.startX += 16;
+ link.endX += 16;
+ }
}
}
}
};
class TableChunk : public Chunk {
public:
int columns, rows;
std::vector<Chunk*> items;
std::vector<int> cachedRowWidth;
static const int lineWidth = 16;
TableChunk() : columns(0), rows(0) {}
virtual ~TableChunk() {
for (auto item : items) {
delete item;
}
}
virtual void updateSizeForced(int maxWidth) override {
cachedMaxWidth = maxWidth;
cachedWidth = 0;
cachedNumberOfLines = 0;
cachedRowWidth.clear();
for (int i = 0; i < columns; i++) {
int mw = (i < columns - 1) ? 0x40000000 : (maxWidth - lineWidth * 2 - cachedWidth);
int w = 0;
for (int j = 0; j < rows; j++) {
auto item = items[j * columns + i];
if (item) {
item->updateSize(mw);
w = std::max(item->cachedWidth, w);
}
}
cachedRowWidth.push_back(w);
cachedWidth += w + lineWidth;
}
cachedWidth += lineWidth;
for (int j = 0; j < rows; j++) {
int h = 0;
for (int i = 0; i < columns; i++) {
auto item = items[j * columns + i];
if (item) {
h = std::max(item->cachedNumberOfLines, h);
}
}
cachedNumberOfLines += h;
}
cachedNumberOfLines += 3;
}
SurfacePtr createSurfaceWithGridLine(int height, bool drawHorizontal, int verticalTop, int verticalBottom) {
SurfacePtr surface = createSurface(cachedWidth, height);
SDL_Color fg = objThemes.getTextColor(true);
Uint32 color = SDL_MapRGBA(surface->format, fg.r, fg.g, fg.b, 255);
if (drawHorizontal) {
SDL_Rect r = { lineWidth / 2, height / 2, cachedWidth - lineWidth, 1 };
SDL_FillRect(surface.get(), &r, color);
}
if (verticalBottom > verticalTop) {
SDL_Rect r = { lineWidth / 2, verticalTop, 1, verticalBottom - verticalTop };
SDL_FillRect(surface.get(), &r, color);
for (int w : cachedRowWidth) {
r.x += w + lineWidth;
SDL_FillRect(surface.get(), &r, color);
}
}
return surface;
}
- virtual void createSurfaces(std::vector<SurfacePtr>& surfaces) override {
+ virtual void createSurfaces(std::vector<SurfacePtr>& surfaces, std::vector<GUITextArea::Hyperlink2>& links) override {
int height = TTF_FontHeight(fontText) + 1;
for (int j = 0; j < rows; j++) {
if (j == 0) surfaces.push_back(createSurfaceWithGridLine(height, true, height / 2, height));
+ const size_t oldLineCount = surfaces.size();
+
std::vector<std::vector<SurfacePtr> > tempSurfaces(columns);
int h = 0;
+ int x = lineWidth;
for (int i = 0; i < columns; i++) {
auto item = items[j * columns + i];
if (item) {
- item->createSurfaces(tempSurfaces[i]);
+ const size_t oldLinkSize = links.size();
+ item->createSurfaces(tempSurfaces[i], links);
h = std::max<int>(tempSurfaces[i].size(), h);
+
+ for (size_t k = oldLinkSize; k < links.size(); k++) {
+ GUITextArea::Hyperlink2 &link = links[k];
+ link.line += oldLineCount;
+ link.startX += x;
+ link.endX += x;
+ }
}
+ x += cachedRowWidth[i] + lineWidth;
}
for (int y = 0; y < h; y++) {
SurfacePtr surface = createSurfaceWithGridLine(height, false, 0, height);
int x = lineWidth;
for (int i = 0; i < columns; i++) {
if (y < (int)tempSurfaces[i].size()) {
auto src = tempSurfaces[i][y].get();
SDL_Rect srcrect = { 0, 0, src->w, src->h };
SDL_Rect dstrect = { x, 0, src->w, src->h };
SDL_BlitSurface(src, &srcrect, surface.get(), &dstrect);
}
x += cachedRowWidth[i] + lineWidth;
}
surfaces.push_back(std::move(surface));
}
if (j == 0) surfaces.push_back(createSurfaceWithGridLine(height, true, 0, height));
}
surfaces.push_back(createSurfaceWithGridLine(height, true, 0, height / 2));
}
};
class CodeChunk : public Chunk {
public:
std::vector<std::string> lines;
virtual void updateSizeForced(int maxWidth) override {
int width = 0;
for (const std::string& s : lines) {
int w = 0;
TTF_SizeUTF8(fontMono, s.c_str(), &w, NULL);
width = std::max(width, w);
}
cachedMaxWidth = maxWidth;
cachedWidth = width;
cachedNumberOfLines = lines.size() + 1;
}
- virtual void createSurfaces(std::vector<SurfacePtr>& surfaces) override {
+ virtual void createSurfaces(std::vector<SurfacePtr>& surfaces, std::vector<GUITextArea::Hyperlink2>& links) override {
SDL_Color fg = objThemes.getTextColor(true);
surfaces.emplace_back(TTF_RenderUTF8_Blended(fontText, "Copy code", fg));
+
+ links.push_back(GUITextArea::Hyperlink2{ surfaces.size() - 1, 0, surfaces.back()->w, "code:" });
+
for (const std::string& s : lines) {
surfaces.emplace_back(TTF_RenderUTF8_Blended(fontMono, s.c_str(), fg));
+ links.back().url += s + "\n";
}
}
};
class HeaderChunk : public Chunk {
public:
int level;
Chunk *child;
HeaderChunk() : level(0), child(NULL) {}
virtual ~HeaderChunk() {
delete child;
}
int getAdditionalWidth() const {
return (level <= 1) ? 0 : (level == 2) ? 24 : 20;
}
int getAdditionalHeight() const {
return (level <= 1) ? 1 : 0;
}
virtual void updateSizeForced(int maxWidth) override {
const int additionalWidth = getAdditionalWidth();
const int additionalHeight = getAdditionalHeight();
cachedMaxWidth = maxWidth;
child->updateSize(maxWidth - additionalWidth);
cachedWidth = child->cachedWidth + additionalWidth;
cachedNumberOfLines = child->cachedNumberOfLines + additionalHeight;
}
- virtual void createSurfaces(std::vector<SurfacePtr>& surfaces) override {
+ virtual void createSurfaces(std::vector<SurfacePtr>& surfaces, std::vector<GUITextArea::Hyperlink2>& links) override {
if (level <= 1) {
- child->createSurfaces(surfaces);
+ child->createSurfaces(surfaces, links);
int h = TTF_FontHeight(fontText);
SurfacePtr surface = createSurface(cachedWidth, h);
SDL_Color fg = objThemes.getTextColor(true);
Uint32 color = SDL_MapRGBA(surface->format, fg.r, fg.g, fg.b, 255);
if (level <= 0) {
SDL_Rect r[2] = {
{ 0, h / 2 - 3, cachedWidth, 2 },
{ 0, h / 2 + 1, cachedWidth, 2 },
};
SDL_FillRects(surface.get(), r, 2, color);
} else {
SDL_Rect r = { 0, h / 2 - 1, cachedWidth, 2 };
SDL_FillRect(surface.get(), &r, color);
}
surfaces.push_back(std::move(surface));
} else {
auto bmGUI = getImageManager().loadImage(getDataPath() + "gfx/gui.png");
+ const size_t oldLineCount = surfaces.size();
+ const size_t oldLinkSize = links.size();
+
std::vector<SurfacePtr> tempSurfaces;
- child->createSurfaces(tempSurfaces);
+ child->createSurfaces(tempSurfaces, links);
const int additionalWidth = getAdditionalWidth();
+ for (size_t i = oldLinkSize; i < links.size(); i++) {
+ GUITextArea::Hyperlink2 &link = links[i];
+ link.line += oldLineCount;
+ link.startX += additionalWidth;
+ link.endX += additionalWidth;
+ }
+
for (int i = 0, m = tempSurfaces.size(); i < m; i++) {
SDL_Surface *src = tempSurfaces[i].get();
SurfacePtr surface = createSurface(src->w + additionalWidth, src->h);
SDL_Rect srcrect = { 0, 0, src->w, src->h };
SDL_Rect dstrect = { additionalWidth, 0, src->w, src->h };
SDL_BlitSurface(src, &srcrect, surface.get(), &dstrect);
if (i == 0) {
if (level == 2) {
srcrect = SDL_Rect{ 64, 0, 16, 16 };
} else {
srcrect = SDL_Rect{ 96, 16, 16, 16 };
}
dstrect = SDL_Rect{ (additionalWidth - 16) / 2, (src->h - 16) / 2, 16, 16 };
SDL_BlitSurface(bmGUI, &srcrect, surface.get(), &dstrect);
}
surfaces.push_back(std::move(surface));
}
}
}
};
class HelpPage {
public:
int level;
std::string title;
std::string nameSpace;
std::vector<Chunk*> chunks;
static const int BIGGEST_LEVEL = 10;
HelpPage() : level(0) {}
~HelpPage() {
for (auto chunk : chunks) {
delete chunk;
}
}
void show(SDL_Renderer& renderer, GUITextArea *textArea) {
SDL_Color fg = objThemes.getTextColor(true);
std::vector<SurfacePtr> surfaces;
+ std::vector<GUITextArea::Hyperlink2> links;
for (auto chunk : chunks) {
if (chunk) {
chunk->updateSize(textArea->width - 16);
- chunk->createSurfaces(surfaces);
+ chunk->createSurfaces(surfaces, links);
surfaces.emplace_back(TTF_RenderUTF8_Blended(fontText, " ", fg));
}
}
textArea->setStringArray(renderer, surfaces);
+ textArea->addHyperlinks(links);
}
};
void parseText(const std::vector<std::string>& lines, size_t start, size_t end, size_t startOfNonSpaces, std::string& output) {
bool init = false;
for (size_t i = start; i < end; i++) {
size_t lp = (i == start) ? startOfNonSpaces : 0;
const std::string& line = lines[i];
bool isSpace = true, isVerbatim = false;
for (; lp < line.size(); lp++) {
char c = line[lp];
if (c == ' ' || c == '\t') {
if (isVerbatim) output.push_back(' ');
isSpace = true;
} else {
if (!isVerbatim && init && isSpace) output.push_back(' ');
if (c == '`') isVerbatim = !isVerbatim;
output.push_back(c);
init = true;
isSpace = false;
}
}
}
}
ParagraphChunk* parseParagraph(const std::vector<std::string>& lines, size_t start, size_t end, size_t startOfNonSpaces) {
auto ret = new ParagraphChunk;
parseText(lines, start, end, startOfNonSpaces, ret->text);
return ret;
}
CodeChunk* parseCode(const std::vector<std::string>& lines, size_t start, size_t end) {
auto ret = new CodeChunk;
for (size_t i = start; i < end; i++) {
ret->lines.push_back(lines[i]);
}
return ret;
}
ItemizeChunk* parseItemize(const std::vector<std::string>& lines, size_t start, size_t end, char marker) {
auto ret = new ItemizeChunk;
size_t last = start, startOfNonSpaces = 0;
for (size_t i = start; i <= end; i++) {
bool isNewItem = false;
size_t lp = 0;
if (i < end) {
lp = lines[i].find_first_not_of(" \t");
if (lp != std::string::npos && lp + 1 < (int)lines[i].size() && lines[i][lp] == marker) {
char c = lines[i][lp + 1];
isNewItem = (c == ' ' || c == '\t');
}
} else {
isNewItem = true;
}
if (isNewItem) {
if (last != i) {
ret->items.push_back(parseParagraph(lines, last, i, startOfNonSpaces));
}
last = i;
startOfNonSpaces = lp + 1;
}
}
return ret;
}
void parseRow(const std::string& text, std::vector<std::string>& output) {
output.clear();
size_t lps = text.find_first_not_of(" \t");
if (lps == std::string::npos) return;
if (text[lps] == '|') lps++;
size_t lpe = text.find_last_not_of(" \t");
if (lpe < lps) return;
if (text[lpe] != '|') lpe++;
std::string item;
for (size_t lp = lps; lp <= lpe; lp++) {
char c = (lp < lpe) ? text[lp] : '|';
if (c == '|') {
output.push_back(item);
item.clear();
} else {
item.push_back(c);
}
}
}
TableChunk* parseTable(const std::vector<std::string>& lines, size_t start, size_t end) {
auto ret = new TableChunk;
std::vector<std::string> row;
//determine the number of columns
parseRow(lines[start], row);
int columns = row.size(), rows = 0;
if (columns > 0) {
ret->columns = columns;
ret->rows = rows = end - start - 1;
ret->items.resize(columns * rows, NULL);
for (int i = 0; i < columns; i++) {
ret->items[i] = parseParagraph(row, i, i + 1, 0);
}
for (int j = 1; j < rows; j++) {
parseRow(lines[start + j + 1], row);
for (int i = 0, m = std::min<int>(row.size(), columns); i < m; i++) {
ret->items[j * columns + i] = parseParagraph(row, i, i + 1, 0);
}
}
}
return ret;
}
HeaderChunk* parseHeader(const std::vector<std::string>& lines, size_t start, size_t end, size_t startOfNonSpaces, int level) {
auto ret = new HeaderChunk;
ret->level = level;
ret->child = parseParagraph(lines, start, end, startOfNonSpaces);
return ret;
}
Chunk* parseChunk(const std::vector<std::string>& lines, size_t& start) {
size_t end, startOfNonSpaces;
// skip empty lines
for (; start < lines.size(); start++) {
startOfNonSpaces = lines[start].find_first_not_of(" \t");
if (startOfNonSpaces != std::string::npos) break;
}
if (start >= lines.size()) return NULL;
// check if it's code
if (lines[start].find("~~~") == 0) {
start++;
if (start >= lines.size()) return NULL;
for (end = start; end < lines.size(); end++) {
if (lines[end].find("~~~") == 0) break;
}
auto ret = parseCode(lines, start, end);
start = end + 1;
return ret;
} else {
for (end = start; end < lines.size(); end++) {
if (lines[end].find_first_not_of(" \t") == std::string::npos) break;
}
}
if (end == start) return NULL;
// check if it's itemize
if (startOfNonSpaces + 1 < (int)lines[start].size()) {
char c = lines[start][startOfNonSpaces];
char c2 = lines[start][startOfNonSpaces + 1];
if ((c == '*' || c == '+' || c == '-') && (c2 == ' ' || c2 == '\t')) {
auto ret = parseItemize(lines, start, end, c);
start = end;
return ret;
}
}
// check if it's table
if (end - start >= 3 && lines[start].find_first_of('|') != std::string::npos && lines[start + 1].find_first_not_of(" \t-|") == std::string::npos) {
auto ret = parseTable(lines, start, end);
start = end;
return ret;
}
// check if it's header of syntax "### title"
if (lines[start][startOfNonSpaces] == '#') {
int level = 0;
for (; level < 5; level++) {
if ((size_t)(startOfNonSpaces + level + 1) >= lines[start].size()) {
level = -1;
break;
}
char c = lines[start][startOfNonSpaces + level + 1];
if (c == ' ' || c == '\t') break;
else if (c != '#') {
level = -1;
break;
}
}
if (level >= 0) {
auto ret = parseHeader(lines, start, end, startOfNonSpaces + level + 1, level);
start = end;
return ret;
}
}
// check if it's header of syntax "title\n==="
if (end - start >= 2) {
int level = -1;
if (lines[end - 1].find_first_not_of(" \t=") == std::string::npos) level = 0;
else if (lines[end - 1].find_first_not_of(" \t-") == std::string::npos) level = 1;
if (level >= 0) {
auto ret = parseHeader(lines, start, end - 1, startOfNonSpaces, level);
start = end;
return ret;
}
}
// now assume it's a normal paragraph
auto ret = parseParagraph(lines, start, end, startOfNonSpaces);
start = end;
return ret;
}
class HelpWindow : public GUIWindow {
public:
int currentPage;
std::vector<int> history;
int currentHistory;
public:
HelpWindow(ImageManager& imageManager, SDL_Renderer& renderer, int left = 0, int top = 0, int width = 0, int height = 0,
bool enabled = true, bool visible = true, const char* caption = NULL)
: GUIWindow(imageManager, renderer, left, top, width, height, enabled, visible, caption)
, currentPage(0)
, currentHistory(0)
{
}
};
HelpManager::HelpManager(GUIEventCallback *parent)
: parent(parent)
{
std::vector<std::string> lines;
{
std::ifstream fin((getDataPath() + "../docs/ScriptAPI.md").c_str());
if (fin) {
//Loop the lines of the file.
std::string line;
char c;
while (fin.get(c)) {
if (c == '\r') {
} else if (c == '\n') {
lines.push_back(line);
line.clear();
} else {
line.push_back(c);
}
}
lines.push_back(line);
} else {
std::cerr << "ERROR: Unable to open the ScriptAPI.md file." << std::endl;
lines.push_back("ERROR: Unable to open the ScriptAPI.md file.");
}
}
//TODO: add hyperlinks of subpages to each page
{
std::string library, nameSpace;
HelpPage *page = new HelpPage;
size_t start = 0;
while (auto chunk = parseChunk(lines, start)) {
if (HeaderChunk *header = dynamic_cast<HeaderChunk*>(chunk)) {
//We parse a new header so add a new page.
if (!page->chunks.empty()) {
pages.push_back(page);
page = new HelpPage;
}
page->level = header->level;
//Get the title.
if (auto par = dynamic_cast<ParagraphChunk*>(header->child)) {
page->title = par->text;
//Get the library name.
if (page->title.find("library") != std::string::npos) {
library.clear();
nameSpace.clear();
size_t lps = page->title.find_first_of('\"');
if (lps != std::string::npos) {
size_t lpe = page->title.find_last_of('\"');
if (lpe > lps) {
library = page->title.substr(lps + 1, lpe - lps - 1);
}
}
}
//Get the namespace.
if (page->title.find("Global") != std::string::npos) {
nameSpace.clear();
} else if (page->title.find("Static") != std::string::npos) {
nameSpace = library + ".";
} else if (page->title.find("Member") != std::string::npos) {
nameSpace = library + ":";
}
}
}
if (ItemizeChunk *itemize = library.empty() ? NULL : dynamic_cast<ItemizeChunk*>(chunk)) {
//We parse a new header so add a new page.
if (!page->chunks.empty()) {
pages.push_back(page);
page = new HelpPage;
}
page->level = HelpPage::BIGGEST_LEVEL;
page->nameSpace = nameSpace;
//Get the title.
if (ParagraphChunk *par = itemize->items.empty() ? NULL : dynamic_cast<ParagraphChunk*>(itemize->items[0])) {
page->title = par->text;
}
}
page->chunks.push_back(chunk);
}
if (page->chunks.empty()) delete page;
else pages.push_back(page);
}
//Normalize the title of each page
for (auto page : pages) {
if (page->title.find_first_of("(/") != std::string::npos || page->title.find("--") != std::string::npos) {
std::string tmp = page->title;
size_t lp = tmp.find("--");
if (lp != std::string::npos) tmp = tmp.substr(0, lp);
std::vector<std::string> names;
std::istringstream stream(tmp);
std::string line;
while (std::getline(stream, line, '/')) {
lp = line.find_first_of('(');
if (lp != std::string::npos) line = line.substr(0, lp);
lp = line.find_first_not_of(" \t");
if (lp != std::string::npos) {
line = line.substr(lp, line.find_last_not_of(" \t") - lp + 1);
if (std::find(names.begin(), names.end(), line) == names.end()) {
names.push_back(line);
}
}
}
tmp.clear();
for (const std::string& s : names) {
if (!tmp.empty()) tmp.push_back('/');
tmp += s;
}
page->title = tmp;
}
//Also normize the contents if necessary.
if (ItemizeChunk *itemize = page->chunks.empty() ? NULL : dynamic_cast<ItemizeChunk*>(page->chunks[0])) {
if (ParagraphChunk *par = itemize->items.empty() ? NULL : dynamic_cast<ParagraphChunk*>(itemize->items[0])) {
size_t lp = par->text.find("--");
if (lp != std::string::npos) {
std::vector<std::string> temp;
temp.push_back(par->text.substr(lp + 2));
page->chunks.push_back(parseParagraph(temp, 0, 1, 0));
lp = par->text.find_last_not_of(" \t", lp);
if (lp != std::string::npos) par->text = par->text.substr(0, lp);
}
}
}
}
}
HelpManager::~HelpManager() {
for (auto page : pages) {
delete page;
}
}
GUIWindow* HelpManager::newWindow(ImageManager& imageManager, SDL_Renderer& renderer, int pageIndex) {
//Create the GUI.
HelpWindow* root = new HelpWindow(imageManager, renderer, (SCREEN_WIDTH - 600) / 2, (SCREEN_HEIGHT - 500) / 2, 600, 500, true, true, _("Scripting Help"));
root->minWidth = root->width; root->minHeight = root->height;
root->name = "scriptingHelpWindow";
root->eventCallback = this;
GUIButton* btn;
const int BUTTON_SPACE = 70;
//Some navigation buttons
btn = new GUIButton(imageManager, renderer, root->width / 2 - BUTTON_SPACE * 3, 60, -1, 36, _("Homepage"), 0, true, true, GUIGravityCenter);
btn->gravityLeft = btn->gravityRight = GUIGravityCenter;
btn->name = "Homepage";
btn->smallFont = true;
btn->eventCallback = root;
root->addChild(btn);
btn = new GUIButton(imageManager, renderer, root->width / 2 - BUTTON_SPACE, 60, -1, 36, _("Back"), 0, true, true, GUIGravityCenter);
btn->gravityLeft = btn->gravityRight = GUIGravityCenter;
btn->name = "Back";
btn->smallFont = true;
btn->eventCallback = root;
root->addChild(btn);
btn = new GUIButton(imageManager, renderer, root->width / 2 + BUTTON_SPACE, 60, -1, 36, _("Forward"), 0, true, true, GUIGravityCenter);
btn->gravityLeft = btn->gravityRight = GUIGravityCenter;
btn->name = "Forward";
btn->smallFont = true;
btn->eventCallback = root;
root->addChild(btn);
btn = new GUIButton(imageManager, renderer, root->width / 2 + BUTTON_SPACE * 3, 60, -1, 36, _("Search"), 0, true, true, GUIGravityCenter);
btn->gravityLeft = btn->gravityRight = GUIGravityCenter;
btn->name = "Search";
btn->smallFont = true;
btn->eventCallback = root;
root->addChild(btn);
//Some buttons for search mode
btn = new GUIButton(imageManager, renderer, root->width / 2 - BUTTON_SPACE * 3, 60, -1, 36, _("Back"), 0, true, false, GUIGravityCenter);
btn->gravityLeft = btn->gravityRight = GUIGravityCenter;
btn->name = "Back2";
btn->smallFont = true;
btn->eventCallback = root;
root->addChild(btn);
btn = new GUIButton(imageManager, renderer, root->width / 2 + BUTTON_SPACE * 3, 60, -1, 36, _("Goto"), 0, true, false, GUIGravityCenter);
btn->gravityLeft = btn->gravityRight = GUIGravityCenter;
btn->name = "Goto";
btn->smallFont = true;
btn->eventCallback = root;
root->addChild(btn);
//Add a text area.
GUITextArea *textArea = new GUITextArea(imageManager, renderer, 25, 100, 550, 340);
textArea->gravityRight = textArea->gravityBottom = GUIGravityRight;
textArea->name = "TextArea";
textArea->editable = false;
textArea->eventCallback = root;
root->addChild(textArea);
//Some widgets for search mode
GUITextBox *textBox = new GUITextBox(imageManager, renderer, 25, 100, 550, 36, NULL, 0, true, false);
textBox->gravityRight = GUIGravityRight;
textBox->name = "TextSearch";
textBox->eventCallback = root;
root->addChild(textBox);
GUIListBox *listBox = new GUIListBox(imageManager, renderer, 25, 140, 550, 300, true, false);
listBox->gravityRight = listBox->gravityBottom = GUIGravityRight;
listBox->name = "List";
root->addChild(listBox);
//The close button
btn = new GUIButton(imageManager, renderer, int(root->width*0.5f), 500 - 44, -1, 36, _("Close"), 0, true, true, GUIGravityCenter);
btn->gravityLeft = btn->gravityRight = GUIGravityCenter;
btn->gravityTop = btn->gravityBottom = GUIGravityRight;
btn->name = "cfgCancel";
btn->eventCallback = root;
root->addChild(btn);
//Show contents.
if (pageIndex < 0 || pageIndex >= (int)pages.size()) pageIndex = 0;
pages[pageIndex]->show(renderer, textArea);
root->currentPage = pageIndex;
root->history.push_back(pageIndex);
return root;
}
void HelpManager::GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name, GUIObject* obj, int eventType) {
HelpWindow *window = dynamic_cast<HelpWindow*>(obj);
if (name == "Homepage") {
auto textArea = dynamic_cast<GUITextArea*>(obj->getChild("TextArea"));
if (textArea && window->currentPage != 0) {
window->currentHistory++;
window->history.resize(window->currentHistory);
window->history.push_back(0);
window->currentPage = 0;
pages[0]->show(renderer, textArea);
}
return;
} else if (name == "Back") {
auto textArea = dynamic_cast<GUITextArea*>(obj->getChild("TextArea"));
if (textArea && window->currentHistory > 0) {
window->currentHistory--;
int index = window->currentPage = window->history[window->currentHistory];
pages[index]->show(renderer, textArea);
}
return;
} else if (name == "Forward") {
auto textArea = dynamic_cast<GUITextArea*>(obj->getChild("TextArea"));
if (textArea && window->currentHistory < (int)window->history.size() - 1) {
window->currentHistory++;
int index = window->currentPage = window->history[window->currentHistory];
pages[index]->show(renderer, textArea);
}
return;
} else if (name == "Search" || name == "Back2") {
bool isSearch = name == "Search";
if (auto o = obj->getChild("Homepage")) o->visible = !isSearch;
if (auto o = obj->getChild("Back")) o->visible = !isSearch;
if (auto o = obj->getChild("Forward")) o->visible = !isSearch;
if (auto o = obj->getChild("Search")) o->visible = !isSearch;
if (auto o = obj->getChild("TextArea")) o->visible = !isSearch;
if (auto o = obj->getChild("Back2")) o->visible = isSearch;
if (auto o = obj->getChild("Goto")) o->visible = isSearch;
auto textBox = obj->getChild("TextSearch");
auto listBox = dynamic_cast<GUIListBox*>(obj->getChild("List"));
if (textBox && listBox) {
textBox->visible = isSearch;
listBox->visible = isSearch;
if (isSearch && listBox->item.empty()) {
updateListBox(imageManager, renderer, listBox, textBox->caption);
}
}
return;
} else if (name == "Goto") {
auto listBox = dynamic_cast<GUIListBox*>(obj->getChild("List"));
auto textArea = dynamic_cast<GUITextArea*>(obj->getChild("TextArea"));
if (listBox && textArea && listBox->value >= 0 && listBox->value < (int)listBox->item.size()) {
const std::string& s = listBox->item[listBox->value];
int index = atoi(s.c_str());
if (index >= 0 && index < (int)pages.size()) {
window->currentHistory++;
window->history.resize(window->currentHistory);
window->history.push_back(index);
window->currentPage = index;
pages[index]->show(renderer, textArea);
GUIEventQueue.push_back(GUIEvent{ this, "Back2", obj, GUIEventClick });
}
}
return;
} else if (name == "TextArea") {
- //TODO:
+ auto textArea = dynamic_cast<GUITextArea*>(obj->getChild("TextArea"));
+ if (textArea && textArea->clickedHyperlink.size() > 5) {
+ std::string s = textArea->clickedHyperlink.substr(0, 5);
+ if (s == "code:") {
+ SDL_SetClipboardText(textArea->clickedHyperlink.c_str() + 5);
+ }
+ }
return;
} else if (name == "TextSearch") {
auto textBox = obj->getChild("TextSearch");
auto listBox = dynamic_cast<GUIListBox*>(obj->getChild("List"));
if (textBox && listBox) {
updateListBox(imageManager, renderer, listBox, textBox->caption);
}
return;
}
//Do the default.
parent->GUIEventCallback_OnEvent(imageManager, renderer, name, obj, eventType);
}
void HelpManager::updateListBox(ImageManager& imageManager, SDL_Renderer& renderer, GUIListBox *listBox, const std::string& keyword) {
std::vector<std::string> keywords;
std::string line;
for (char c : keyword) {
if (c == ' ' || c == '\t') {
if (!line.empty()) keywords.push_back(line);
line.clear();
} else {
line.push_back(tolower((unsigned char)c));
}
}
if (!line.empty()) keywords.push_back(line);
int backup = listBox->value;
listBox->clearItems();
for (size_t i = 0; i < pages.size(); i++) {
HelpPage *page = pages[i];
bool match = true;
std::string tmp;
for (char c : page->title) {
tmp.push_back(tolower((unsigned char)c));
}
for (const std::string& s : keywords) {
if (tmp.find(s) == std::string::npos) {
match = false;
break;
}
}
if (match) {
SharedTexture tex;
SDL_Color fg = objThemes.getTextColor(true);
if (page->level == HelpPage::BIGGEST_LEVEL) {
SDL_Color fg2 = { Uint8((255 + fg.r) / 2), Uint8((255 + fg.g) / 2), Uint8((255 + fg.b) / 2), Uint8(255) };
SurfacePtr surf1(TTF_RenderUTF8_Blended(fontMono, (std::string(5, ' ') + page->nameSpace).c_str(), fg2));
SurfacePtr surf2(TTF_RenderUTF8_Blended(fontMono, page->title.empty() ? " " : page->title.c_str(), fg));
SurfacePtr surf = createSurface(surf1->w + surf2->w, std::max(surf1->h, surf2->h) + 4);
SDL_Rect srcrect = { 0, 0, surf1->w, surf1->h };
SDL_Rect dstrect = { 0, 2, surf1->w, surf1->h };
SDL_BlitSurface(surf1.get(), &srcrect, surf.get(), &dstrect);
srcrect = SDL_Rect{ 0, 0, surf2->w, surf2->h };
dstrect = SDL_Rect{ surf1->w, 2, surf2->w, surf2->h };
SDL_BlitSurface(surf2.get(), &srcrect, surf.get(), &dstrect);
tex = textureFromSurface(renderer, std::move(surf));
} else {
tex = textureFromTextShared(renderer, *fontText,
(std::string(page->level, ' ') + page->title).c_str(),
fg);
}
char s[32];
sprintf(s, "%d", i);
listBox->addItem(renderer, s, tex);
}
}
listBox->value = (backup >= 0 && backup < (int)listBox->item.size()) ? backup : -1;
}

File Metadata

Mime Type
text/x-diff
Expires
Fri, May 8, 8:29 PM (1 w, 23 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
62728
Default Alt Text
(70 KB)

Event Timeline