aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2020-09-28 13:32:34 +0200
committerdec05eba <dec05eba@protonmail.com>2020-09-28 13:32:34 +0200
commit4277763df5c1dac8ff389d3bfd138f03acc7f1e2 (patch)
treeb0c3fc77ea601f105308d42840adb1ce2050b414 /src
parenta16cfdc4f6cd14d576760c100a817c08f45be394 (diff)
Implement text editing with navigation and multilingual fonts
Diffstat (limited to 'src')
-rw-r--r--src/Entry.cpp72
-rw-r--r--src/ImageViewer.cpp1
-rw-r--r--src/QuickMedia.cpp95
-rw-r--r--src/SearchBar.cpp3
-rw-r--r--src/StringUtils.cpp10
-rw-r--r--src/Text.cpp418
-rw-r--r--src/plugins/Matrix.cpp6
-rw-r--r--src/plugins/Plugin.cpp4
8 files changed, 368 insertions, 241 deletions
diff --git a/src/Entry.cpp b/src/Entry.cpp
new file mode 100644
index 0000000..977feab
--- /dev/null
+++ b/src/Entry.cpp
@@ -0,0 +1,72 @@
+#include "../include/Entry.hpp"
+#include <SFML/Graphics/RenderWindow.hpp>
+#include <SFML/Graphics/RectangleShape.hpp>
+#include <SFML/Window/Event.hpp>
+#include <cmath>
+
+const float background_margin_horizontal = 15.0f;
+const float padding_vertical = 5.0f;
+const float background_margin_vertical = 4.0f;
+
+namespace QuickMedia {
+ Entry::Entry(const std::string &placeholder_text, sf::Font *font, sf::Font *cjk_font) :
+ on_submit_callback(nullptr),
+ text("", font, cjk_font, 18, 0.0f),
+ width(0.0f),
+ background(sf::Vector2f(1.0f, 1.0f), 10.0f, 10),
+ placeholder(placeholder_text, *font, 18)
+ {
+ text.setEditable(true);
+ background.setFillColor(sf::Color(55, 60, 68));
+ placeholder.setFillColor(sf::Color(255, 255, 255, 100));
+ }
+
+ void Entry::process_event(sf::Event &event) {
+ text.processEvent(event);
+ if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Enter && !event.key.shift) {
+ if(on_submit_callback) {
+ bool clear_text = on_submit_callback(text.getString());
+ if(clear_text)
+ text.setString("");
+ }
+ }
+ }
+
+ // TODO: Set the max number of visible lines and use glScissor to cut off the lines outsides
+ // (and also split text into lines to not draw them at all once they are not inside the scissor box)
+ void Entry::draw(sf::RenderWindow &window) {
+ background.setSize(sf::Vector2f(width, get_height()));
+ window.draw(background);
+ if(text.getString().isEmpty() && !text.isEditable()) {
+ window.draw(placeholder);
+ //sf::Vector2f placeholder_pos = placeholder.getPosition();
+ //const float caret_margin = 2.0f;
+ //const float vspace = placeholder.getFont()->getLineSpacing(18);
+ //sf::RectangleShape caret_rect(sf::Vector2f(2.0f, floor(vspace - caret_margin * 2.0f)));
+ //caret_rect.setPosition(floor(placeholder_pos.x), floor(placeholder_pos.y + caret_margin));
+ //window.draw(caret_rect);
+ } else {
+ text.draw(window);
+ }
+ }
+
+ void Entry::set_editable(bool editable) {
+ text.setEditable(editable);
+ }
+
+ void Entry::set_position(const sf::Vector2f &pos) {
+ background.setPosition(pos);
+ text.setPosition(pos + sf::Vector2f(background_margin_horizontal, background_margin_vertical));
+ placeholder.setPosition(pos + sf::Vector2f(background_margin_horizontal, background_margin_vertical + 7.0f));
+ }
+
+ void Entry::set_max_width(float width) {
+ this->width = width;
+ text.setMaxWidth(this->width - background_margin_horizontal * 2.0f);
+ }
+
+ float Entry::get_height() {
+ text.updateGeometry();
+ return std::floor(text.getHeight() + background_margin_vertical * 2.0f + padding_vertical *2.0f);
+ }
+} \ No newline at end of file
diff --git a/src/ImageViewer.cpp b/src/ImageViewer.cpp
index c45e35d..c929086 100644
--- a/src/ImageViewer.cpp
+++ b/src/ImageViewer.cpp
@@ -4,6 +4,7 @@
#include "../plugins/Manga.hpp"
#include <cmath>
#include <SFML/Window/Event.hpp>
+#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/RectangleShape.hpp>
namespace QuickMedia {
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index a0d8508..a854613 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -18,6 +18,7 @@
#include "../include/ImageViewer.hpp"
#include "../include/ImageUtils.hpp"
#include "../include/base64_url.hpp"
+#include "../include/Entry.hpp"
#include <SFML/Graphics/RectangleShape.hpp>
#include <SFML/Window/Clipboard.hpp>
@@ -3107,6 +3108,7 @@ namespace QuickMedia {
} else if(navigation_stage == NavigationStage::POSTING_COMMENT) {
// TODO: Show "Posting..." when posting comment
} else if(navigation_stage == NavigationStage::VIEWING_ATTACHED_IMAGE) {
+ // TODO: Use image instead of data with string. texture->loadFromMemory creates a temporary image anyways that parses the string.
std::string image_data;
if(downloading_image && load_image_future.valid() && load_image_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
downloading_image = false;
@@ -3341,22 +3343,16 @@ namespace QuickMedia {
// get_all_room_messages is not needed here because its done in the loop, where the initial timeout is 0ms
- SearchBar chat_input(*font, &plugin_logo, "Send a message...");
- chat_input.set_background_color(sf::Color::Transparent);
- chat_input.padding_vertical = 10.0f;
-
- // TODO: Scroll to bottom when receiving new messages, but only if we are already at the bottom?
-
- // TODO: Filer for rooms and settings
- chat_input.onTextUpdateCallback = nullptr;
-
Page new_page = Page::CHAT;
+ bool typing_message = false;
- // TODO: Show post message immediately, instead of waiting for sync. Otherwise it can take a while until we receive the message,
- // which happens when uploading an image.
- chat_input.onTextSubmitCallback = [matrix, &tabs, &selected_tab, &current_room_id, &new_page](const std::string &text) -> bool {
+ sf::Sprite logo_sprite(plugin_logo);
+
+ Entry chat_input("Press ctrl+c to begin writing a message...", font.get(), cjk_font.get());
+ chat_input.set_editable(false);
+ chat_input.on_submit_callback = [matrix, &chat_input, &tabs, &selected_tab, &current_room_id, &new_page, &typing_message](const sf::String &text) {
if(tabs[selected_tab].type == ChatTabType::MESSAGES) {
- if(text.empty())
+ if(text.isEmpty())
return false;
if(text[0] == '/') {
@@ -3364,9 +3360,13 @@ namespace QuickMedia {
strip(command);
if(command == "/upload") {
new_page = Page::FILE_MANAGER;
+ chat_input.set_editable(false);
+ typing_message = false;
return true;
} else if(command == "/logout") {
new_page = Page::CHAT_LOGIN;
+ chat_input.set_editable(false);
+ typing_message = false;
return true;
} else {
fprintf(stderr, "Error: invalid command: %s, expected /upload\n", command.c_str());
@@ -3375,11 +3375,14 @@ namespace QuickMedia {
}
// TODO: Make asynchronous
- if(matrix->post_message(current_room_id, text) != PluginResult::OK) {
+ if(matrix->post_message(current_room_id, text) == PluginResult::OK) {
+ chat_input.set_editable(false);
+ typing_message = false;
+ return true;
+ } else {
show_notification("QuickMedia", "Failed to post matrix message", Urgency::CRITICAL);
return false;
}
- return true;
}
return false;
};
@@ -3413,6 +3416,9 @@ namespace QuickMedia {
sf::RectangleShape more_messages_below_rect;
more_messages_below_rect.setFillColor(sf::Color(128, 50, 50));
+ sf::RectangleShape chat_input_shade;
+ chat_input_shade.setFillColor(sf::Color(33, 38, 44));
+
sf::Clock start_typing_timer;
const double typing_timeout_seconds = 3.0;
bool typing = false;
@@ -3430,13 +3436,15 @@ namespace QuickMedia {
sf::Clock frame_timer;
+ float prev_chat_height = chat_input.get_height();
+
while (current_page == Page::CHAT) {
sf::Int32 frame_time_ms = frame_timer.restart().asMilliseconds();
while (window.pollEvent(event)) {
base_event_handler(event, Page::EXIT, false, false, false);
if(event.type == sf::Event::Resized || event.type == sf::Event::GainedFocus) {
redraw = true;
- } else if(event.type == sf::Event::KeyPressed) {
+ } else if(event.type == sf::Event::KeyPressed && !typing_message) {
if(event.key.code == sf::Keyboard::Up || event.key.code == sf::Keyboard::PageUp || event.key.code == sf::Keyboard::Home) {
bool hit_top = false;
switch(event.key.code) {
@@ -3511,9 +3519,14 @@ namespace QuickMedia {
}
}
- if(tabs[selected_tab].type == ChatTabType::MESSAGES) {
+ if(!typing_message && tabs[selected_tab].type == ChatTabType::MESSAGES && event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::C && event.key.control) {
+ chat_input.set_editable(true);
+ typing_message = true;
+ }
+
+ if(typing_message && tabs[selected_tab].type == ChatTabType::MESSAGES) {
if(event.type == sf::Event::TextEntered) {
- chat_input.onTextEntered(event.text.unicode);
+ //chat_input.onTextEntered(event.text.unicode);
// TODO: Also show typing event when ctrl+v pasting?
if(event.text.unicode != 13) { // Return key
start_typing_timer.restart();
@@ -3523,8 +3536,12 @@ namespace QuickMedia {
}
typing = true;
}
+ } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::Escape) {
+ chat_input.set_editable(false);
+ typing_message = false;
}
- chat_input.on_event(event);
+ //chat_input.on_event(event);
+ chat_input.process_event(event);
} else if(tabs[selected_tab].type == ChatTabType::ROOMS && event.type == sf::Event::KeyReleased && event.key.code == sf::Keyboard::Enter) {
BodyItem *selected_item = tabs[selected_tab].body->get_selected();
if(selected_item) {
@@ -3607,10 +3624,22 @@ namespace QuickMedia {
const float tab_shade_height = tab_spacer_height + std::floor(tab_vertical_offset) + tab_height + 10.0f;
+ const float chat_height = chat_input.get_height();
+ if(std::abs(chat_height - prev_chat_height) > 1.0f) {
+ prev_chat_height = chat_height;
+ redraw = true;
+ }
if(redraw) {
+ const float logo_padding_x = 15.0f;
+ const float chat_input_padding_x = 15.0f;
+ const float chat_input_padding_y = 15.0f;
+
+ //chat_height += 10.0f;
redraw = false;
- chat_input.onWindowResize(window_size);
- chat_input.set_vertical_position(window_size.y - chat_input.getBottomWithoutShadow());
+ chat_input.set_max_width(window_size.x - (logo_padding_x + plugin_logo.getSize().x + chat_input_padding_x * 2.0f));
+ chat_input.set_position(sf::Vector2f(logo_padding_x + plugin_logo.getSize().x + chat_input_padding_x, window_size.y - chat_height - chat_input_padding_y));
+ //chat_input.onWindowResize(window_size);
+ //chat_input.set_vertical_position(window_size.y - chat_input.getBottomWithoutShadow());
float body_padding_horizontal = 25.0f;
float body_padding_vertical = 5.0f;
@@ -3620,15 +3649,17 @@ namespace QuickMedia {
body_padding_horizontal = 0.0f;
}
- float input_bottom = chat_input.getBottomWithoutShadow();
- if(tabs[selected_tab].type != ChatTabType::MESSAGES)
- input_bottom = 0.0f;
+ chat_input_shade.setSize(sf::Vector2f(window_size.x, chat_input.get_height() + chat_input_padding_y * 2.0f));
+ chat_input_shade.setPosition(0.0f, window_size.y - chat_input_shade.getSize().y);
+
body_pos = sf::Vector2f(body_padding_horizontal, body_padding_vertical + tab_shade_height);
- body_size = sf::Vector2f(body_width, window_size.y - input_bottom - body_padding_vertical - tab_shade_height);
+ body_size = sf::Vector2f(body_width, window_size.y - chat_input_shade.getSize().y - body_padding_vertical + tab_shade_height);
//get_body_dimensions(window_size, &chat_input, body_pos, body_size, true);
more_messages_below_rect.setSize(sf::Vector2f(window_size.x, gradient_height));
- more_messages_below_rect.setPosition(0.0f, std::floor(window_size.y - chat_input.getBottomWithoutShadow() - gradient_height));
+ more_messages_below_rect.setPosition(0.0f, std::floor(window_size.y - chat_input_shade.getSize().y - gradient_height));
+
+ logo_sprite.setPosition(logo_padding_x, window_size.y - chat_input_shade.getSize().y * 0.5f - plugin_logo.getSize().y * 0.5f);
}
if(!sync_running && sync_timer.getElapsedTime().asMilliseconds() >= sync_min_time_ms) {
@@ -3681,16 +3712,12 @@ namespace QuickMedia {
fetching_previous_messages_running = false;
}
- chat_input.update();
+ //chat_input.update();
window.clear(back_color);
- if(tabs[selected_tab].type == ChatTabType::MESSAGES)
- chat_input.draw(window, false);
-
const float width_per_tab = window_size.x / tabs.size();
tab_background.setSize(sf::Vector2f(std::floor(width_per_tab - tab_margin_x * 2.0f), tab_height));
-
tabs[selected_tab].body->draw(window, body_pos, body_size);
const float tab_y = tab_spacer_height + std::floor(tab_vertical_offset + tab_height * 0.5f - (tab_text_size + 5.0f) * 0.5f);
@@ -3739,6 +3766,12 @@ namespace QuickMedia {
window.draw(more_messages_below_rect);
}
+ if(tabs[selected_tab].type == ChatTabType::MESSAGES) {
+ window.draw(chat_input_shade);
+ chat_input.draw(window); //chat_input.draw(window, false);
+ window.draw(logo_sprite);
+ }
+
window.display();
}
diff --git a/src/SearchBar.cpp b/src/SearchBar.cpp
index 419ca38..fa81c61 100644
--- a/src/SearchBar.cpp
+++ b/src/SearchBar.cpp
@@ -2,9 +2,12 @@
#include "../include/Scale.hpp"
#include <SFML/Window/Event.hpp>
#include <SFML/Window/Clipboard.hpp>
+#include <SFML/Graphics/RenderWindow.hpp>
#include <cmath>
#include <assert.h>
+// TODO: Use a seperate placeholder sf::Text instead of switching the text to placeholder text....
+
const sf::Color text_placeholder_color(255, 255, 255, 100);
const sf::Color front_color(55, 60, 68);
const float background_margin_horizontal = 15.0f;
diff --git a/src/StringUtils.cpp b/src/StringUtils.cpp
index f255971..111822e 100644
--- a/src/StringUtils.cpp
+++ b/src/StringUtils.cpp
@@ -4,10 +4,10 @@
namespace QuickMedia {
void string_split(const std::string &str, char delimiter, StringSplitCallback callback_func) {
size_t index = 0;
- while(true) {
+ while(index < str.size()) {
size_t new_index = str.find(delimiter, index);
if(new_index == std::string::npos)
- break;
+ new_index = str.size();
if(!callback_func(str.data() + index, new_index - index))
break;
@@ -19,11 +19,12 @@ namespace QuickMedia {
size_t string_replace_all(std::string &str, char old_char, const std::string &new_str) {
size_t num_replaced_substrings = 0;
size_t index = 0;
- while(true) {
+ while(index < str.size()) {
index = str.find(old_char, index);
if(index == std::string::npos)
break;
str.replace(index, 1, new_str);
+ index += new_str.size();
++num_replaced_substrings;
}
return num_replaced_substrings;
@@ -32,11 +33,12 @@ namespace QuickMedia {
size_t string_replace_all(std::string &str, const std::string &old_str, const std::string &new_str) {
size_t num_replaced_substrings = 0;
size_t index = 0;
- while(true) {
+ while(index < str.size()) {
index = str.find(old_str, index);
if(index == std::string::npos)
break;
str.replace(index, old_str.size(), new_str);
+ index += new_str.size();
++num_replaced_substrings;
}
return num_replaced_substrings;
diff --git a/src/Text.cpp b/src/Text.cpp
index 8f58e3d..3962374 100644
--- a/src/Text.cpp
+++ b/src/Text.cpp
@@ -1,6 +1,9 @@
#include "../include/Text.hpp"
#include <SFML/Graphics/RectangleShape.hpp>
#include <SFML/Window/Clipboard.hpp>
+#include <SFML/Window/Event.hpp>
+#include <SFML/Graphics/RenderTarget.hpp>
+#include <SFML/Graphics/Font.hpp>
#include <cmath>
#include <functional>
@@ -48,8 +51,8 @@ namespace QuickMedia
void Text::setString(sf::String str)
{
- if(str != this->str)
- {
+ //if(str != this->str)
+ //{
this->str = std::move(str);
dirty = true;
dirtyText = true;
@@ -58,7 +61,7 @@ namespace QuickMedia
caretIndex = this->str.getSize();
dirtyCaret = true;
}
- }
+ // }
}
const sf::String& Text::getString() const
@@ -156,6 +159,10 @@ namespace QuickMedia
}
}
+ bool Text::isEditable() const {
+ return editable;
+ }
+
float Text::getWidth() const
{
return boundingBox.width;
@@ -214,10 +221,18 @@ namespace QuickMedia
index += 1 + offset;
}
}
+
+ float Text::get_text_quad_left_side(const VertexRef &vertex_ref) const {
+ return vertices[vertex_ref.vertices_index][vertex_ref.index].position.x;
+ }
+
+ float Text::get_text_quad_right_side(const VertexRef &vertex_ref) const {
+ return vertices[vertex_ref.vertices_index][vertex_ref.index + 1].position.x;
+ }
- // Logic loosely based on https://github.com/SFML/SFML/wiki/Source:-CurvedText
void Text::updateGeometry(bool update_even_if_not_dirty) {
if(dirtyText) {
+ assert(dirty);
dirtyText = false;
splitTextByFont();
}
@@ -225,10 +240,11 @@ namespace QuickMedia
if(!update_even_if_not_dirty && !dirty)
return;
+ vertices_linear.clear();
vertices[0].clear();
vertices[1].clear();
float hspace = font->getGlyph(' ', characterSize, false).advance + characterSpacing;
- float vspace = font->getLineSpacing(characterSize);
+ float vspace = font->getLineSpacing(characterSize); // TODO: What about japanese font???
boundingBox = sf::FloatRect();
@@ -236,27 +252,27 @@ namespace QuickMedia
sf::Uint32 prevCodePoint = 0;
for(usize textElementIndex = 0; textElementIndex < textElements.size(); ++textElementIndex)
{
- size_t lastSpacingWordWrapIndex = -1;
- float lastSpacingAccumulatedOffset = 0.0f;
TextElement &textElement = textElements[textElementIndex];
const sf::Font *ff = font;
- size_t vertices_index = 0;
+ int vertices_index = 0;
if(textElement.is_japanese) {
ff = cjk_font;
vertices_index = 1;
}
usize vertexOffset = vertices[vertices_index].getVertexCount();
- vertices[vertices_index].resize(vertices[vertices_index].getVertexCount() + 4 * (textElement.text.size + 1));
+ vertices[vertices_index].resize(vertices[vertices_index].getVertexCount() + 4 * textElement.text.size); // TODO: Precalculate
textElement.position = glyphPos;
for(size_t i = 0; i < textElement.text.size; ++i)
{
sf::Uint32 codePoint = textElement.text[i];
+ // TODO: Make this work when combining multiple different fonts (for example latin and japanese).
+ // For japanese we could use a hack, because all japanese characters are monospace (exception being half-width characters).
float kerning = ff->getKerning(prevCodePoint, codePoint, characterSize);
prevCodePoint = codePoint;
glyphPos.x += kerning;
- usize vertexStart = vertexOffset + i * 4;
+ int vertexStart = vertexOffset + i * 4;
switch(codePoint)
{
@@ -267,67 +283,34 @@ namespace QuickMedia
vertices[vertices_index][vertexStart + 2] = { sf::Vector2f(glyphPos.x + hspace, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
vertices[vertices_index][vertexStart + 3] = { sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
glyphPos.x += hspace;
- if(glyphPos.x > maxWidth * 0.5f)
- {
- lastSpacingWordWrapIndex = i;
- lastSpacingAccumulatedOffset = glyphPos.x;
- }
+ vertices_linear.push_back({vertices_index, vertexStart, 0, codePoint});
continue;
}
case '\t':
{
+ const float char_width = hspace * TAB_WIDTH;
vertices[vertices_index][vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertices_index][vertexStart + 1] = { sf::Vector2f(glyphPos.x + hspace * TAB_WIDTH, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertices_index][vertexStart + 2] = { sf::Vector2f(glyphPos.x + hspace * TAB_WIDTH, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertexStart + 1] = { sf::Vector2f(glyphPos.x + char_width, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertexStart + 2] = { sf::Vector2f(glyphPos.x + char_width, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
vertices[vertices_index][vertexStart + 3] = { sf::Vector2f(glyphPos.x, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
- glyphPos.x += (hspace * TAB_WIDTH);
- if(glyphPos.x > maxWidth * 0.5f)
- {
- lastSpacingWordWrapIndex = i;
- lastSpacingAccumulatedOffset = glyphPos.x;
- }
+ glyphPos.x += char_width;
+ vertices_linear.push_back({vertices_index, vertexStart, 0, codePoint});
continue;
}
case '\n':
{
- vertices[vertices_index][vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertices_index][vertexStart + 1] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertices_index][vertexStart + 2] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertices_index][vertexStart + 3] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
glyphPos.x = 0.0f;
glyphPos.y += floor(vspace + lineSpacing);
+ vertices[vertices_index][vertexStart + 0] = { sf::Vector2f(0.0f, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertexStart + 1] = { sf::Vector2f(0.0f, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertexStart + 2] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
+ vertices[vertices_index][vertexStart + 3] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() };
+ vertices_linear.push_back({vertices_index, vertexStart, 0, codePoint});
continue;
}
}
const sf::Glyph &glyph = ff->getGlyph(codePoint, characterSize, false);
- // TODO: Fix wrap-around with multiple textElements. Right now it only wrap-arounds within the same textElement, so with mixed latin-japanese it will
- // wrap at character size rather than at whitespace
- if(glyphPos.x + glyph.advance > maxWidth)
- {
- // If there was a space in the text and text width is too long, then we need to word wrap at space index instead,
- // which means we need to change the position of all vertices after the space to the current vertex
- if(lastSpacingWordWrapIndex != (size_t)-1)
- {
- for(size_t j = lastSpacingWordWrapIndex; j < i; ++j)
- {
- for(size_t k = 0; k < 4; ++k)
- {
- sf::Vector2f &vertexPos = vertices[vertices_index][vertexOffset + j * 4 + k].position;
- vertexPos.x -= lastSpacingAccumulatedOffset;
- vertexPos.y += floor(vspace + lineSpacing);
- }
- }
-
- glyphPos.x -= lastSpacingAccumulatedOffset;
- lastSpacingWordWrapIndex = -1;
- lastSpacingAccumulatedOffset = 0.0f;
- }
- else
- glyphPos.x = 0.0f;
-
- glyphPos.y += floor(vspace + lineSpacing);
- }
sf::Vector2f vertexTopLeft(glyphPos.x + glyph.bounds.left, glyphPos.y + glyph.bounds.top);
sf::Vector2f vertexTopRight(glyphPos.x + glyph.bounds.left + glyph.bounds.width, glyphPos.y + glyph.bounds.top);
@@ -347,43 +330,110 @@ namespace QuickMedia
vertices[vertices_index][vertexStart + 3] = { vertexBottomLeft, fontColor, textureBottomLeft };
glyphPos.x += glyph.advance + characterSpacing;
+ vertices_linear.push_back({vertices_index, vertexStart, 0, codePoint});
}
- vertices[vertices_index][vertices[vertices_index].getVertexCount() - 4] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertices_index][vertices[vertices_index].getVertexCount() - 3] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertices_index][vertices[vertices_index].getVertexCount() - 2] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
- vertices[vertices_index][vertices[vertices_index].getVertexCount() - 1] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
+ //vertices[vertices_index][vertices[vertices_index].getVertexCount() - 4] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
+ //vertices[vertices_index][vertices[vertices_index].getVertexCount() - 3] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
+ //vertices[vertices_index][vertices[vertices_index].getVertexCount() - 2] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
+ //vertices[vertices_index][vertices[vertices_index].getVertexCount() - 1] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() };
prevCodePoint = 0;
}
-
- boundingBox.height = glyphPos.y + lineSpacing;
- boundingBox.height += vspace;
- for(size_t vertices_index = 0; vertices_index < 2; ++vertices_index) {
- usize numVertices = vertices[vertices_index].getVertexCount();
- for(usize i = 0; i < numVertices; i += 4)
- {
- const sf::Vertex &bottomRight = vertices[vertices_index][i + 2];
- boundingBox.width = std::max(boundingBox.width, bottomRight.position.x);
+ const float line_height = floor(vspace + lineSpacing);
+ float text_wrap_offset = 0.0f;
+ float text_offset_y = 0.0f;
+ int last_space_index = -1;
+ int num_lines = 1;
+ // TODO: Binary search?
+ for(int i = 0; i < (int)vertices_linear.size(); ++i) {
+ VertexRef &vertex_ref = vertices_linear[i];
+ switch(vertex_ref.codepoint) {
+ case ' ':
+ case '\t':
+ last_space_index = i;
+ break;
+ case '\n':
+ text_wrap_offset = 0.0f;
+ last_space_index = -1;
+ ++num_lines;
+ break;
+ default:
+ break;
+ }
+
+ sf::Vertex *vertex = &vertices[vertex_ref.vertices_index][vertex_ref.index];
+ vertex[0].position.x -= text_wrap_offset;
+ vertex[1].position.x -= text_wrap_offset;
+ vertex[2].position.x -= text_wrap_offset;
+ vertex[3].position.x -= text_wrap_offset;
+
+ vertex[0].position.y += text_offset_y;
+ vertex[1].position.y += text_offset_y;
+ vertex[2].position.y += text_offset_y;
+ vertex[3].position.y += text_offset_y;
+ vertex_ref.line = num_lines - 1;
+
+ float vertex_right_side = get_text_quad_right_side(vertex_ref);
+ if(vertex_right_side > maxWidth) {
+ ++num_lines;
+ // TODO: Ignore line wrap on space
+ if(last_space_index != -1 && last_space_index != i) {
+ float vertex_left_side = get_text_quad_left_side(vertices_linear[last_space_index + 1]);
+ for(int j = last_space_index + 1; j <= i; ++j) {
+ VertexRef &vertex_ref_wrap = vertices_linear[j];
+ sf::Vertex *vertex = &vertices[vertex_ref_wrap.vertices_index][vertex_ref_wrap.index];
+ vertex[0].position.x -= vertex_left_side;
+ vertex[1].position.x -= vertex_left_side;
+ vertex[2].position.x -= vertex_left_side;
+ vertex[3].position.x -= vertex_left_side;
+
+ vertex[0].position.y += line_height;
+ vertex[1].position.y += line_height;
+ vertex[2].position.y += line_height;
+ vertex[3].position.y += line_height;
+
+ vertex_ref_wrap.line = num_lines - 1;
+ }
+ last_space_index = -1;
+ text_wrap_offset += vertex_left_side;
+ } else {
+ float vertex_left_side = get_text_quad_left_side(vertex_ref);
+ vertex[0].position.x -= vertex_left_side;
+ vertex[1].position.x -= vertex_left_side;
+ vertex[2].position.x -= vertex_left_side;
+ vertex[3].position.x -= vertex_left_side;
+
+ vertex[0].position.y += line_height;
+ vertex[1].position.y += line_height;
+ vertex[2].position.y += line_height;
+ vertex[3].position.y += line_height;
+
+ text_wrap_offset += vertex_left_side;
+ vertex_ref.line = num_lines - 1;
+ }
+ text_offset_y += line_height;
}
}
+ boundingBox.width = 0.0f;
+ for(VertexRef &vertex_ref : vertices_linear) {
+ boundingBox.width = std::max(boundingBox.width, get_text_quad_right_side(vertex_ref));
+ }
+ boundingBox.height = num_lines * line_height;
dirty = false;
}
-
-#if 0
+
+ // TODO: Fix caret up/down navigation! its broken because of newlines
void Text::updateCaret()
{
assert(!dirty && !dirtyText);
- if(textElements.size() == 0)
- {
- float vspace = font->getLineSpacing(characterSize);
+ if(vertices_linear.empty()) {
caretIndex = 0;
- caretPosition = sf::Vector2f(0.0f, -vspace);
+ caretPosition = sf::Vector2f(0.0f, floor(font->getLineSpacing(characterSize)));
return;
}
-
switch(caretMoveDirection)
{
@@ -407,49 +457,41 @@ namespace QuickMedia
caretIndex = getEndOfLine(caretIndex);
break;
}
- default:
+ case CaretMoveDirection::NONE:
// Ignore...
break;
}
-
- caretIndex = std::min(std::max(0, caretIndex), (int)textElements[0].text.size);
-
- usize vertexIndex = caretIndex * 4;
- if(vertexIndex == 0)
- {
- float vspace = font->getLineSpacing(characterSize);
- caretPosition = sf::Vector2f(0.0f, -vspace);
- }
- else
- {
- const sf::Vertex &topLeftVertex = vertices[vertexIndex];
- caretPosition = topLeftVertex.position;
- }
- }
- bool Text::isCaretAtEnd() const
- {
- assert(!dirty && !dirtyText);
- return textElements[0].text.size == 0 || caretIndex == (int)textElements[0].text.size;
+ if(caretIndex == (int)vertices_linear.size()) {
+ caretPosition.x = get_text_quad_right_side(vertices_linear.back());
+ caretPosition.y = (1 + vertices_linear.back().line) * floor(font->getLineSpacing(characterSize) + lineSpacing);
+ } else if(caretIndex == 0) {
+ caretPosition = sf::Vector2f(0.0f, floor(font->getLineSpacing(characterSize)));
+ } else {
+ if(vertices_linear[caretIndex].codepoint == '\n') {
+ caretPosition.x = get_text_quad_right_side(vertices_linear[caretIndex - 1]);
+ caretPosition.y = (1 + vertices_linear[caretIndex - 1].line) * floor(font->getLineSpacing(characterSize) + lineSpacing);
+ } else {
+ caretPosition.x = get_text_quad_left_side(vertices_linear[caretIndex]);
+ caretPosition.y = (1 + vertices_linear[caretIndex].line) * floor(font->getLineSpacing(characterSize) + lineSpacing);
+ }
+ }
}
// TODO: This can be optimized by using binary search
int Text::getStartOfLine(int startIndex) const
{
assert(!dirty && !dirtyText);
- int numVertices = vertices.getVertexCount();
- if(numVertices < 4) return 0;
-
- usize vertexIndex = startIndex * 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);
+ const int num_vertices = vertices_linear.size();
+ const int start_index_wrap = startIndex < num_vertices ? startIndex : num_vertices - 1;
+ int start_line = vertices_linear[start_index_wrap].line;
+ if(vertices_linear[start_index_wrap].codepoint == '\n')
+ --start_line;
+ for(int i = startIndex - 1; i >= 0; --i) {
+ if(vertices_linear[i].line != start_line) {
+ if(i + 2 <= num_vertices && vertices_linear[i + 1].codepoint == '\n')
+ return i + 2;
+ return i + 1;
}
}
return 0;
@@ -459,55 +501,46 @@ namespace QuickMedia
int Text::getEndOfLine(int startIndex) const
{
assert(!dirty && !dirtyText);
- int numVertices = vertices.getVertexCount();
- if(numVertices < 4) return 0;
-
- usize vertexIndex = startIndex * 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);
+ const int num_vertices = vertices_linear.size();
+ const int start_index_wrap = startIndex < num_vertices ? startIndex : num_vertices - 1;
+ int start_line = vertices_linear[start_index_wrap].line;
+ if(vertices_linear[start_index_wrap].codepoint == '\n')
+ return startIndex;
+ for(int i = startIndex + 1; i < (int)vertices_linear.size(); ++i) {
+ if(vertices_linear[i].line != start_line) {
+ if(vertices_linear[i].codepoint == '\n')
+ return i;
+ return i - 1;
}
}
- return numVertices / 4;
+ return (int)vertices_linear.size();
}
// TODO: This can be optimized by using binary search
int Text::getPreviousLineClosestPosition(int startIndex) const
{
assert(!dirty && !dirtyText);
- int numVertices = vertices.getVertexCount();
- if(numVertices < 4) return 0;
-
- usize vertexIndex = startIndex * 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;
- }
+ const int num_vertices = vertices_linear.size();
+ float start_left_pos;
+ if(startIndex == num_vertices) {
+ start_left_pos = get_text_quad_right_side(vertices_linear.back());
+ if(vertices_linear.back().codepoint == '\n')
+ return getStartOfLine(startIndex - 1);
+ } else {
+ start_left_pos = get_text_quad_left_side(vertices_linear[startIndex]);
+ if(vertices_linear[startIndex].codepoint == '\n')
+ return getStartOfLine(startIndex - 1);
+ }
+ float closest_char = 999999.9f;
+ for(int i = getStartOfLine(startIndex) - 1; i >= 0; --i) {
+ //if(vertices_linear[i].codepoint == '\n')
+ // continue;
+ const float left_pos = get_text_quad_left_side(vertices_linear[i]);
+ const float pos_diff = std::abs(start_left_pos - left_pos);
+ if(pos_diff > closest_char)
+ return i + 1;
+ closest_char = pos_diff;
}
-
- if(closestIndex != -1)
- return closestIndex / 4;
-
return 0;
}
@@ -515,43 +548,31 @@ namespace QuickMedia
int Text::getNextLineClosestPosition(int startIndex) const
{
assert(!dirty && !dirtyText);
- int numVertices = vertices.getVertexCount();
- if(numVertices < 4) return 0;
-
- usize vertexIndex = startIndex * 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;
- }
+ const int num_vertices = vertices_linear.size();
+ float start_left_pos;
+ if(startIndex == num_vertices) {
+ return startIndex;
+ } else {
+ start_left_pos = get_text_quad_left_side(vertices_linear[startIndex]);
+ if(vertices_linear[startIndex].codepoint == '\n')
+ return startIndex + 1;
}
-
- 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));
+ float closest_char = 999999.9f;
+ for(int i = getEndOfLine(startIndex) + 1; i < (int)vertices_linear.size(); ++i) {
+ //if(vertices_linear[i].codepoint == '\n')
+ // continue;
+ const float left_pos = get_text_quad_left_side(vertices_linear[i]);
+ const float pos_diff = std::abs(start_left_pos - left_pos);
+ if(pos_diff > closest_char)
+ return i - 1;
+ closest_char = pos_diff;
+ }
+ return (int)vertices_linear.size();
}
-
+
+ // TODO: Optimize text editing by only processing the changed parts in updateGeometry.
+ // TODO: Split text into lines and add to vertices list so the lines that are cut off are not visible. This is good when using the text and as text input
+ // where there are a max number of rows shown at a time.
void Text::processEvent(const sf::Event &event)
{
if(!editable) return;
@@ -602,7 +623,7 @@ namespace QuickMedia
}
else if(event.key.code == sf::Keyboard::Return)
{
- if(sf::Keyboard::isKeyPressed(sf::Keyboard::LShift) || sf::Keyboard::isKeyPressed(sf::Keyboard::RShift))
+ if(event.key.shift)
{
if(caretAtEnd)
str += '\n';
@@ -650,24 +671,19 @@ namespace QuickMedia
dirtyCaret = true;
}
}
-#endif
+
bool Text::draw(sf::RenderTarget &target)
{
updateGeometry();
-
-#if 0
+
if(dirtyCaret || caretMoveDirection != CaretMoveDirection::NONE)
{
updateCaret();
dirtyCaret = false;
caretMoveDirection = CaretMoveDirection::NONE;
}
-#endif
-
- float vspace = font->getLineSpacing(characterSize);
sf::Vector2f pos = position;
- pos.y += floor(vspace); // Origin is at bottom left, we want it to be at top left
// TODO: Do not use maxWidth here. Max width might be set to 99999 and actual text width might be 200. Text width should be calculated instead
//sf::FloatRect targetRect(0.0f, 0.0f, maxWidth, target.getSize().y);
@@ -685,6 +701,9 @@ namespace QuickMedia
}
return false;
}
+
+ const float vspace = font->getLineSpacing(characterSize);
+ pos.y += floor(vspace); // Origin is at bottom left, we want it to be at top left
if(!visible) {
visible = true;
@@ -700,21 +719,14 @@ namespace QuickMedia
}
lastSeenTimer.restart();
- pos.y -= floor(vspace);
-
-#if 0
if(!editable) return true;
-
- //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))));
+ pos.y -= floor(vspace * 2.0f);
+
+ const float caret_margin = 2.0f;
+
+ sf::RectangleShape caretRect(sf::Vector2f(2.0f, floor(vspace - caret_margin * 2.0f)));
+ caretRect.setPosition(floor(pos.x + caretPosition.x), floor(pos.y + caretPosition.y + caret_margin + 4.0f));
target.draw(caretRect);
return true;
-#else
- return true;
-#endif
}
}
diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp
index d364b16..513a9fb 100644
--- a/src/plugins/Matrix.cpp
+++ b/src/plugins/Matrix.cpp
@@ -666,7 +666,10 @@ namespace QuickMedia {
std::string formatted_body;
bool contains_formatted_text = false;
if(msgtype == MessageType::TEXT) {
- string_split(body, '\n', [&formatted_body, &contains_formatted_text](const char *str, size_t size){
+ int line = 0;
+ string_split(body, '\n', [&formatted_body, &contains_formatted_text, &line](const char *str, size_t size){
+ if(line > 0)
+ formatted_body += "<br/>";
if(size > 0 && str[0] == '>') {
std::string line(str, size);
html_escape_sequences(line);
@@ -677,6 +680,7 @@ namespace QuickMedia {
} else {
formatted_body.append(str, size);
}
+ ++line;
return true;
});
}
diff --git a/src/plugins/Plugin.cpp b/src/plugins/Plugin.cpp
index f23175c..20c4b0a 100644
--- a/src/plugins/Plugin.cpp
+++ b/src/plugins/Plugin.cpp
@@ -35,11 +35,11 @@ namespace QuickMedia {
void html_escape_sequences(std::string &str) {
const std::array<HtmlEscapeSequence, 6> escape_sequences = {
+ HtmlEscapeSequence { '&', "&amp;" }, // This should be first, to not accidentally replace a new sequence caused by replacing this
HtmlEscapeSequence { '"', "&quot;" },
HtmlEscapeSequence { '\'', "&#39;" },
HtmlEscapeSequence { '<', "&lt;" },
- HtmlEscapeSequence { '>', "&gt;" },
- HtmlEscapeSequence { '&', "&amp;" } // This should be last, to not accidentally replace a new sequence caused by replacing this
+ HtmlEscapeSequence { '>', "&gt;" }
};
for(const HtmlEscapeSequence &escape_sequence : escape_sequences) {