294
src/ImGui.cpp
294
src/ImGui.cpp
@@ -111,6 +111,40 @@ auto encode_utf8(u32 cp) -> std::string
|
||||
return std::string(buf, len);
|
||||
}
|
||||
|
||||
auto rune_index_for_byte(std::string_view text, std::size_t byte_offset) -> int
|
||||
{
|
||||
auto spans = decode_utf8(text);
|
||||
int idx = 0;
|
||||
for (auto const &span : spans) {
|
||||
if (span.start >= byte_offset)
|
||||
break;
|
||||
idx++;
|
||||
}
|
||||
if (byte_offset >= text.size())
|
||||
idx = static_cast<int>(spans.size());
|
||||
return idx;
|
||||
}
|
||||
|
||||
auto clamp_preedit_index(int value, std::size_t text_size) -> std::size_t
|
||||
{
|
||||
if (value < 0)
|
||||
return 0;
|
||||
auto const as_size = static_cast<std::size_t>(value);
|
||||
return std::min(as_size, text_size);
|
||||
}
|
||||
|
||||
auto slice_bytes(std::string_view text, std::size_t begin, std::size_t end)
|
||||
-> std::string_view
|
||||
{
|
||||
if (begin > text.size())
|
||||
begin = text.size();
|
||||
if (end > text.size())
|
||||
end = text.size();
|
||||
if (end < begin)
|
||||
end = begin;
|
||||
return std::string_view(text.data() + begin, end - begin);
|
||||
}
|
||||
|
||||
constexpr float HORIZONTAL_PADDING = 6.0f;
|
||||
constexpr float VERTICAL_PADDING = 4.0f;
|
||||
constexpr float CARET_WIDTH = 2.0f;
|
||||
@@ -135,6 +169,127 @@ void ImGui::end() { }
|
||||
|
||||
void ImGui::set_font(FontHandle font) { m_font = font; }
|
||||
|
||||
auto ImGui::focused_text_input() const -> std::optional<std::size_t>
|
||||
{
|
||||
if (m_focused_id == 0)
|
||||
return std::nullopt;
|
||||
return m_focused_id;
|
||||
}
|
||||
|
||||
auto ImGui::text_input_surrounding(std::size_t id,
|
||||
std::pmr::string const &str) const -> std::optional<TextInputSurrounding>
|
||||
{
|
||||
auto it = m_ti_states.find(id);
|
||||
if (it == m_ti_states.end())
|
||||
return std::nullopt;
|
||||
TextInputSurrounding info;
|
||||
info.text.assign(str.data(), str.size());
|
||||
info.caret_byte = std::min(it->second.caret_byte, str.size());
|
||||
info.cursor = static_cast<int>(info.caret_byte);
|
||||
info.anchor = static_cast<int>(info.caret_byte);
|
||||
return info;
|
||||
}
|
||||
|
||||
auto ImGui::text_input_cursor(std::size_t id) const
|
||||
-> std::optional<TextInputCursor>
|
||||
{
|
||||
auto it = m_ti_states.find(id);
|
||||
if (it == m_ti_states.end())
|
||||
return std::nullopt;
|
||||
TextInputCursor cursor;
|
||||
cursor.rect = it->second.caret_rect;
|
||||
cursor.visible
|
||||
= it->second.caret_visible && !it->second.preedit_cursor_hidden;
|
||||
return cursor;
|
||||
}
|
||||
|
||||
void ImGui::ime_commit_text(std::pmr::string &str, std::string_view text)
|
||||
{
|
||||
if (m_focused_id == 0)
|
||||
return;
|
||||
auto it = m_ti_states.find(m_focused_id);
|
||||
if (it == m_ti_states.end())
|
||||
return;
|
||||
auto &state = it->second;
|
||||
std::size_t insert_pos = std::min(state.caret_byte, str.size());
|
||||
if (!text.empty())
|
||||
str.insert(insert_pos, text);
|
||||
state.caret_byte = insert_pos + text.size();
|
||||
std::string_view const view(str.data(), str.size());
|
||||
state.current_rune_idx = rune_index_for_byte(view, state.caret_byte);
|
||||
state.caret_timer = 0.0;
|
||||
state.caret_visible = true;
|
||||
state.external_change = true;
|
||||
}
|
||||
|
||||
void ImGui::ime_delete_surrounding(
|
||||
std::pmr::string &str, std::size_t before, std::size_t after)
|
||||
{
|
||||
if (m_focused_id == 0)
|
||||
return;
|
||||
auto it = m_ti_states.find(m_focused_id);
|
||||
if (it == m_ti_states.end())
|
||||
return;
|
||||
auto &state = it->second;
|
||||
std::size_t caret_byte = std::min(state.caret_byte, str.size());
|
||||
std::size_t start = before > caret_byte ? 0 : caret_byte - before;
|
||||
std::size_t end = std::min(caret_byte + after, str.size());
|
||||
if (end > start) {
|
||||
str.erase(start, end - start);
|
||||
state.caret_byte = start;
|
||||
std::string_view const view(str.data(), str.size());
|
||||
state.current_rune_idx = rune_index_for_byte(view, state.caret_byte);
|
||||
state.caret_timer = 0.0;
|
||||
state.caret_visible = true;
|
||||
state.external_change = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ImGui::ime_set_preedit(std::string text, int cursor_begin, int cursor_end)
|
||||
{
|
||||
if (m_focused_id == 0)
|
||||
return;
|
||||
auto it = m_ti_states.find(m_focused_id);
|
||||
if (it == m_ti_states.end())
|
||||
return;
|
||||
auto &state = it->second;
|
||||
state.preedit_text = std::move(text);
|
||||
state.preedit_cursor_hidden = (cursor_begin == -1 && cursor_end == -1);
|
||||
std::size_t const size = state.preedit_text.size();
|
||||
if (state.preedit_cursor_hidden) {
|
||||
state.preedit_cursor_begin = 0;
|
||||
state.preedit_cursor_end = 0;
|
||||
} else {
|
||||
auto begin_clamped = clamp_preedit_index(cursor_begin, size);
|
||||
auto end_clamped = clamp_preedit_index(cursor_end, size);
|
||||
state.preedit_cursor_begin = static_cast<int>(begin_clamped);
|
||||
state.preedit_cursor_end = static_cast<int>(end_clamped);
|
||||
}
|
||||
state.preedit_active
|
||||
= !state.preedit_text.empty() || !state.preedit_cursor_hidden;
|
||||
if (state.preedit_active) {
|
||||
state.caret_timer = 0.0;
|
||||
state.caret_visible = !state.preedit_cursor_hidden;
|
||||
}
|
||||
}
|
||||
|
||||
void ImGui::ime_clear_preedit()
|
||||
{
|
||||
if (m_focused_id == 0)
|
||||
return;
|
||||
auto it = m_ti_states.find(m_focused_id);
|
||||
if (it == m_ti_states.end())
|
||||
return;
|
||||
auto &state = it->second;
|
||||
state.preedit_text.clear();
|
||||
state.preedit_cursor_begin = 0;
|
||||
state.preedit_cursor_end = 0;
|
||||
state.preedit_active = false;
|
||||
state.preedit_cursor_hidden = false;
|
||||
state.caret_visible = true;
|
||||
state.caret_timer = 0.0;
|
||||
}
|
||||
|
||||
auto ImGui::text_input(std::size_t id, std::pmr::string &str, Rectangle rec,
|
||||
TextInputOptions options) -> std::bitset<2>
|
||||
{
|
||||
@@ -351,9 +506,14 @@ auto ImGui::text_input(std::size_t id, std::pmr::string &str, Rectangle rec,
|
||||
caret_activity = true;
|
||||
}
|
||||
|
||||
state.caret_byte = caret_byte;
|
||||
|
||||
double const dt = static_cast<double>(GetFrameTime());
|
||||
if (m_focused_id == id) {
|
||||
if (caret_activity) {
|
||||
if (state.preedit_active && state.preedit_cursor_hidden) {
|
||||
state.caret_visible = false;
|
||||
state.caret_timer = 0.0;
|
||||
} else if (caret_activity) {
|
||||
state.caret_timer = 0.0;
|
||||
state.caret_visible = true;
|
||||
} else {
|
||||
@@ -376,15 +536,62 @@ auto ImGui::text_input(std::size_t id, std::pmr::string &str, Rectangle rec,
|
||||
|
||||
Vector2 prefix_metrics { 0.0f, 0.0f };
|
||||
Vector2 full_metrics { 0.0f, 0.0f };
|
||||
Vector2 preedit_metrics { 0.0f, 0.0f };
|
||||
Vector2 caret_preedit_metrics { 0.0f, 0.0f };
|
||||
Vector2 selection_prefix_metrics { 0.0f, 0.0f };
|
||||
Vector2 selection_metrics { 0.0f, 0.0f };
|
||||
std::string display_buffer;
|
||||
bool const has_preedit = state.preedit_active
|
||||
&& (!state.preedit_text.empty() || !state.preedit_cursor_hidden);
|
||||
|
||||
if (m_font.has_value() && m_text_renderer) {
|
||||
int const font_px = static_cast<int>(options.font_size);
|
||||
std::string_view const prefix_view(str.data(), caret_byte);
|
||||
prefix_metrics = m_text_renderer->measure_text(
|
||||
*m_font, prefix_view, static_cast<int>(options.font_size));
|
||||
full_metrics = m_text_renderer->measure_text(
|
||||
*m_font, str_view, static_cast<int>(options.font_size));
|
||||
prefix_metrics
|
||||
= m_text_renderer->measure_text(*m_font, prefix_view, font_px);
|
||||
|
||||
if (has_preedit) {
|
||||
std::string_view const preedit_view(
|
||||
state.preedit_text.data(), state.preedit_text.size());
|
||||
preedit_metrics
|
||||
= m_text_renderer->measure_text(*m_font, preedit_view, font_px);
|
||||
|
||||
auto caret_idx = clamp_preedit_index(
|
||||
state.preedit_cursor_end, preedit_view.size());
|
||||
caret_preedit_metrics = m_text_renderer->measure_text(
|
||||
*m_font, slice_bytes(preedit_view, 0, caret_idx), font_px);
|
||||
|
||||
if (!state.preedit_cursor_hidden
|
||||
&& state.preedit_cursor_begin != state.preedit_cursor_end) {
|
||||
auto sel_begin
|
||||
= clamp_preedit_index(std::min(state.preedit_cursor_begin,
|
||||
state.preedit_cursor_end),
|
||||
preedit_view.size());
|
||||
auto sel_end
|
||||
= clamp_preedit_index(std::max(state.preedit_cursor_begin,
|
||||
state.preedit_cursor_end),
|
||||
preedit_view.size());
|
||||
selection_prefix_metrics = m_text_renderer->measure_text(
|
||||
*m_font, slice_bytes(preedit_view, 0, sel_begin), font_px);
|
||||
selection_metrics = m_text_renderer->measure_text(*m_font,
|
||||
slice_bytes(preedit_view, sel_begin, sel_end), font_px);
|
||||
}
|
||||
|
||||
display_buffer.reserve(str.size() + state.preedit_text.size());
|
||||
display_buffer.append(prefix_view);
|
||||
display_buffer.append(preedit_view);
|
||||
display_buffer.append(str_view.substr(caret_byte));
|
||||
full_metrics = m_text_renderer->measure_text(*m_font,
|
||||
std::string_view(display_buffer.data(), display_buffer.size()),
|
||||
font_px);
|
||||
} else {
|
||||
full_metrics
|
||||
= m_text_renderer->measure_text(*m_font, str_view, font_px);
|
||||
}
|
||||
}
|
||||
|
||||
state.cursor_position.x = prefix_metrics.x;
|
||||
float caret_offset = prefix_metrics.x + caret_preedit_metrics.x;
|
||||
state.cursor_position.x = caret_offset;
|
||||
|
||||
float const available_width
|
||||
= std::max(0.0f, rec.width - 2.0f * HORIZONTAL_PADDING);
|
||||
@@ -392,11 +599,11 @@ auto ImGui::text_input(std::size_t id, std::pmr::string &str, Rectangle rec,
|
||||
state.scroll_offset.x = 0.0f;
|
||||
} else {
|
||||
float &scroll = state.scroll_offset.x;
|
||||
float caret_local = state.cursor_position.x - scroll;
|
||||
float caret_local = caret_offset - scroll;
|
||||
if (caret_local > available_width) {
|
||||
scroll = state.cursor_position.x - available_width;
|
||||
scroll = caret_offset - available_width;
|
||||
} else if (caret_local < 0.0f) {
|
||||
scroll = state.cursor_position.x;
|
||||
scroll = caret_offset;
|
||||
}
|
||||
scroll = std::clamp(
|
||||
scroll, 0.0f, std::max(0.0f, full_metrics.x - available_width));
|
||||
@@ -431,26 +638,68 @@ auto ImGui::text_input(std::size_t id, std::pmr::string &str, Rectangle rec,
|
||||
}
|
||||
state.cursor_position.y = caret_top;
|
||||
|
||||
float const caret_draw_x
|
||||
= rec.x + HORIZONTAL_PADDING + caret_offset - state.scroll_offset.x;
|
||||
state.caret_rect = Rectangle {
|
||||
caret_draw_x,
|
||||
caret_top,
|
||||
CARET_WIDTH,
|
||||
caret_height,
|
||||
};
|
||||
|
||||
BeginScissorMode(rec.x, rec.y, rec.width, rec.height);
|
||||
{
|
||||
if (m_font.has_value() && m_text_renderer) {
|
||||
Vector2 const text_pos {
|
||||
int const font_px = static_cast<int>(options.font_size);
|
||||
Vector2 const base_pos {
|
||||
rec.x + HORIZONTAL_PADDING - state.scroll_offset.x,
|
||||
baseline_y,
|
||||
};
|
||||
Color const text_color { 255, 255, 255, 255 };
|
||||
m_text_renderer->draw_text(*m_font, str_view, text_pos,
|
||||
static_cast<int>(options.font_size), text_color);
|
||||
std::string_view const left_view(str.data(), caret_byte);
|
||||
std::string_view const right_view(
|
||||
str.data() + caret_byte, str.size() - caret_byte);
|
||||
|
||||
m_text_renderer->draw_text(
|
||||
*m_font, left_view, base_pos, font_px, text_color);
|
||||
|
||||
float advance = prefix_metrics.x;
|
||||
if (has_preedit) {
|
||||
Vector2 const preedit_pos {
|
||||
base_pos.x + advance,
|
||||
base_pos.y,
|
||||
};
|
||||
if (selection_metrics.x > 0.0f) {
|
||||
float const sel_offset
|
||||
= prefix_metrics.x + selection_prefix_metrics.x;
|
||||
Rectangle const sel_rect {
|
||||
rec.x + HORIZONTAL_PADDING + sel_offset
|
||||
- state.scroll_offset.x,
|
||||
caret_top,
|
||||
selection_metrics.x,
|
||||
caret_height,
|
||||
};
|
||||
Color const highlight { 120, 160, 255, 100 };
|
||||
DrawRectangleRec(sel_rect, highlight);
|
||||
}
|
||||
Color const preedit_color { 200, 220, 255, 255 };
|
||||
m_text_renderer->draw_text(*m_font,
|
||||
std::string_view(
|
||||
state.preedit_text.data(), state.preedit_text.size()),
|
||||
preedit_pos, font_px, preedit_color);
|
||||
advance += preedit_metrics.x;
|
||||
}
|
||||
|
||||
Vector2 const right_pos {
|
||||
base_pos.x + advance,
|
||||
base_pos.y,
|
||||
};
|
||||
m_text_renderer->draw_text(
|
||||
*m_font, right_view, right_pos, font_px, text_color);
|
||||
|
||||
if (m_focused_id == id && state.caret_visible) {
|
||||
float const caret_x = std::round(rec.x + HORIZONTAL_PADDING
|
||||
+ state.cursor_position.x - state.scroll_offset.x);
|
||||
Rectangle caret_rect {
|
||||
caret_x,
|
||||
caret_top,
|
||||
CARET_WIDTH,
|
||||
caret_height,
|
||||
};
|
||||
Rectangle caret_rect = state.caret_rect;
|
||||
caret_rect.x = std::round(caret_rect.x);
|
||||
Color const caret_color { 255, 255, 255, 200 };
|
||||
DrawRectangleRec(caret_rect, caret_color);
|
||||
}
|
||||
@@ -458,6 +707,11 @@ auto ImGui::text_input(std::size_t id, std::pmr::string &str, Rectangle rec,
|
||||
}
|
||||
EndScissorMode();
|
||||
|
||||
if (state.external_change) {
|
||||
changed = true;
|
||||
state.external_change = false;
|
||||
}
|
||||
|
||||
return std::bitset<2> { static_cast<unsigned long long>(
|
||||
(submitted ? 1 : 0) | (changed ? 2 : 0)) };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user