aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2018-05-06 17:15:51 +0200
committerdec05eba <dec05eba@protonmail.com>2018-05-06 17:18:19 +0200
commit7a2cb2c4b81a8a0696d3a11ce8781542f181bb12 (patch)
tree0d77fa05f4a99c4c1ff06f12ba76fae62550fc9b
parentb2f6a0235c5de32a3fcd359e28f4d1e3bd6950df (diff)
Make dchat Text editable
Not finished yet. Currently text can be entered, removed and you can move caret using arrow keys (up, down, left, right), home and end. Need to implement text selection and remove focus from chatbar when editing message board text. Chatbar should be replaced with dchat Text for proper multiline editable text.
-rw-r--r--.kdev4/dchat.kdev44
-rw-r--r--.vscode/launch.json27
-rw-r--r--README.md2
-rw-r--r--include/Text.hpp31
-rw-r--r--src/Cache.cpp42
-rw-r--r--src/Channel.cpp1
-rw-r--r--src/ChannelSidePanel.cpp4
-rw-r--r--src/MessageBoard.cpp5
-rw-r--r--src/Text.cpp325
-rw-r--r--src/UsersSidePanel.cpp4
10 files changed, 379 insertions, 66 deletions
diff --git a/.kdev4/dchat.kdev4 b/.kdev4/dchat.kdev4
index c0f72bd..a3fb28a 100644
--- a/.kdev4/dchat.kdev4
+++ b/.kdev4/dchat.kdev4
@@ -4,8 +4,8 @@ BuildItems=@Variant(\x00\x00\x00\t\x00\x00\x00\x00\x01\x00\x00\x00\x0b\x00\x00\x
[CustomDefinesAndIncludes][ProjectPath0]
Path=.
parseAmbiguousAsCPP=true
-parserArguments=-ferror-limit=100 -fspell-checking -Wdocumentation -Wunused-parameter -Wunreachable-code -Wall -std=c++11
-parserArgumentsC=-ferror-limit=100 -fspell-checking -Wdocumentation -Wunused-parameter -Wunreachable-code -Wall -std=c99
+parserArguments=-ferror-limit=100 -fspell-checking -Wdocumentation -Wunused-parameter -Wunreachable-code -Wall -std=c++14
+parserArgumentsC=-ferror-limit=100 -fspell-checking -Wdocumentation -Wunused-parameter -Wunreachable-code -Wall -std=c11
[CustomDefinesAndIncludes][ProjectPath0][Compiler]
Name=Clang
diff --git a/.vscode/launch.json b/.vscode/launch.json
deleted file mode 100644
index d49b13b..0000000
--- a/.vscode/launch.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- // Use IntelliSense to learn about possible attributes.
- // Hover to view descriptions of existing attributes.
- // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
- "version": "0.2.0",
- "configurations": [
- {
- "name": "(gdb) Launch",
- "type": "cppdbg",
- "request": "launch",
- "program": "${workspaceFolder}/sibs-build/debug/dchat",
- "args": [],
- "stopAtEntry": false,
- "cwd": "${workspaceFolder}",
- "environment": [],
- "externalConsole": true,
- "MIMode": "gdb",
- "setupCommands": [
- {
- "description": "Enable pretty-printing for gdb",
- "text": "-enable-pretty-printing",
- "ignoreFailures": true
- }
- ]
- }
- ]
-} \ No newline at end of file
diff --git a/README.md b/README.md
index 7eaff6b..f837cee 100644
--- a/README.md
+++ b/README.md
@@ -5,3 +5,5 @@ Currently only runs on GNU/Linux.
opendht, boost, libnsgif, mpv, sfml
## GNU/Linux specific
xsel, curl
+# TODO
+Use http(s) library to download content (curl?) and stream gif. If content is html, then stop downloading after we have retrieved title, header image and paragraph
diff --git a/include/Text.hpp b/include/Text.hpp
index daea7ca..9df973d 100644
--- a/include/Text.hpp
+++ b/include/Text.hpp
@@ -5,6 +5,7 @@
#include <SFML/Graphics/VertexArray.hpp>
#include <SFML/Graphics/Font.hpp>
#include <SFML/Graphics/RenderTarget.hpp>
+#include <SFML/Window/Event.hpp>
#include <SFML/System/String.hpp>
#include <vector>
@@ -35,7 +36,6 @@ namespace dchat
Text(const sf::String &str, const sf::Font *font, unsigned int characterSize, float maxWidth, bool plainText = true);
void setString(const sf::String &str);
- void appendStringNewLine(const sf::String &str);
void setPosition(float x, float y);
void setPosition(const sf::Vector2f &position);
@@ -43,16 +43,36 @@ namespace dchat
void setCharacterSize(unsigned int characterSize);
void setFillColor(sf::Color color);
void setLineSpacing(float lineSpacing);
+ void setEditable(bool editable);
// Warning: won't update until @draw is called
float getHeight() const;
+ void processEvent(const sf::Event &event);
+
// Performs culling. @updateGeometry is called even if text is not visible if text is dirty, because updateGeometry might change the dimension of the text and make is visible
void draw(sf::RenderTarget &target, Cache &cache);
private:
+ enum class CaretMoveDirection : u8
+ {
+ NONE,
+ UP,
+ DOWN,
+ HOME,
+ END
+ };
+
void stringSplitElements(sf::String &stringToSplit, usize startIndex);
void updateGeometry();
- private:
+ void updateCaret();
+ bool isCaretAtEnd() const;
+ int getStartOfLine(int startIndex) const;
+ int getEndOfLine(int startIndex) const;
+ int getRowByPosition(const sf::Vector2f &position) const;
+
+ int getPreviousLineClosestPosition(int startIndex) const;
+ int getNextLineClosestPosition(int startIndex) const;
+ private:
sf::String str;
const sf::Font *font;
unsigned int characterSize;
@@ -62,9 +82,16 @@ namespace dchat
sf::Color color;
sf::Color urlColor;
bool dirty;
+ bool dirtyText;
+ bool dirtyCaret;
bool plainText;
+ bool editable;
+ CaretMoveDirection caretMoveDirection;
float totalHeight;
float lineSpacing;
std::vector<TextElement> textElements;
+
+ int caretIndex;
+ sf::Vector2f caretPosition;
};
}
diff --git a/src/Cache.cpp b/src/Cache.cpp
index cba346b..c795595 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -196,22 +196,32 @@ namespace dchat
int exitStatus;
if(it->process->try_get_exit_status(exitStatus))
{
+ ContentByUrlResult contentByUrlResult;
bool failed = exitStatus != 0;
- if(!failed)
+ if(failed)
+ {
+ contentByUrlResult = { (sf::Texture*)nullptr, ContentByUrlResult::Type::FAILED_DOWNLOAD };
+ }
+ else
{
boost::filesystem::path filepath = getImagesDir();
odhtdb::Hash urlHash(it->url.data(), it->url.size());
filepath /= urlHash.toString();
- ContentByUrlResult contentByUrlResult = loadImageFromFile(filepath);
- imageDownloadMutex.lock();
- contentUrlCache[it->url] = contentByUrlResult;
- imageDownloadMutex.unlock();
+ contentByUrlResult = loadImageFromFile(filepath);
if(contentByUrlResult.type == ContentByUrlResult::Type::CACHED)
{
printf("Download content from url: %s\n", it->url.c_str());
}
}
+
+ imageDownloadMutex.lock();
+ contentUrlCache[it->url] = contentByUrlResult;
+ boost::filesystem::path downloadingFilepath = getImagesDir() / ".downloading";
+ // Intentionally ignore failure, program should not crash if we fail to remove these files...
+ boost::system::error_code err;
+ boost::filesystem::remove(downloadingFilepath, err);
+ imageDownloadMutex.unlock();
it = imageDownloadProcesses.erase(it);
}
else
@@ -243,6 +253,18 @@ namespace dchat
downloadWaitThread.join();
}
+ void replaceFileIgnoreError(const boost::filesystem::path &path)
+ {
+ try
+ {
+ fileReplace(path, StringView());
+ }
+ catch(FileException &e)
+ {
+ fprintf(stderr, "Failed to replace file: %s, reason: %s\n", path.string().c_str(), e.what());
+ }
+ }
+
const ContentByUrlResult Cache::getContentByUrl(const string &url, int downloadLimitBytes)
{
lock_guard<mutex> lock(imageDownloadMutex);
@@ -255,6 +277,15 @@ namespace dchat
odhtdb::Hash urlHash(url.data(), url.size());
filepath /= urlHash.toString();
+ boost::filesystem::path downloadingFilepath = getImagesDir() / ".downloading";
+ if(boost::filesystem::exists(downloadingFilepath))
+ {
+ // Intentionally ignore failure, program should not crash if we fail to remove these files...
+ boost::system::error_code err;
+ boost::filesystem::remove(filepath, err);
+ boost::filesystem::remove(downloadingFilepath, err);
+ }
+
ContentByUrlResult contentByUrlResult = loadImageFromFile(filepath);
if(contentByUrlResult.type == ContentByUrlResult::Type::CACHED)
{
@@ -268,6 +299,7 @@ namespace dchat
return contentByUrlResult;
}
+ replaceFileIgnoreError(downloadingFilepath);
ContentByUrlResult result((sf::Texture*)nullptr, ContentByUrlResult::Type::DOWNLOADING);
contentUrlCache[url] = result;
diff --git a/src/Channel.cpp b/src/Channel.cpp
index f8e79c6..2bd1b0b 100644
--- a/src/Channel.cpp
+++ b/src/Channel.cpp
@@ -31,6 +31,7 @@ namespace dchat
{
Message *message = new Message(&systemUser, u8"pepedab https://discordemoji.com/assets/emoji/PepeDab.gif coggers https://discordemoji.com/assets/emoji/COGGERS.gif check out this url http://www.grandtournation.com/6808/start-date-of-the-grand-tour-season-3-confirmed-mark-your-calendars/ owo");
+ message->text.setEditable(true);
messageBoard.addMessage(message);
}
diff --git a/src/ChannelSidePanel.cpp b/src/ChannelSidePanel.cpp
index 8d0b714..89a550a 100644
--- a/src/ChannelSidePanel.cpp
+++ b/src/ChannelSidePanel.cpp
@@ -33,7 +33,7 @@ namespace dchat
{
float posY = ChannelTopPanel::getHeight();
auto windowSize = window.getSize();
- sf::RectangleShape rect(sf::Vector2f(getWidth(), windowSize.y));
+ sf::RectangleShape rect(sf::Vector2f(getWidth(), windowSize.y - ChannelTopPanel::getHeight()));
rect.setPosition(0.0f, posY);
rect.setFillColor(ColorScheme::getPanelColor());
window.draw(rect);
@@ -68,6 +68,6 @@ namespace dchat
float ChannelSidePanel::getWidth()
{
- return WIDTH * Settings::getScaling();
+ return floor(WIDTH * Settings::getScaling());
}
}
diff --git a/src/MessageBoard.cpp b/src/MessageBoard.cpp
index cee3e50..d73dc84 100644
--- a/src/MessageBoard.cpp
+++ b/src/MessageBoard.cpp
@@ -238,6 +238,11 @@ namespace dchat
selectingTextStart.x = mousePos.x;
selectingTextStart.y = mousePos.y;
}
+
+ for(Message *message : messages)
+ {
+ message->text.processEvent(event);
+ }
}
void MessageBoard::draw(sf::RenderWindow &window, Cache &cache)
diff --git a/src/Text.cpp b/src/Text.cpp
index bc23235..be147db 100644
--- a/src/Text.cpp
+++ b/src/Text.cpp
@@ -22,8 +22,13 @@ namespace dchat
color(sf::Color::White),
urlColor(URL_COLOR),
dirty(false),
+ dirtyText(false),
+ dirtyCaret(false),
plainText(false),
- totalHeight(0.0f)
+ editable(false),
+ caretMoveDirection(CaretMoveDirection::NONE),
+ totalHeight(0.0f),
+ caretIndex(0)
{
}
@@ -36,32 +41,28 @@ namespace dchat
color(sf::Color::White),
urlColor(URL_COLOR),
dirty(true),
+ dirtyText(false),
+ dirtyCaret(false),
plainText(_plainText),
+ editable(false),
+ caretMoveDirection(CaretMoveDirection::NONE),
totalHeight(0.0f),
- lineSpacing(0.0f)
+ lineSpacing(0.0f),
+ caretIndex(0)
{
setString(_str);
}
+
void Text::setString(const sf::String &str)
{
if(str != this->str)
{
this->str = str;
dirty = true;
- textElements.clear();
- stringSplitElements(this->str, 0);
+ dirtyText = true;
}
}
- void Text::appendStringNewLine(const sf::String &str)
- {
- usize prevSize = this->str.getSize();
- this->str += '\n';
- this->str += str;
- dirty = true;
- stringSplitElements(this->str, prevSize);
- }
-
void Text::setPosition(float x, float y)
{
position.x = x;
@@ -109,6 +110,20 @@ namespace dchat
}
}
+ void Text::setEditable(bool editable)
+ {
+ if(editable != this->editable)
+ {
+ this->editable = editable;
+ if(!plainText)
+ {
+ dirty = true;
+ dirtyText = true;
+ }
+ dirtyCaret = true;
+ }
+ }
+
float Text::getHeight() const
{
return totalHeight;
@@ -142,7 +157,7 @@ namespace dchat
{
StringViewUtf32 wholeStr(&stringToSplit[startIndex], stringToSplit.getSize() - startIndex);
textElements.push_back({ wholeStr, TextElement::Type::TEXT });
- if(plainText)
+ if(plainText || editable)
return;
const char *httpStrRaw = "http://";
@@ -159,9 +174,9 @@ namespace dchat
static_assert(sizeof(*parentheseStr.getData()) == sizeof(u32), "sf::String size has changed...");
std::vector<TextElement> newTextElements;
- for(size_t i = 0; i < textElements.size(); ++i)
+ // Split emoji
+ for(const TextElement &textElement : textElements)
{
- TextElement textElement = textElements[i];
if(textElement.type != TextElement::Type::TEXT)
{
newTextElements.push_back(textElement);
@@ -199,10 +214,10 @@ namespace dchat
}
textElements = newTextElements;
+ // Split http
newTextElements.clear();
- for(size_t i = 0; i < textElements.size(); ++i)
+ for(const TextElement &textElement : textElements)
{
- TextElement textElement = textElements[i];
if(textElement.type != TextElement::Type::TEXT)
{
newTextElements.push_back(textElement);
@@ -225,10 +240,10 @@ namespace dchat
}
textElements = newTextElements;
+ // Split https
newTextElements.clear();
- for(size_t i = 0; i < textElements.size(); ++i)
+ for(const TextElement &textElement : textElements)
{
- TextElement textElement = textElements[i];
if(textElement.type != TextElement::Type::TEXT)
{
newTextElements.push_back(textElement);
@@ -333,10 +348,13 @@ namespace dchat
prevCodePoint = codePoint;
glyphPos.x += kerning;
+ usize vertexStart = vertexOffset + i * 4;
+
switch(codePoint)
{
case ' ':
{
+ vertices[vertexStart].position = sf::Vector2f(glyphPos.x, glyphPos.y - vspace);
glyphPos.x += hspace;
if(glyphPos.x > maxWidth * 0.5f)
{
@@ -347,6 +365,7 @@ namespace dchat
}
case '\t':
{
+ vertices[vertexStart].position = sf::Vector2f(glyphPos.x, glyphPos.y - vspace);
glyphPos.x += (hspace * TAB_WIDTH);
if(glyphPos.x > maxWidth * 0.5f)
{
@@ -357,12 +376,14 @@ namespace dchat
}
case '\n':
{
+ vertices[vertexStart].position = sf::Vector2f(glyphPos.x, glyphPos.y - vspace);
glyphPos.x = 0.0f;
glyphPos.y += vspace + lineSpacing;
continue;
}
case '\v':
{
+ vertices[vertexStart].position = sf::Vector2f(glyphPos.x, glyphPos.y - vspace);
glyphPos.y += (vspace * TAB_WIDTH) + lineSpacing;
continue;
}
@@ -408,10 +429,10 @@ namespace dchat
sf::Color fontColor = (textElement.type == TextElement::Type::TEXT ? color : urlColor);
- vertices[vertexOffset + i * 4 + 0] = { vertexTopLeft, fontColor, textureTopLeft };
- vertices[vertexOffset + i * 4 + 1] = { vertexTopRight, fontColor, textureTopRight };
- vertices[vertexOffset + i * 4 + 2] = { vertexBottomRight, fontColor, textureBottomRight };
- vertices[vertexOffset + i * 4 + 3] = { vertexBottomLeft, fontColor, textureBottomLeft };
+ vertices[vertexStart + 0] = { vertexTopLeft, fontColor, textureTopLeft };
+ vertices[vertexStart + 1] = { vertexTopRight, fontColor, textureTopRight };
+ vertices[vertexStart + 2] = { vertexBottomRight, fontColor, textureBottomRight };
+ vertices[vertexStart + 3] = { vertexBottomLeft, fontColor, textureBottomLeft };
glyphPos.x += glyph.advance;
}
@@ -433,14 +454,256 @@ namespace dchat
totalHeight = glyphPos.y + vspace + lineSpacing;
}
+ void Text::updateCaret()
+ {
+ assert(!dirty && !dirtyText);
+ switch(caretMoveDirection)
+ {
+ case CaretMoveDirection::UP:
+ {
+ caretIndex = getPreviousLineClosestPosition(caretIndex);
+ break;
+ }
+ case CaretMoveDirection::DOWN:
+ {
+ caretIndex = getNextLineClosestPosition(caretIndex);
+ break;
+ }
+ case CaretMoveDirection::HOME:
+ {
+ caretIndex = getStartOfLine(caretIndex);
+ break;
+ }
+ case CaretMoveDirection::END:
+ {
+ caretIndex = getEndOfLine(caretIndex);
+ break;
+ }
+ default:
+ // Ignore...
+ break;
+ }
+
+ usize vertexIndex = caretIndex * 4;
+ const sf::Vertex &topLeftVertex = vertices[vertexIndex];
+ caretPosition = topLeftVertex.position;
+ }
+
+ bool Text::isCaretAtEnd() const
+ {
+ assert(!dirty && !dirtyText);
+ return textElements[0].text.size == 0 || caretIndex == textElements[0].text.size;
+ }
+
+ // TODO: This can be optimized by using step algorithm (jump to middle of vertices list and check if it's at next row, if not then divide by 2 again, and do this recursively)
+ int Text::getStartOfLine(int startIndex) const
+ {
+ assert(!dirty && !dirtyText);
+ int numVertices = vertices.getVertexCount();
+ if(numVertices < 4) return 0;
+
+ usize vertexIndex = caretIndex * 4;
+ const sf::Vertex &startTopLeftVertex = vertices[vertexIndex];
+ int startRow = getRowByPosition(startTopLeftVertex.position);
+ for(int i = startIndex * 4; i >= 0; i -= 4)
+ {
+ const sf::Vertex &topLeftVertex = vertices[i];
+ int row = getRowByPosition(topLeftVertex.position);
+ if(row != startRow)
+ {
+ return std::max(0, i / 4 + 1);
+ }
+ }
+ return 0;
+ }
+
+ // TODO: This can be optimized by using step algorithm (jump to middle of vertices list and check if it's at next row, if not then divide by 2 again, and do this recursively)
+ int Text::getEndOfLine(int startIndex) const
+ {
+ assert(!dirty && !dirtyText);
+ int numVertices = vertices.getVertexCount();
+ if(numVertices < 4) return 0;
+
+ usize vertexIndex = caretIndex * 4;
+ const sf::Vertex &startTopLeftVertex = vertices[vertexIndex];
+ int startRow = getRowByPosition(startTopLeftVertex.position);
+ for(int i = startIndex * 4; i < numVertices; i += 4)
+ {
+ const sf::Vertex &topLeftVertex = vertices[i];
+ int row = getRowByPosition(topLeftVertex.position);
+ if(row != startRow)
+ {
+ return std::max(0, i / 4 - 1);
+ }
+ }
+ return numVertices / 4;
+ }
+
+ // TODO: This can be optimized by using step algorithm (jump to middle of vertices list and check if it's at next row, if not then divide by 2 again, and do this recursively)
+ int Text::getPreviousLineClosestPosition(int startIndex) const
+ {
+ assert(!dirty && !dirtyText);
+ int numVertices = vertices.getVertexCount();
+ if(numVertices < 4) return 0;
+
+ usize vertexIndex = caretIndex * 4;
+ const sf::Vertex &startTopLeftVertex = vertices[vertexIndex];
+ int startRow = getRowByPosition(startTopLeftVertex.position);
+ int closestIndex = -1;
+ float closestAbsoluteDiffX = 0.0f;
+ for(int i = startIndex * 4; i >= 0; i -= 4)
+ {
+ const sf::Vertex &topLeftVertex = vertices[i];
+ int row = getRowByPosition(topLeftVertex.position);
+ float absoluteDiffX = fabs(topLeftVertex.position.x - startTopLeftVertex.position.x);
+ int rowDiff = abs(row - startRow);
+ if(rowDiff > 1)
+ break;
+
+ if(rowDiff == 1 && (closestIndex == -1 || absoluteDiffX < closestAbsoluteDiffX))
+ {
+ closestIndex = i;
+ closestAbsoluteDiffX = absoluteDiffX;
+ }
+ }
+
+ if(closestIndex != -1)
+ return closestIndex / 4;
+
+ return 0;
+ }
+
+ // TODO: This can be optimized by using step algorithm (jump to middle of vertices list and check if it's at next row, if not then divide by 2 again, and do this recursively)
+ int Text::getNextLineClosestPosition(int startIndex) const
+ {
+ assert(!dirty && !dirtyText);
+ int numVertices = vertices.getVertexCount();
+ if(numVertices < 4) return 0;
+
+ usize vertexIndex = caretIndex * 4;
+ const sf::Vertex &startTopLeftVertex = vertices[vertexIndex];
+ int startRow = getRowByPosition(startTopLeftVertex.position);
+ int closestIndex = -1;
+ float closestAbsoluteDiffX = 0.0f;
+ for(int i = startIndex * 4; i < numVertices; i += 4)
+ {
+ const sf::Vertex &topLeftVertex = vertices[i];
+ int row = getRowByPosition(topLeftVertex.position);
+ float absoluteDiffX = fabs(topLeftVertex.position.x - startTopLeftVertex.position.x);
+ int rowDiff = abs(row - startRow);
+ if(rowDiff > 1)
+ break;
+
+ if(rowDiff == 1 && (closestIndex == -1 || absoluteDiffX < closestAbsoluteDiffX))
+ {
+ closestIndex = i;
+ closestAbsoluteDiffX = absoluteDiffX;
+ }
+ }
+
+ if(closestIndex != -1)
+ return closestIndex / 4;
+
+ return numVertices / 4;
+ }
+
+ int Text::getRowByPosition(const sf::Vector2f &position) const
+ {
+ assert(!dirty && !dirtyText);
+ const float vspace = font->getLineSpacing(characterSize);
+ return static_cast<int>(1.0f + position.y / (vspace + lineSpacing));
+ }
+
+ void Text::processEvent(const sf::Event &event)
+ {
+ if(!editable) return;
+
+ if(event.type == sf::Event::KeyPressed)
+ {
+ if(event.key.code == sf::Keyboard::Left && caretIndex > 0)
+ {
+ --caretIndex;
+ dirtyCaret = true;
+ }
+ else if(event.key.code == sf::Keyboard::Right && !isCaretAtEnd())
+ {
+ ++caretIndex;
+ dirtyCaret = true;
+ }
+ else if(event.key.code == sf::Keyboard::BackSpace && caretIndex > 0)
+ {
+ auto strBefore = str.substring(0, caretIndex - 1);
+ auto strAfter = str.substring(caretIndex);
+ setString(strBefore + strAfter);
+ --caretIndex;
+ dirtyCaret = true;
+ }
+ else if(event.key.code == sf::Keyboard::Delete && !isCaretAtEnd())
+ {
+ auto strBefore = str.substring(0, caretIndex);
+ auto strAfter = str.substring(caretIndex + 1);
+ setString(strBefore + strAfter);
+ }
+ else if(event.key.code == sf::Keyboard::Up)
+ {
+ caretMoveDirection = CaretMoveDirection::UP;
+ }
+ else if(event.key.code == sf::Keyboard::Down)
+ {
+ caretMoveDirection = CaretMoveDirection::DOWN;
+ }
+ else if(event.key.code == sf::Keyboard::Home)
+ {
+ caretMoveDirection = CaretMoveDirection::HOME;
+ }
+ else if(event.key.code == sf::Keyboard::End)
+ {
+ caretMoveDirection = CaretMoveDirection::END;
+ }
+ }
+ else if(event.type == sf::Event::TextEntered)
+ {
+ if(event.text.unicode == 8 || event.text.unicode == 127) // backspace, del
+ return;
+
+ if(isCaretAtEnd())
+ str += event.text.unicode;
+ else
+ {
+ auto strBefore = str.substring(0, caretIndex);
+ auto strAfter = str.substring(caretIndex);
+ str = strBefore + event.text.unicode + strAfter;
+ }
+
+ ++caretIndex;
+ dirty = true;
+ dirtyText = true;
+ dirtyCaret = true;
+ }
+ }
+
void Text::draw(sf::RenderTarget &target, Cache &cache)
{
+ if(dirtyText)
+ {
+ textElements.clear();
+ stringSplitElements(this->str, 0);
+ dirtyText = false;
+ }
+
if(dirty)
{
updateGeometry();
dirty = false;
}
+ if(dirtyCaret || caretMoveDirection != CaretMoveDirection::NONE)
+ {
+ updateCaret();
+ dirtyCaret = false;
+ caretMoveDirection = CaretMoveDirection::NONE;
+ }
+
float vspace = font->getLineSpacing(characterSize);
sf::RenderStates states;
@@ -458,6 +721,7 @@ namespace dchat
states.texture = &font->getTexture(characterSize);
target.draw(vertices, states);
+ pos.y -= floor(vspace);
for(TextElement &textElement : textElements)
{
if(textElement.type == TextElement::Type::EMOJI)
@@ -551,9 +815,8 @@ namespace dchat
}
case ContentByUrlResult::CachedType::WEB_PAGE_PREVIEW:
{
- const float previewWidth = floor(imageHeight * 1.77f);
+ const float previewWidth = fmin(maxWidth, floor(imageHeight * 1.77f));
- // No need to perform culling here, that is done in @Text draw function
contentByUrlResult.webPagePreview->title.setCharacterSize(characterSize);
contentByUrlResult.webPagePreview->title.setMaxWidth(previewWidth);
contentByUrlResult.webPagePreview->title.setPosition(pos);
@@ -573,5 +836,15 @@ namespace dchat
}
}
}
+
+ if(!editable) return;
+
+ //float rows = floor(totalHeight / (vspace + lineSpacing));
+ const float caretRow = getRowByPosition(caretPosition);
+
+ sf::RectangleShape caretRect(sf::Vector2f(2.0f, floor(vspace)));
+ caretRect.setFillColor(sf::Color::White);
+ caretRect.setPosition(sf::Vector2f(floor(pos.x + caretPosition.x), floor(pos.y + caretRow * (vspace + lineSpacing))));
+ target.draw(caretRect);
}
}
diff --git a/src/UsersSidePanel.cpp b/src/UsersSidePanel.cpp
index f665d1f..9c04062 100644
--- a/src/UsersSidePanel.cpp
+++ b/src/UsersSidePanel.cpp
@@ -20,7 +20,7 @@ namespace dchat
{
float posY = ChannelTopPanel::getHeight();
auto windowSize = window.getSize();
- sf::RectangleShape rect(sf::Vector2f(getWidth(), windowSize.y));
+ sf::RectangleShape rect(sf::Vector2f(getWidth(), windowSize.y - ChannelTopPanel::getHeight()));
rect.setFillColor(ColorScheme::getPanelColor());
rect.setPosition(windowSize.x - getWidth(), posY);
window.draw(rect);
@@ -45,6 +45,6 @@ namespace dchat
float UsersSidePanel::getWidth()
{
- return WIDTH * Settings::getScaling();
+ return floor(WIDTH * Settings::getScaling());
}
}