aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
6 files changed, 346 insertions, 35 deletions
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());
}
}