diff --git a/CMakeLists.txt b/CMakeLists.txt index 63aac16..28dfe7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,7 @@ src/SDL2/Views/TextListView.cpp src/SDL2/Views/ScrollableTextListView.cpp src/SDL2/Views/ProgressView.cpp src/SDL2/Views/CheckView.cpp +src/SDL2/Views/MultilineEditTextView.cpp src/SDL2/Views/EditTextView.cpp src/SDL2/Views/PictureView.cpp src/SDL2/Views/VScrollView.cpp diff --git a/include/TessesFramework/Http/HttpUtils.hpp b/include/TessesFramework/Http/HttpUtils.hpp index 5ac1be6..7a13d47 100644 --- a/include/TessesFramework/Http/HttpUtils.hpp +++ b/include/TessesFramework/Http/HttpUtils.hpp @@ -71,11 +71,19 @@ namespace Tesses::Framework::Http } StatusCode; struct CaseInsensitiveLess { + CaseInsensitiveLess(const CaseInsensitiveLess& str); + CaseInsensitiveLess(); + CaseInsensitiveLess* offset; + bool caseSensitive; bool operator() (const std::string& s1, const std::string& s2) const; }; + class HttpDictionary { public: + HttpDictionary(); + HttpDictionary(bool caseSensitive); std::map,CaseInsensitiveLess> kvp; + void SetCaseSensitive(bool isCaseSensitive); void Clear(); void Clear(std::string key, bool kvpExistsAfter); void SetValue(std::string key, std::string value); @@ -116,6 +124,7 @@ struct CaseInsensitiveLess { class Uri { public: + Uri(); std::string GetQuery(); std::string GetPathAndQuery(); uint16_t GetPort(); diff --git a/include/TessesFramework/SDL2/FontCache.hpp b/include/TessesFramework/SDL2/FontCache.hpp index e4b592a..7442aae 100644 --- a/include/TessesFramework/SDL2/FontCache.hpp +++ b/include/TessesFramework/SDL2/FontCache.hpp @@ -24,7 +24,7 @@ class FontCache int MaxHeight(); int PointSize(); void CalculateSize(std::string text, int& x,int& y); - void Render(SDL_Renderer* renderer,int x,int y, std::string text, const SDL_Color& color); + void Render(SDL_Renderer* renderer,int x,int y, std::string text, const SDL_Color& color,size_t begin=0,size_t end=std::string::npos); ~FontCache(); }; } diff --git a/src/Http/HttpServer.cpp b/src/Http/HttpServer.cpp index 9de22d2..f2afe71 100644 --- a/src/Http/HttpServer.cpp +++ b/src/Http/HttpServer.cpp @@ -678,6 +678,7 @@ namespace Tesses::Framework::Http this->statusCode = OK; this->strm = strm; this->sent = false; + this->queryParams.SetCaseSensitive(true); this->responseHeaders.AddValue("Server","TessesFrameworkWebServer"); } Stream& ServerContext::GetStream() diff --git a/src/Http/HttpUtils.cpp b/src/Http/HttpUtils.cpp index 11caaa7..e2f06b8 100644 --- a/src/Http/HttpUtils.cpp +++ b/src/Http/HttpUtils.cpp @@ -137,6 +137,10 @@ namespace Tesses::Framework::Http { return true; } + Uri::Uri() + { + this->query.SetCaseSensitive(true); + } std::string Uri::GetPathAndQuery() { return this->path + this->GetQuery(); @@ -740,6 +744,28 @@ namespace Tesses::Framework::Http { return ""; } } + CaseInsensitiveLess::CaseInsensitiveLess(const CaseInsensitiveLess& str) + { + this->caseSensitive = str.caseSensitive; + this->offset = this; + } + CaseInsensitiveLess::CaseInsensitiveLess() + { + this->caseSensitive=false; + this->offset = this; + } + void HttpDictionary::SetCaseSensitive(bool isCaseSensitive) + { + this->kvp.key_comp().offset->caseSensitive=isCaseSensitive; + } + HttpDictionary::HttpDictionary(bool isCaseSensitive) + { + this->SetCaseSensitive(isCaseSensitive); + } + HttpDictionary::HttpDictionary() : HttpDictionary(false) + { + + } bool HttpDictionary::AnyEquals(std::string key, std::string value) { @@ -858,6 +884,7 @@ namespace Tesses::Framework::Http { return true; } bool CaseInsensitiveLess::operator() (const std::string& s1, const std::string& s2) const { + if(this->caseSensitive) return s1 == s2; return HttpUtils::ToLower(s1) < HttpUtils::ToLower(s2); } diff --git a/src/SDL2/FontCache.cpp b/src/SDL2/FontCache.cpp index 46d1ede..90acd06 100644 --- a/src/SDL2/FontCache.cpp +++ b/src/SDL2/FontCache.cpp @@ -87,13 +87,16 @@ void FontCache::CalculateSize(std::string text, int& x,int& y) } if(myX > x) x = myX; } -void FontCache::Render(SDL_Renderer* renderer,int x,int y, std::string text,const SDL_Color& color) +void FontCache::Render(SDL_Renderer* renderer,int x,int y, std::string text,const SDL_Color& color, size_t begin, size_t end) { int myX = x; int maxH = MaxHeight(); - for(auto c : text) + if(begin >= text.size()) return; + if(end > text.size()) end = text.size(); + + for(size_t i = begin; i < end; i++) { - switch(c) + switch(text[i]) { case '\n': { @@ -112,7 +115,7 @@ void FontCache::Render(SDL_Renderer* renderer,int x,int y, std::string text,cons break; default: { - auto tex = GetCharOfColor(c,color); + auto tex = GetCharOfColor(text[i],color); int wi; int he; SDL_QueryTexture(tex,NULL,NULL,&wi,&he); diff --git a/src/SDL2/GUIWindow.cpp b/src/SDL2/GUIWindow.cpp index 6f358c2..6619bcd 100644 --- a/src/SDL2/GUIWindow.cpp +++ b/src/SDL2/GUIWindow.cpp @@ -8,6 +8,7 @@ #include "TessesFramework/SDL2/Views/TextListView.hpp" #include "TessesFramework/SDL2/Views/AbsoluteView.hpp" #include "TessesFramework/SDL2/Views/EditTextView.hpp" +#include "TessesFramework/SDL2/Views/MultilineEditTextView.hpp" #include "TessesFramework/SDL2/Views/PictureView.hpp" #include "TessesFramework/SDL2/Views/ScrollableTextListView.hpp" #include "TessesFramework/SDL2/Views/HScrollView.hpp" @@ -580,6 +581,17 @@ namespace Tesses::Framework::SDL2 etv->SetHint(hint); return etv; } + else if(type == "MultilineEditTextView") + { + auto etv = new Views::MultilineEditTextView(); + etv->SetId(id); + etv->SetText(text); + std::string hint; + json.TryGetValueAsType("Hint",hint); + etv->SetHint(hint); + return etv; + } + else if(type == "PictureView") { auto pv = new Views::PictureView(); diff --git a/src/SDL2/Views/MultilineEditTextView.cpp b/src/SDL2/Views/MultilineEditTextView.cpp index 99e4118..7f594ce 100644 --- a/src/SDL2/Views/MultilineEditTextView.cpp +++ b/src/SDL2/Views/MultilineEditTextView.cpp @@ -4,6 +4,18 @@ namespace Tesses::Framework::SDL2::Views { + static int numberWidth(size_t n) + { + if(n == 0) return 1; + size_t digits = 0; + while(n != 0) + { + n /= 10; + digits++; + } + return digits; + } + void MultilineEditTextView::OnDraw(SDL_Renderer* renderer, SDL_Rect& r) { //1 | @@ -11,17 +23,42 @@ namespace Tesses::Framework::SDL2::Views // 9 | //10 | + auto win = this->GetWindow(); + auto w = win->monospaced_font->MaxWidth(); + auto h = win->monospaced_font->MaxHeight(); + auto digitsTotal = numberWidth(this->lines.size()); + auto numLines = (r.h - (win->palette.borderSize*4)) / h; + + if(numLines+this->topLeft.y > this->lines.size()) numLines = this->lines.size() - this->topLeft.y; + + for(size_t i = 0; i < numLines; i++) + { + win->monospaced_font->Render(renderer, r.x+(win->palette.borderSize*2),r.y+(h*i), Http::HttpUtils::LeftPad(std::to_string(i+this->topLeft.y+1),digitsTotal,' '),win->palette.accent); + win->monospaced_font->Render(renderer,r.x+(win->palette.borderSize*2)+(w*(digitsTotal+5)),r.y+(h*i),this->lines[i+this->topLeft.y],win->palette.accent,this->topLeft.x); + } - } bool MultilineEditTextView::OnEvent(SDL_Event& event, SDL_Rect& myBounds, SDL_Rect& visibleBounds) { - + if(event.type == SDL_TEXTINPUT) + { + TypeText(event.text.text); + } + else if(event.type == SDL_KEYDOWN) + { + switch(event.key.keysym.sym) + { + case SDLK_RETURN: + TypeText("\n"); + break; + } + } + return false; } MultilineEditTextView::MultilineEditTextView() :MultilineEditTextView(std::string()) { - + } MultilineEditTextView::MultilineEditTextView(std::string hint) : View() { @@ -72,7 +109,7 @@ namespace Tesses::Framework::SDL2::Views SDL_Point cursorBegin = this->cursorPos; SDL_Point cursorEnd = this->cursorEnd; - if(cursorBegin.y > cursorEnd.y || ((cursorBegin.y == cursorEnd.y) && (cursorBegin.x > cursorEnd.x))) + if((cursorBegin.y > cursorEnd.y || ((cursorBegin.y == cursorEnd.y) && (cursorBegin.x > cursorEnd.x))) && this->cursorEnd.x != -1 && this->cursorEnd.y != -1) { cursorBegin.y ^= cursorEnd.y; cursorEnd.y ^= cursorBegin.y; @@ -97,7 +134,7 @@ namespace Tesses::Framework::SDL2::Views this->cursorPos = cursorBegin+text.size(); this->cursorEnd = std::string::npos;*/ - if(cursorEnd.y != -1 && cursorEnd.x != -1) + if(this->cursorEnd.y != -1 && this->cursorEnd.x != -1) { int line = cursorBegin.y; for(int y = cursorBegin.y; y <= cursorEnd.y && y < lines.size(); y++) @@ -137,32 +174,49 @@ namespace Tesses::Framework::SDL2::Views - auto mylines = Http::HttpUtils::SplitString(text,"\n"); + auto mylines = text == "\n" ? std::vector({"",""}) : Http::HttpUtils::SplitString(text,"\n"); if(!mylines.empty()) { + int setXTo = 0; + if(mylines.size()==1) + { + setXTo=mylines[0].size()+cursorBegin.x; + } + else { + setXTo=mylines.back().size(); + } if(cursorBegin.y < this->lines.size()) { + if(cursorBegin.x > 0) { mylines[0] = this->lines[cursorBegin.y].substr(0,cursorBegin.x) + mylines[0]; } - if(cursorBegin.x < this->lines[cursorBegin.y].size()) { mylines.back() += this->lines[cursorBegin.y].substr(cursorBegin.x); } + + this->lines.erase(this->lines.begin()+cursorBegin.y); } - + bool first=true; for(auto& item : mylines) { + if(!first) + { + cursorBegin.y++; + } this->lines.insert(this->lines.begin()+cursorBegin.y,{item}); + first=false; } + cursorBegin.x = setXTo; + } diff --git a/src/SDL2/Views/TabView.cpp b/src/SDL2/Views/TabView.cpp index 3516451..e6511c6 100644 --- a/src/SDL2/Views/TabView.cpp +++ b/src/SDL2/Views/TabView.cpp @@ -12,6 +12,74 @@ namespace Tesses::Framework::SDL2::Views int h = win->monospaced_font->MaxHeight() + (win->palette.borderSize*3); + int w = win->monospaced_font->MaxWidth() + (win->palette.borderSize*3); + + size_t noItems = this->items.size(); + size_t offset = 0; + size_t width = noItems == 0 ? 0 : (r.w / noItems); + if(noItems > 0 && (r.w / noItems) < w*5) + { + offset=this->firstTab; + noItems = (r.w - (w*2)) / (w * 5); + width = noItems == 0 ? 0 : ((r.w - (w*2)) / noItems); + if(offset > items.size()) + offset = items.size()-3; + if((offset+noItems)>items.size()) noItems = items.size()-offset; + } + + + for(size_t i = 0; i < noItems; i++) + { + + Clipper clipper(renderer,r); + int yBottom = (h - 1)+r.y; + //auto bc = win->palette.GetBorderColor(false,(i+offset)==this->selectedTab,false); + std::string text = this->items[i+offset].first; + int textW; + int textH; + win->monospaced_font->CalculateSize(text,textW,textH); + + + + + /*if(i>0) + { + //auto bc2 = win->palette.GetBorderColor(false,(i+offset)==this->selectedTab || ((i+offset)-1)==this->selectedTab,false); + //SDL_SetRenderDrawColor(renderer,bc2.r,bc2.g,bc2.b,bc2.a); + // SDL_RenderDrawLine(renderer,(width*i)+r.x,r.y,(width*i)+r.x,yBottom); + }*/ + + //SDL_SetRenderDrawColor(renderer,bc.r,bc.g,bc.b,bc.a); + if((i+offset)==this->selectedTab) + { + SDL_SetRenderDrawColor(renderer,win->palette.accent.r,win->palette.accent.g,win->palette.accent.b,win->palette.accent.a); + SDL_Rect r0={.x=(int)(width*i+1)+r.x,.y=r.y,.w=(int)width-2,.h=yBottom-r.y}; + SDL_RenderFillRect(renderer,&r0); + } + //SDL_RenderDrawLine(renderer,(width*i+1)+r.x,yBottom,(width*(i+1)-2),yBottom); + clipper.Clip({.x=(int)(width*i+1)+r.x,.y=r.y,.w=(int)width-2,.h=yBottom-r.y}); + int textX = ((width-2)/2) - (textW/2); + int textY = ((yBottom-r.y)/2) - (textH/2); + + win->monospaced_font->Render(renderer,textX+r.x+(width*i+2),textY+r.y,text,((i+offset)==this->selectedTab) ? win->palette.background : win->palette.accent); + } + + if(noItems != items.size()) + { + auto greaterThanX=(r.w-w)+r.x; + auto lessThanX = greaterThanX-w; + int lgtX; + int lgtY; + win->monospaced_font->CalculateSize("<",lgtX,lgtY); + lgtX = (w/2)-(lgtX/2); + lgtY = (h/2)-(lgtY/2); + win->monospaced_font->Render(renderer,lessThanX+lgtX,r.y+lgtY,"<",win->palette.accent); + + win->monospaced_font->CalculateSize(">",lgtX,lgtY); + lgtX = (w/2)-(lgtX/2); + lgtY = (h/2)-(lgtY/2); + win->monospaced_font->Render(renderer,greaterThanX+lgtX,r.y+lgtY,">",win->palette.accent); + } SDL_Rect viewBounds = { .x=r.x, @@ -34,6 +102,62 @@ namespace Tesses::Framework::SDL2::Views { int h = win->monospaced_font->MaxHeight() + (win->palette.borderSize*3); + + int w = win->monospaced_font->MaxWidth() + (win->palette.borderSize*3); + int yBottom = (h - 1)+myBounds.y; + size_t noItems = this->items.size(); + size_t offset = 0; + + size_t width = noItems == 0 ? 0 : (myBounds.w / noItems); + if(noItems > 0 && (myBounds.w / noItems) < w*5) + { + offset=this->firstTab; + noItems = (myBounds.w - (w*2)) / (w * 5); + width = noItems == 0 ? 0 : ((myBounds.w - (w*2)) / noItems); + if(offset > items.size()) + offset = items.size()-3; + if((offset+noItems)>items.size()) noItems = items.size()-offset; + } + + int width_total = width*noItems; + + if(event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) + { + if(event.button.x >= visibleBounds.x && event.button.x < visibleBounds.x+visibleBounds.w && event.button.y >= visibleBounds.y && event.button.y < visibleBounds.y+visibleBounds.h) + { + + if(event.button.y < myBounds.y+h && event.button.x < myBounds.x+width_total) + { + this->selectedTab = offset + ((event.button.x-myBounds.x) / width); + return true; + } + if(event.button.y < myBounds.y+h && event.button.x < myBounds.x+myBounds.w) + { + if(noItems != items.size()) + { + auto greaterThanX=myBounds.w-w+myBounds.x; + auto lessThanX = greaterThanX-w; + + + if(event.button.x >= lessThanX && event.button.x < greaterThanX) + { + firstTab--; + if(firstTab >= items.size()) + firstTab=0; + } + else if(event.button.x >= greaterThanX) + { + firstTab++; + if(firstTab >= items.size()) + firstTab = items.size()-1; + } + } + } + + } + } + + SDL_Rect viewBounds = { .x=0, .y=h,