From f92ac0050ffcc4b85b116602d4a45344b7e86f52 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Tue, 29 Sep 2020 01:13:00 +0200 Subject: Fix caret navigation --- src/QuickMedia.cpp | 6 +- src/Text.cpp | 210 +++++++++++++++++++++++++++++------------------------ 2 files changed, 117 insertions(+), 99 deletions(-) (limited to 'src') diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index 6a10da4..8a7cbf6 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -2984,7 +2984,7 @@ namespace QuickMedia { body->clamp_selection(); } body->set_page_scroll(previous_page_scroll); - } else if(event.key.code == sf::Keyboard::C && event.key.control && selected_item) { + } else if(event.key.code == sf::Keyboard::M && event.key.control && selected_item) { navigation_stage = NavigationStage::REPLYING; } else if(event.key.code == sf::Keyboard::R && selected_item) { std::string text_to_add = ">>" + selected_item->post_number; @@ -3348,7 +3348,7 @@ namespace QuickMedia { sf::Sprite logo_sprite(plugin_logo); - Entry chat_input("Press ctrl+c to begin writing a message...", font.get(), cjk_font.get()); + Entry chat_input("Press ctrl+m 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, ¤t_room_id, &new_page, &typing_message](const sf::String &text) { if(tabs[selected_tab].type == ChatTabType::MESSAGES) { @@ -3519,7 +3519,7 @@ namespace QuickMedia { } } - if(!typing_message && tabs[selected_tab].type == ChatTabType::MESSAGES && event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::C && event.key.control) { + if(!typing_message && tabs[selected_tab].type == ChatTabType::MESSAGES && event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::M && event.key.control) { chat_input.set_editable(true); typing_message = true; } diff --git a/src/Text.cpp b/src/Text.cpp index 3962374..5e0ad87 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -42,7 +42,8 @@ namespace QuickMedia caretMoveDirection(CaretMoveDirection::NONE), lineSpacing(0.0f), characterSpacing(0.0f), - caretIndex(0) + caretIndex(0), + caret_offset_x(0.0f) { vertices[0].setPrimitiveType(sf::PrimitiveType::Quads); vertices[1].setPrimitiveType(sf::PrimitiveType::Quads); @@ -229,6 +230,39 @@ namespace QuickMedia float Text::get_text_quad_right_side(const VertexRef &vertex_ref) const { return vertices[vertex_ref.vertices_index][vertex_ref.index + 1].position.x; } + + float Text::get_caret_offset_by_caret_index(int index) const { + const int num_vertices = vertices_linear.size(); + if(num_vertices == 0) + return 0.0f; + else if(index < num_vertices) + return get_text_quad_left_side(vertices_linear[index]); + else + return get_text_quad_right_side(vertices_linear[num_vertices - 1]); + } + + VertexRef& Text::get_vertex_ref_clamp(int index) { + const int num_vertices = vertices_linear.size(); + assert(num_vertices > 0); + if(index == num_vertices) + return vertices_linear.back(); + else + return vertices_linear[index]; + } + + int Text::get_vertex_line(int index) const { + const int num_vertices = vertices_linear.size(); + if(num_vertices == 0) { + return 0; + } else if(index < num_vertices) { + return vertices_linear[index].line; + } else { + if(vertices_linear.back().codepoint == '\n') + return vertices_linear.back().line + 1; + else + return vertices_linear.back().line; + } + } void Text::updateGeometry(bool update_even_if_not_dirty) { if(dirtyText) { @@ -299,12 +333,12 @@ namespace QuickMedia } 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(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; + vertices[vertices_index][vertexStart + 2] = { sf::Vector2f(glyphPos.x, 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 = 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; } @@ -349,6 +383,19 @@ namespace QuickMedia // TODO: Binary search? for(int i = 0; i < (int)vertices_linear.size(); ++i) { VertexRef &vertex_ref = vertices_linear[i]; + + 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; + switch(vertex_ref.codepoint) { case ' ': case '\t': @@ -363,18 +410,6 @@ namespace QuickMedia 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; @@ -423,58 +458,65 @@ namespace QuickMedia } boundingBox.height = num_lines * line_height; dirty = false; + + // TODO: Clear vertices_linear when not in edit mode, but take into consideration switching between edit/not-edit mode } - // TODO: Fix caret up/down navigation! its broken because of newlines void Text::updateCaret() { assert(!dirty && !dirtyText); if(vertices_linear.empty()) { caretIndex = 0; caretPosition = sf::Vector2f(0.0f, floor(font->getLineSpacing(characterSize))); + caret_offset_x = 0.0f; return; } - switch(caretMoveDirection) - { - case CaretMoveDirection::UP: - { + switch(caretMoveDirection) { + case CaretMoveDirection::NONE: { + caret_offset_x = get_caret_offset_by_caret_index(caretIndex); + break; + } + case CaretMoveDirection::UP: { caretIndex = getPreviousLineClosestPosition(caretIndex); break; } - case CaretMoveDirection::DOWN: - { + case CaretMoveDirection::DOWN: { caretIndex = getNextLineClosestPosition(caretIndex); break; } - case CaretMoveDirection::HOME: - { + case CaretMoveDirection::HOME: { caretIndex = getStartOfLine(caretIndex); + caret_offset_x = get_caret_offset_by_caret_index(caretIndex); break; } - case CaretMoveDirection::END: - { + case CaretMoveDirection::END: { caretIndex = getEndOfLine(caretIndex); + caret_offset_x = get_caret_offset_by_caret_index(caretIndex); + break; + } + case CaretMoveDirection::LEFT: { + caretIndex = std::max(0, caretIndex - 1); + caret_offset_x = get_caret_offset_by_caret_index(caretIndex); break; } - case CaretMoveDirection::NONE: - // Ignore... + case CaretMoveDirection::RIGHT: { + caretIndex = std::min((int)vertices_linear.size(), caretIndex + 1); + caret_offset_x = get_caret_offset_by_caret_index(caretIndex); break; + } } 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))); + VertexRef &last_vertex = get_vertex_ref_clamp(caretIndex); + if(last_vertex.codepoint == '\n') + caretPosition.x = 0.0f; + else + caretPosition.x = get_text_quad_right_side(last_vertex); + caretPosition.y = (1 + get_vertex_line(caretIndex)) * floor(font->getLineSpacing(characterSize) + lineSpacing); } 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); - } + caretPosition.x = get_caret_offset_by_caret_index(caretIndex); + caretPosition.y = (1 + get_vertex_line(caretIndex)) * floor(font->getLineSpacing(characterSize) + lineSpacing); } } @@ -482,15 +524,9 @@ namespace QuickMedia int Text::getStartOfLine(int startIndex) const { assert(!dirty && !dirtyText); - 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; + int start_line = get_vertex_line(startIndex); 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; + if(get_vertex_line(i) != start_line) { return i + 1; } } @@ -502,45 +538,32 @@ namespace QuickMedia { assert(!dirty && !dirtyText); 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; + int start_line = get_vertex_line(startIndex); + for(int i = startIndex + 1; i < num_vertices; ++i) { + if(get_vertex_line(i) != start_line) { return i - 1; } } - return (int)vertices_linear.size(); + return num_vertices; } // TODO: This can be optimized by using binary search int Text::getPreviousLineClosestPosition(int startIndex) const { assert(!dirty && !dirtyText); - 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); - } + const int start_line = get_vertex_line(startIndex); float closest_char = 999999.9f; - for(int i = getStartOfLine(startIndex) - 1; i >= 0; --i) { - //if(vertices_linear[i].codepoint == '\n') - // continue; + int closest_index = -1; + for(int i = getStartOfLine(startIndex) - 1; i >= 0 && get_vertex_line(i) == start_line - 1; --i) { 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; + const float pos_diff = std::abs(caret_offset_x - left_pos); + if(pos_diff < closest_char) { + closest_char = pos_diff; + closest_index = i; + } } + if(closest_index != -1) + return closest_index; return 0; } @@ -549,25 +572,20 @@ namespace QuickMedia { assert(!dirty && !dirtyText); 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; - } + const int start_line = get_vertex_line(startIndex); float closest_char = 999999.9f; - for(int i = getEndOfLine(startIndex) + 1; i < (int)vertices_linear.size(); ++i) { - //if(vertices_linear[i].codepoint == '\n') - // continue; + int closest_index = -1; + for(int i = getEndOfLine(startIndex) + 1; i < num_vertices && get_vertex_line(i) == start_line + 1; ++i) { 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; + const float pos_diff = std::abs(caret_offset_x - left_pos); + if(pos_diff < closest_char) { + closest_char = pos_diff; + closest_index = i; + } } - return (int)vertices_linear.size(); + if(closest_index != -1) + return closest_index; + return num_vertices; } // TODO: Optimize text editing by only processing the changed parts in updateGeometry. @@ -577,18 +595,18 @@ namespace QuickMedia { if(!editable) return; - bool caretAtEnd = textElements.size() == 0 || textElements[0].text.size == 0 || caretIndex == (int)textElements[0].text.size; + bool caretAtEnd = caretIndex == (int)vertices_linear.size(); if(event.type == sf::Event::KeyPressed) { if(event.key.code == sf::Keyboard::Left && caretIndex > 0) { - --caretIndex; + caretMoveDirection = CaretMoveDirection::LEFT; dirtyCaret = true; } else if(event.key.code == sf::Keyboard::Right && !caretAtEnd) { - ++caretIndex; + caretMoveDirection = CaretMoveDirection::RIGHT; dirtyCaret = true; } else if(event.key.code == sf::Keyboard::BackSpace && caretIndex > 0) @@ -621,7 +639,7 @@ namespace QuickMedia { caretMoveDirection = CaretMoveDirection::END; } - else if(event.key.code == sf::Keyboard::Return) + else if(event.key.code == sf::Keyboard::Enter) { if(event.key.shift) { -- cgit v1.2.3