112
src/App.cpp
112
src/App.cpp
@@ -319,6 +319,11 @@ auto App::init_wayland() -> void
|
|||||||
app->m_kbd.typing.push_back('v');
|
app->m_kbd.typing.push_back('v');
|
||||||
handled = true;
|
handled = true;
|
||||||
break;
|
break;
|
||||||
|
case XKB_KEY_c:
|
||||||
|
case XKB_KEY_C:
|
||||||
|
app->m_kbd.typing.push_back('c');
|
||||||
|
handled = true;
|
||||||
|
break;
|
||||||
case XKB_KEY_w:
|
case XKB_KEY_w:
|
||||||
case XKB_KEY_W:
|
case XKB_KEY_W:
|
||||||
if (ctrl) {
|
if (ctrl) {
|
||||||
@@ -530,7 +535,6 @@ auto App::init_wayland() -> void
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
wl_data_offer_add_listener(offer, &offer_l, app);
|
wl_data_offer_add_listener(offer, &offer_l, app);
|
||||||
// swap old
|
|
||||||
if (app->m_wayland.curr_offer
|
if (app->m_wayland.curr_offer
|
||||||
&& app->m_wayland.curr_offer != offer)
|
&& app->m_wayland.curr_offer != offer)
|
||||||
wl_data_offer_destroy(
|
wl_data_offer_destroy(
|
||||||
@@ -551,45 +555,54 @@ auto App::init_wayland() -> void
|
|||||||
app->m_clipboard_cache.clear();
|
app->m_clipboard_cache.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
char const *mime_utf8 = "text/plain;charset=utf-8";
|
|
||||||
char const *mime_plain = "text/plain";
|
char const *mime = "text/plain;charset=utf-8";
|
||||||
int fds[2];
|
int fds[2];
|
||||||
if (pipe(fds) != 0)
|
if (pipe(fds) != 0)
|
||||||
return;
|
return;
|
||||||
wl_data_offer_receive(offer, mime_utf8, fds[1]);
|
|
||||||
|
wl_data_offer_receive(offer, mime, fds[1]);
|
||||||
wl_display_flush(app->m_wayland.display);
|
wl_display_flush(app->m_wayland.display);
|
||||||
close(fds[1]);
|
close(fds[1]);
|
||||||
std::string data_utf8;
|
|
||||||
char buf[4096];
|
|
||||||
for (;;) {
|
|
||||||
ssize_t n = read(fds[0], buf, sizeof(buf));
|
|
||||||
if (n > 0)
|
|
||||||
data_utf8.append(buf, buf + n);
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
close(fds[0]);
|
|
||||||
|
|
||||||
if (data_utf8.empty()) {
|
int rfd = fds[0];
|
||||||
if (pipe(fds) != 0)
|
|
||||||
return;
|
std::thread([app, rfd, offer]() {
|
||||||
wl_data_offer_receive(
|
std::string data;
|
||||||
offer, mime_plain, fds[1]);
|
char buf[4096];
|
||||||
wl_display_flush(app->m_wayland.display);
|
|
||||||
close(fds[1]);
|
|
||||||
std::string data_plain;
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
ssize_t n = read(fds[0], buf, sizeof(buf));
|
ssize_t n = read(rfd, buf, sizeof buf);
|
||||||
if (n > 0)
|
if (n > 0) {
|
||||||
data_plain.append(buf, buf + n);
|
data.append(buf, buf + n);
|
||||||
else
|
continue;
|
||||||
break;
|
}
|
||||||
|
if (n < 0 && errno == EINTR)
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
close(fds[0]);
|
close(rfd);
|
||||||
app->m_clipboard_cache = std::move(data_plain);
|
|
||||||
} else {
|
struct Ctx {
|
||||||
app->m_clipboard_cache = std::move(data_utf8);
|
App *app;
|
||||||
}
|
wl_data_offer *offer;
|
||||||
|
std::string data;
|
||||||
|
};
|
||||||
|
auto *ctx
|
||||||
|
= new Ctx { app, offer, std::move(data) };
|
||||||
|
|
||||||
|
g_main_context_invoke(
|
||||||
|
nullptr,
|
||||||
|
+[](gpointer p) -> gboolean {
|
||||||
|
auto *ctx = static_cast<Ctx *>(p);
|
||||||
|
if (!ctx->data.empty())
|
||||||
|
ctx->app->m_clipboard_cache
|
||||||
|
= std::move(ctx->data);
|
||||||
|
wl_data_offer_destroy(ctx->offer);
|
||||||
|
delete ctx;
|
||||||
|
return G_SOURCE_REMOVE;
|
||||||
|
},
|
||||||
|
ctx);
|
||||||
|
}).detach();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
wl_data_device_add_listener(app->m_wayland.ddev, &ddev_l, app);
|
wl_data_device_add_listener(app->m_wayland.ddev, &ddev_l, app);
|
||||||
@@ -1103,18 +1116,33 @@ auto App::clipboard(std::string_view const &str) -> void
|
|||||||
static wl_data_source_listener const src_l = {
|
static wl_data_source_listener const src_l = {
|
||||||
.target = [](void *, wl_data_source *, char const *) {},
|
.target = [](void *, wl_data_source *, char const *) {},
|
||||||
.send =
|
.send =
|
||||||
[](void *data, wl_data_source *, char const *mime, int32_t fd) {
|
[](void *data, wl_data_source *, char const *, int32_t fd) {
|
||||||
auto *app = static_cast<App *>(data);
|
auto *app = static_cast<App *>(data);
|
||||||
(void)mime;
|
|
||||||
size_t off = 0;
|
int wfd = dup(fd);
|
||||||
while (off < app->m_clipboard_cache.size()) {
|
|
||||||
ssize_t n = write(fd, app->m_clipboard_cache.data() + off,
|
|
||||||
app->m_clipboard_cache.size() - off);
|
|
||||||
if (n <= 0)
|
|
||||||
break;
|
|
||||||
off += static_cast<size_t>(n);
|
|
||||||
}
|
|
||||||
close(fd);
|
close(fd);
|
||||||
|
|
||||||
|
std::pmr::string payload = app->m_clipboard_cache;
|
||||||
|
std::thread([wfd, payload = std::move(payload)]() {
|
||||||
|
size_t off = 0;
|
||||||
|
while (off < payload.size()) {
|
||||||
|
ssize_t n = write(wfd, payload.data() + off,
|
||||||
|
std::min<size_t>(64 * 1024, payload.size() - off));
|
||||||
|
if (n > 0) {
|
||||||
|
off += (size_t)n;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (n < 0 && (errno == EINTR))
|
||||||
|
continue;
|
||||||
|
if (n < 0 && (errno == EAGAIN)) {
|
||||||
|
std::this_thread::sleep_for(
|
||||||
|
std::chrono::milliseconds(1));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
close(wfd);
|
||||||
|
}).detach();
|
||||||
},
|
},
|
||||||
.cancelled =
|
.cancelled =
|
||||||
[](void *data, wl_data_source *src) {
|
[](void *data, wl_data_source *src) {
|
||||||
|
|||||||
531
src/ImGui.cpp
531
src/ImGui.cpp
@@ -19,6 +19,9 @@ struct CodepointSpan {
|
|||||||
usize end {};
|
usize end {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr inline float px_pos(float x) { return std::floor(x + 0.5f); }
|
||||||
|
constexpr inline float px_w(float w) { return std::ceil(w); }
|
||||||
|
|
||||||
constexpr auto utf8_rune_from_first(char const *s) -> u32
|
constexpr auto utf8_rune_from_first(char const *s) -> u32
|
||||||
{
|
{
|
||||||
u8 b0 = static_cast<u8>(s[0]);
|
u8 b0 = static_cast<u8>(s[0]);
|
||||||
@@ -60,7 +63,7 @@ constexpr auto decode_utf8(std::string_view text) -> std::vector<CodepointSpan>
|
|||||||
len = 4;
|
len = 4;
|
||||||
|
|
||||||
if (i + len > text.size())
|
if (i + len > text.size())
|
||||||
len = 1; // avoid overflow
|
len = 1;
|
||||||
|
|
||||||
u32 cp = utf8_rune_from_first(text.data() + i);
|
u32 cp = utf8_rune_from_first(text.data() + i);
|
||||||
spans.push_back({ cp, i, i + len });
|
spans.push_back({ cp, i, i + len });
|
||||||
@@ -132,7 +135,6 @@ auto slice_bytes(std::string_view text, usize begin, usize end)
|
|||||||
|
|
||||||
constexpr float HORIZONTAL_PADDING = 6.0f;
|
constexpr float HORIZONTAL_PADDING = 6.0f;
|
||||||
constexpr float VERTICAL_PADDING = 4.0f;
|
constexpr float VERTICAL_PADDING = 4.0f;
|
||||||
constexpr float CARET_WIDTH = 2.0f;
|
|
||||||
constexpr double CARET_BLINK_INTERVAL = 0.5;
|
constexpr double CARET_BLINK_INTERVAL = 0.5;
|
||||||
constexpr float CARET_DESCENT_FRACTION = 0.25f;
|
constexpr float CARET_DESCENT_FRACTION = 0.25f;
|
||||||
|
|
||||||
@@ -289,7 +291,7 @@ size_t utf8_length(std::string_view const &s)
|
|||||||
{
|
{
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
for (unsigned char c : s)
|
for (unsigned char c : s)
|
||||||
if ((c & 0xC0) != 0x80) // continuation bytes
|
if ((c & 0xC0) != 0x80)
|
||||||
++count;
|
++count;
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
@@ -305,7 +307,6 @@ auto ImGui::text_input(usize id, std::pmr::string &str, Rectangle rec,
|
|||||||
bool changed { false };
|
bool changed { false };
|
||||||
|
|
||||||
auto &state { m_ti_states[id] };
|
auto &state { m_ti_states[id] };
|
||||||
|
|
||||||
assert(!options.multiline && "Multiline not yet implemented.");
|
assert(!options.multiline && "Multiline not yet implemented.");
|
||||||
|
|
||||||
if (m_focused_id == 0)
|
if (m_focused_id == 0)
|
||||||
@@ -322,157 +323,258 @@ auto ImGui::text_input(usize id, std::pmr::string &str, Rectangle rec,
|
|||||||
std::string_view str_view(str.data(), str.size());
|
std::string_view str_view(str.data(), str.size());
|
||||||
auto spans { decode_utf8(str_view) };
|
auto spans { decode_utf8(str_view) };
|
||||||
|
|
||||||
auto is_space { [](u32 cp) -> bool {
|
auto is_space = [](u32 cp) -> bool {
|
||||||
if (cp == '\n' || cp == '\r' || cp == '\t' || cp == '\v' || cp == '\f')
|
if (cp == '\n' || cp == '\r' || cp == '\t' || cp == '\v' || cp == '\f')
|
||||||
return true;
|
return true;
|
||||||
if (cp <= 0x7F)
|
if (cp <= 0x7F)
|
||||||
return std::isspace(static_cast<unsigned char>(cp)) != 0;
|
return std::isspace(static_cast<unsigned char>(cp)) != 0;
|
||||||
return false;
|
return false;
|
||||||
} };
|
};
|
||||||
|
|
||||||
auto clamp_cursor { [&]() -> usize {
|
auto clamp_cursor = [&]() -> usize {
|
||||||
int const max_idx = static_cast<int>(spans.size());
|
int const max_idx = static_cast<int>(spans.size());
|
||||||
state.current_rune_idx = std::clamp(state.current_rune_idx, 0, max_idx);
|
state.current_rune_idx = std::clamp(state.current_rune_idx, 0, max_idx);
|
||||||
if (state.current_rune_idx == max_idx)
|
if (state.current_rune_idx == max_idx)
|
||||||
return str.size();
|
return str.size();
|
||||||
return spans[state.current_rune_idx].start;
|
return spans[(usize)state.current_rune_idx].start;
|
||||||
} };
|
};
|
||||||
|
|
||||||
usize caret_byte = clamp_cursor();
|
usize caret_byte = clamp_cursor();
|
||||||
|
|
||||||
auto refresh_spans { [&]() {
|
auto refresh_spans = [&]() {
|
||||||
str_view = std::string_view(str.data(), str.size());
|
str_view = std::string_view(str.data(), str.size());
|
||||||
spans = decode_utf8(str_view);
|
spans = decode_utf8(str_view);
|
||||||
caret_byte = clamp_cursor();
|
caret_byte = clamp_cursor();
|
||||||
} };
|
};
|
||||||
|
|
||||||
auto erase_range { [&](usize byte_begin, usize byte_end) {
|
auto erase_range = [&](usize byte_begin, usize byte_end) {
|
||||||
if (byte_end > byte_begin && byte_begin < str.size()) {
|
if (byte_end > byte_begin && byte_begin < str.size()) {
|
||||||
str.erase(byte_begin, byte_end - byte_begin);
|
str.erase(byte_begin, byte_end - byte_begin);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
} };
|
};
|
||||||
|
|
||||||
|
auto selection_range_bytes
|
||||||
|
= [&](int a_idx, int b_idx) -> std::pair<usize, usize> {
|
||||||
|
int lo = std::max(0, std::min(a_idx, b_idx));
|
||||||
|
int hi = std::max(0, std::max(a_idx, b_idx));
|
||||||
|
usize byte_begin
|
||||||
|
= (lo >= (int)spans.size()) ? str.size() : spans[(usize)lo].start;
|
||||||
|
usize byte_end
|
||||||
|
= (hi >= (int)spans.size()) ? str.size() : spans[(usize)hi].start;
|
||||||
|
return { byte_begin, byte_end };
|
||||||
|
};
|
||||||
|
|
||||||
|
auto erase_selection_if_any = [&]() -> bool {
|
||||||
|
if (!state.has_selection(state.current_rune_idx))
|
||||||
|
return false;
|
||||||
|
auto [b, e] = selection_range_bytes(
|
||||||
|
state.sel_anchor_idx, state.current_rune_idx);
|
||||||
|
if (e > b) {
|
||||||
|
str.erase(b, e - b);
|
||||||
|
changed = true;
|
||||||
|
refresh_spans();
|
||||||
|
state.current_rune_idx = rune_index_for_byte(str_view, b);
|
||||||
|
state.clear_selection();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto move_left_word = [&]() {
|
||||||
|
while (state.current_rune_idx > 0
|
||||||
|
&& is_space(spans[(usize)(state.current_rune_idx - 1)].codepoint))
|
||||||
|
state.current_rune_idx--;
|
||||||
|
while (state.current_rune_idx > 0
|
||||||
|
&& !is_space(spans[(usize)(state.current_rune_idx - 1)].codepoint))
|
||||||
|
state.current_rune_idx--;
|
||||||
|
};
|
||||||
|
auto move_right_word = [&]() {
|
||||||
|
while (state.current_rune_idx < (int)spans.size()
|
||||||
|
&& is_space(spans[(usize)state.current_rune_idx].codepoint))
|
||||||
|
state.current_rune_idx++;
|
||||||
|
while (state.current_rune_idx < (int)spans.size()
|
||||||
|
&& !is_space(spans[(usize)state.current_rune_idx].codepoint))
|
||||||
|
state.current_rune_idx++;
|
||||||
|
};
|
||||||
|
|
||||||
bool caret_activity = false;
|
bool caret_activity = false;
|
||||||
|
|
||||||
if (m_focused_id == id && m_rune != 0) {
|
if (m_focused_id == id && m_rune != 0) {
|
||||||
bool request_refresh = false;
|
bool request_refresh = false;
|
||||||
auto handle_backspace { [&]() {
|
|
||||||
if (state.current_rune_idx <= 0
|
|
||||||
|| state.current_rune_idx > static_cast<int>(spans.size()))
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
auto handle_backspace = [&]() {
|
||||||
|
if (state.current_rune_idx <= 0
|
||||||
|
|| state.current_rune_idx > (int)spans.size())
|
||||||
|
return;
|
||||||
if (m_ctrl) {
|
if (m_ctrl) {
|
||||||
int idx = state.current_rune_idx;
|
int idx = state.current_rune_idx, scan = idx - 1;
|
||||||
int scan = idx - 1;
|
while (scan >= 0 && is_space(spans[(usize)scan].codepoint))
|
||||||
while (scan >= 0
|
|
||||||
&& is_space(spans[static_cast<usize>(scan)].codepoint))
|
|
||||||
scan--;
|
scan--;
|
||||||
while (scan >= 0
|
while (scan >= 0 && !is_space(spans[(usize)scan].codepoint))
|
||||||
&& !is_space(spans[static_cast<usize>(scan)].codepoint))
|
|
||||||
scan--;
|
scan--;
|
||||||
int start_idx = std::max(scan + 1, 0);
|
int start_idx = std::max(scan + 1, 0);
|
||||||
usize byte_begin = spans[static_cast<usize>(start_idx)].start;
|
usize b = spans[(usize)start_idx].start;
|
||||||
usize byte_end = (idx >= static_cast<int>(spans.size()))
|
usize e = (idx >= (int)spans.size()) ? str.size()
|
||||||
? str.size()
|
: spans[(usize)idx].start;
|
||||||
: spans[static_cast<usize>(idx)].start;
|
erase_range(b, e);
|
||||||
erase_range(byte_begin, byte_end);
|
|
||||||
state.current_rune_idx = start_idx;
|
state.current_rune_idx = start_idx;
|
||||||
} else {
|
} else {
|
||||||
auto const &prev
|
auto const &prev = spans[(usize)(state.current_rune_idx - 1)];
|
||||||
= spans[static_cast<usize>(state.current_rune_idx - 1)];
|
|
||||||
erase_range(prev.start, prev.end);
|
erase_range(prev.start, prev.end);
|
||||||
state.current_rune_idx--;
|
state.current_rune_idx--;
|
||||||
}
|
}
|
||||||
request_refresh = true;
|
request_refresh = true;
|
||||||
} };
|
};
|
||||||
|
|
||||||
auto handle_delete { [&]() {
|
auto handle_delete = [&]() {
|
||||||
if (state.current_rune_idx < 0
|
if (state.current_rune_idx < 0
|
||||||
|| state.current_rune_idx >= static_cast<int>(spans.size())) {
|
|| state.current_rune_idx >= (int)spans.size()) {
|
||||||
if (!m_ctrl)
|
if (!m_ctrl)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int idx = state.current_rune_idx;
|
int idx = state.current_rune_idx;
|
||||||
if (m_ctrl) {
|
if (m_ctrl) {
|
||||||
int scan = idx;
|
int scan = idx;
|
||||||
while (scan < static_cast<int>(spans.size())
|
while (scan < (int)spans.size()
|
||||||
&& is_space(spans[static_cast<usize>(scan)].codepoint))
|
&& is_space(spans[(usize)scan].codepoint))
|
||||||
scan++;
|
scan++;
|
||||||
while (scan < static_cast<int>(spans.size())
|
while (scan < (int)spans.size()
|
||||||
&& !is_space(spans[static_cast<usize>(scan)].codepoint))
|
&& !is_space(spans[(usize)scan].codepoint))
|
||||||
scan++;
|
scan++;
|
||||||
usize byte_begin = (idx < static_cast<int>(spans.size()))
|
usize b = (idx < (int)spans.size()) ? spans[(usize)idx].start
|
||||||
? spans[static_cast<usize>(idx)].start
|
: str.size();
|
||||||
: str.size();
|
usize e = (scan < (int)spans.size()) ? spans[(usize)scan].start
|
||||||
usize byte_end = (scan < static_cast<int>(spans.size()))
|
: str.size();
|
||||||
? spans[static_cast<usize>(scan)].start
|
erase_range(b, e);
|
||||||
: str.size();
|
} else if (idx < (int)spans.size()) {
|
||||||
erase_range(byte_begin, byte_end);
|
auto const &curr = spans[(usize)idx];
|
||||||
} else if (idx < static_cast<int>(spans.size())) {
|
|
||||||
auto const &curr { spans[static_cast<usize>(idx)] };
|
|
||||||
erase_range(curr.start, curr.end);
|
erase_range(curr.start, curr.end);
|
||||||
}
|
}
|
||||||
request_refresh = true;
|
request_refresh = true;
|
||||||
} };
|
};
|
||||||
|
|
||||||
|
bool extend = m_shift;
|
||||||
|
|
||||||
switch (m_rune) {
|
switch (m_rune) {
|
||||||
case 1: // Left (H)
|
case 1: // Left
|
||||||
|
if (!extend)
|
||||||
|
state.clear_selection();
|
||||||
if (state.current_rune_idx > 0) {
|
if (state.current_rune_idx > 0) {
|
||||||
state.current_rune_idx--;
|
if (extend && state.sel_anchor_idx == -1)
|
||||||
if (m_ctrl) {
|
state.sel_anchor_idx = state.current_rune_idx;
|
||||||
while (state.current_rune_idx > 0
|
if (m_ctrl)
|
||||||
&& is_space(
|
move_left_word();
|
||||||
spans[static_cast<usize>(state.current_rune_idx)]
|
else
|
||||||
.codepoint))
|
state.current_rune_idx--;
|
||||||
state.current_rune_idx--;
|
|
||||||
while (state.current_rune_idx > 0
|
|
||||||
&& !is_space(
|
|
||||||
spans[static_cast<usize>(state.current_rune_idx)]
|
|
||||||
.codepoint))
|
|
||||||
state.current_rune_idx--;
|
|
||||||
}
|
|
||||||
caret_byte = clamp_cursor();
|
caret_byte = clamp_cursor();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 4: // Right (L)
|
case 4: // Right
|
||||||
if (state.current_rune_idx < static_cast<int>(spans.size())) {
|
if (!extend)
|
||||||
state.current_rune_idx++;
|
state.clear_selection();
|
||||||
if (m_ctrl) {
|
if (state.current_rune_idx < (int)spans.size()) {
|
||||||
while (
|
if (extend && state.sel_anchor_idx == -1)
|
||||||
state.current_rune_idx < static_cast<int>(spans.size())
|
state.sel_anchor_idx = state.current_rune_idx;
|
||||||
&& is_space(spans[static_cast<usize>(
|
if (m_ctrl)
|
||||||
state.current_rune_idx - 1)]
|
move_right_word();
|
||||||
.codepoint))
|
else
|
||||||
state.current_rune_idx++;
|
state.current_rune_idx++;
|
||||||
while (
|
|
||||||
state.current_rune_idx < static_cast<int>(spans.size())
|
|
||||||
&& !is_space(spans[static_cast<usize>(
|
|
||||||
state.current_rune_idx - 1)]
|
|
||||||
.codepoint))
|
|
||||||
state.current_rune_idx++;
|
|
||||||
}
|
|
||||||
caret_byte = clamp_cursor();
|
caret_byte = clamp_cursor();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 3: // Up (K)
|
case 3: // Up -> home
|
||||||
|
if (!extend)
|
||||||
|
state.clear_selection();
|
||||||
|
if (extend && state.sel_anchor_idx == -1)
|
||||||
|
state.sel_anchor_idx = state.current_rune_idx;
|
||||||
state.current_rune_idx = 0;
|
state.current_rune_idx = 0;
|
||||||
caret_byte = clamp_cursor();
|
caret_byte = clamp_cursor();
|
||||||
break;
|
break;
|
||||||
case 2: // Down (J)
|
case 2: // Down -> end
|
||||||
state.current_rune_idx = static_cast<int>(spans.size());
|
if (!extend)
|
||||||
|
state.clear_selection();
|
||||||
|
if (extend && state.sel_anchor_idx == -1)
|
||||||
|
state.sel_anchor_idx = state.current_rune_idx;
|
||||||
|
state.current_rune_idx = (int)spans.size();
|
||||||
caret_byte = clamp_cursor();
|
caret_byte = clamp_cursor();
|
||||||
break;
|
break;
|
||||||
case 8: // Backspace
|
case 8: // Backspace
|
||||||
|
if (erase_selection_if_any()) {
|
||||||
|
request_refresh = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
handle_backspace();
|
handle_backspace();
|
||||||
break;
|
break;
|
||||||
case 0x7F: // Delete
|
case 0x7F: // Delete
|
||||||
|
if (erase_selection_if_any()) {
|
||||||
|
request_refresh = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
handle_delete();
|
handle_delete();
|
||||||
break;
|
break;
|
||||||
|
case 'a':
|
||||||
|
if (m_ctrl) {
|
||||||
|
state.sel_anchor_idx = 0;
|
||||||
|
state.current_rune_idx = (int)spans.size();
|
||||||
|
state.selecting_all = true;
|
||||||
|
request_refresh = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
[[fallthrough]];
|
||||||
|
case 'c':
|
||||||
|
if (m_ctrl) {
|
||||||
|
if (state.has_selection(state.current_rune_idx)
|
||||||
|
&& m_clipboard_set) {
|
||||||
|
auto [b, e] = selection_range_bytes(
|
||||||
|
state.sel_anchor_idx, state.current_rune_idx);
|
||||||
|
m_clipboard_set(std::string_view(str.data() + b, e - b));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
[[fallthrough]];
|
||||||
|
case 'x':
|
||||||
|
if (m_ctrl) {
|
||||||
|
if (state.has_selection(state.current_rune_idx)
|
||||||
|
&& m_clipboard_set) {
|
||||||
|
auto [b, e] = selection_range_bytes(
|
||||||
|
state.sel_anchor_idx, state.current_rune_idx);
|
||||||
|
m_clipboard_set(std::string_view(str.data() + b, e - b));
|
||||||
|
str.erase(b, e - b);
|
||||||
|
changed = true;
|
||||||
|
request_refresh = true;
|
||||||
|
refresh_spans();
|
||||||
|
state.current_rune_idx = rune_index_for_byte(str_view, b);
|
||||||
|
state.clear_selection();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
[[fallthrough]];
|
||||||
|
case 'v':
|
||||||
|
if (m_ctrl && !m_clipboard.empty()) {
|
||||||
|
erase_selection_if_any();
|
||||||
|
if (!options.multiline) {
|
||||||
|
std::string clip2;
|
||||||
|
clip2.reserve(m_clipboard.size());
|
||||||
|
for (auto ch : m_clipboard)
|
||||||
|
if (ch != '\n' && ch != '\r')
|
||||||
|
clip2.push_back(ch);
|
||||||
|
str.insert(caret_byte, clip2);
|
||||||
|
state.current_rune_idx += (int)utf8_length(clip2);
|
||||||
|
} else {
|
||||||
|
str.insert(caret_byte, m_clipboard);
|
||||||
|
state.current_rune_idx += (int)utf8_length(m_clipboard);
|
||||||
|
}
|
||||||
|
changed = true;
|
||||||
|
request_refresh = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
goto insert_printable;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case '\r':
|
case '\r':
|
||||||
case '\n':
|
case '\n':
|
||||||
if (options.multiline) {
|
if (options.multiline) {
|
||||||
|
erase_selection_if_any();
|
||||||
auto encoded { encode_utf8('\n') };
|
auto encoded { encode_utf8('\n') };
|
||||||
if (!encoded.empty()) {
|
if (!encoded.empty()) {
|
||||||
str.insert(caret_byte, encoded);
|
str.insert(caret_byte, encoded);
|
||||||
@@ -484,28 +586,10 @@ auto ImGui::text_input(usize id, std::pmr::string &str, Rectangle rec,
|
|||||||
submitted = true;
|
submitted = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'v':
|
|
||||||
if (m_ctrl && !m_clipboard.empty()) {
|
|
||||||
if (!options.multiline) {
|
|
||||||
std::string clipboard_no_newlines;
|
|
||||||
for (auto const &ch : m_clipboard) {
|
|
||||||
if (ch == '\n' || ch == '\r')
|
|
||||||
continue;
|
|
||||||
clipboard_no_newlines.push_back(ch);
|
|
||||||
}
|
|
||||||
str.insert(caret_byte, clipboard_no_newlines);
|
|
||||||
state.current_rune_idx
|
|
||||||
+= utf8_length(clipboard_no_newlines);
|
|
||||||
} else {
|
|
||||||
str.insert(caret_byte, m_clipboard);
|
|
||||||
state.current_rune_idx += utf8_length(m_clipboard);
|
|
||||||
}
|
|
||||||
changed = true;
|
|
||||||
request_refresh = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
|
insert_printable:
|
||||||
if (m_rune >= 0x20) {
|
if (m_rune >= 0x20) {
|
||||||
|
erase_selection_if_any();
|
||||||
auto encoded { encode_utf8(m_rune) };
|
auto encoded { encode_utf8(m_rune) };
|
||||||
if (!encoded.empty()) {
|
if (!encoded.empty()) {
|
||||||
str.insert(caret_byte, encoded);
|
str.insert(caret_byte, encoded);
|
||||||
@@ -517,17 +601,18 @@ auto ImGui::text_input(usize id, std::pmr::string &str, Rectangle rec,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request_refresh) {
|
if (request_refresh)
|
||||||
refresh_spans();
|
refresh_spans();
|
||||||
} else {
|
else
|
||||||
caret_byte = clamp_cursor();
|
caret_byte = clamp_cursor();
|
||||||
}
|
|
||||||
caret_activity = true;
|
caret_activity = true;
|
||||||
|
if (!m_shift)
|
||||||
|
state.clear_selection();
|
||||||
}
|
}
|
||||||
|
|
||||||
state.caret_byte = caret_byte;
|
state.caret_byte = caret_byte;
|
||||||
|
|
||||||
double const dt = static_cast<double>(GetFrameTime());
|
double const dt = (double)GetFrameTime();
|
||||||
if (m_focused_id == id) {
|
if (m_focused_id == id) {
|
||||||
if (state.preedit_active && state.preedit_cursor_hidden) {
|
if (state.preedit_active && state.preedit_cursor_hidden) {
|
||||||
state.caret_visible = false;
|
state.caret_visible = false;
|
||||||
@@ -540,8 +625,7 @@ auto ImGui::text_input(usize id, std::pmr::string &str, Rectangle rec,
|
|||||||
state.caret_visible = true;
|
state.caret_visible = true;
|
||||||
state.caret_timer += dt;
|
state.caret_timer += dt;
|
||||||
if (state.caret_timer >= CARET_BLINK_INTERVAL) {
|
if (state.caret_timer >= CARET_BLINK_INTERVAL) {
|
||||||
int toggles = static_cast<int>(
|
int toggles = (int)(state.caret_timer / CARET_BLINK_INTERVAL);
|
||||||
state.caret_timer / CARET_BLINK_INTERVAL);
|
|
||||||
state.caret_timer
|
state.caret_timer
|
||||||
= std::fmod(state.caret_timer, CARET_BLINK_INTERVAL);
|
= std::fmod(state.caret_timer, CARET_BLINK_INTERVAL);
|
||||||
if (toggles % 2 == 1)
|
if (toggles % 2 == 1)
|
||||||
@@ -553,82 +637,6 @@ auto ImGui::text_input(usize id, std::pmr::string &str, Rectangle rec,
|
|||||||
state.caret_timer = 0.0;
|
state.caret_timer = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
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>(style().font_size);
|
|
||||||
std::string_view const prefix_view(str.data(), caret_byte);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
if (full_metrics.x <= available_width) {
|
|
||||||
state.scroll_offset.x = 0.0f;
|
|
||||||
} else {
|
|
||||||
float &scroll = state.scroll_offset.x;
|
|
||||||
float caret_local = caret_offset - scroll;
|
|
||||||
if (caret_local > available_width) {
|
|
||||||
scroll = caret_offset - available_width;
|
|
||||||
} else if (caret_local < 0.0f) {
|
|
||||||
scroll = caret_offset;
|
|
||||||
}
|
|
||||||
scroll = std::clamp(
|
|
||||||
scroll, 0.0f, std::max(0.0f, full_metrics.x - available_width));
|
|
||||||
}
|
|
||||||
state.scroll_offset.y = 0.0f;
|
|
||||||
|
|
||||||
Color const bg_col { 16, 16, 16, 100 };
|
Color const bg_col { 16, 16, 16, 100 };
|
||||||
Color const border_col { 220, 220, 220, 180 };
|
Color const border_col { 220, 220, 220, 180 };
|
||||||
DrawRectangleRec(rec, bg_col);
|
DrawRectangleRec(rec, bg_col);
|
||||||
@@ -636,11 +644,11 @@ auto ImGui::text_input(usize id, std::pmr::string &str, Rectangle rec,
|
|||||||
|
|
||||||
float const text_top = rec.y + VERTICAL_PADDING;
|
float const text_top = rec.y + VERTICAL_PADDING;
|
||||||
float const baseline_y = text_top + style().font_size;
|
float const baseline_y = text_top + style().font_size;
|
||||||
float const max_caret_height
|
float const max_caret_h
|
||||||
= std::max(0.0f, rec.height - 2.0f * VERTICAL_PADDING);
|
= std::max(0.0f, rec.height - 2.0f * VERTICAL_PADDING);
|
||||||
float caret_height = style().font_size * (1.0f + CARET_DESCENT_FRACTION);
|
float caret_height = style().font_size * (1.0f + CARET_DESCENT_FRACTION);
|
||||||
caret_height = std::min(caret_height,
|
caret_height = std::min(
|
||||||
max_caret_height > 0.0f ? max_caret_height : caret_height);
|
caret_height, max_caret_h > 0.0f ? max_caret_h : caret_height);
|
||||||
if (caret_height <= 0.0f)
|
if (caret_height <= 0.0f)
|
||||||
caret_height = style().font_size;
|
caret_height = style().font_size;
|
||||||
float caret_top = baseline_y - style().font_size;
|
float caret_top = baseline_y - style().font_size;
|
||||||
@@ -657,72 +665,111 @@ auto ImGui::text_input(usize id, std::pmr::string &str, Rectangle rec,
|
|||||||
}
|
}
|
||||||
state.cursor_position.y = caret_top;
|
state.cursor_position.y = caret_top;
|
||||||
|
|
||||||
float const caret_draw_x
|
float const available_width
|
||||||
= rec.x + HORIZONTAL_PADDING + caret_offset - state.scroll_offset.x;
|
= std::max(0.0f, rec.width - 2.0f * HORIZONTAL_PADDING);
|
||||||
state.caret_rect = Rectangle {
|
float const base_y = px_pos(baseline_y);
|
||||||
caret_draw_x,
|
|
||||||
caret_top,
|
int const font_px = (int)style().font_size;
|
||||||
CARET_WIDTH,
|
|
||||||
caret_height,
|
Vector2 prefix_metrics { 0.0f, 0.0f };
|
||||||
};
|
{
|
||||||
|
std::string_view prefix(str.data(), caret_byte);
|
||||||
|
if (m_text_renderer)
|
||||||
|
prefix_metrics
|
||||||
|
= m_text_renderer->measure_text(*m_font, prefix, font_px);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 caret_preedit_metrics { 0.0f, 0.0f };
|
||||||
|
bool const has_preedit = state.preedit_active
|
||||||
|
&& (!state.preedit_text.empty() || !state.preedit_cursor_hidden);
|
||||||
|
if (has_preedit && m_text_renderer) {
|
||||||
|
auto pe_end = clamp_preedit_index(
|
||||||
|
state.preedit_cursor_end, state.preedit_text.size());
|
||||||
|
caret_preedit_metrics = m_text_renderer->measure_text(*m_font,
|
||||||
|
std::string_view(state.preedit_text.data(), (usize)pe_end),
|
||||||
|
font_px);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 full_metrics { 0.0f, 0.0f };
|
||||||
|
{
|
||||||
|
std::string display;
|
||||||
|
display.reserve(
|
||||||
|
str.size() + (has_preedit ? state.preedit_text.size() : 0));
|
||||||
|
display.append(std::string_view(str.data(), caret_byte));
|
||||||
|
if (has_preedit)
|
||||||
|
display.append(state.preedit_text);
|
||||||
|
display.append(
|
||||||
|
std::string_view(str.data() + caret_byte, str.size() - caret_byte));
|
||||||
|
if (m_text_renderer)
|
||||||
|
full_metrics = m_text_renderer->measure_text(*m_font,
|
||||||
|
std::string_view(display.data(), display.size()), font_px);
|
||||||
|
}
|
||||||
|
|
||||||
|
float caret_offset = prefix_metrics.x + caret_preedit_metrics.x;
|
||||||
|
state.cursor_position.x = caret_offset;
|
||||||
|
|
||||||
|
if (full_metrics.x <= available_width) {
|
||||||
|
state.scroll_offset.x = 0.0f;
|
||||||
|
} else {
|
||||||
|
float &scroll = state.scroll_offset.x;
|
||||||
|
float caret_local = caret_offset - scroll;
|
||||||
|
float const pad = 8.0f;
|
||||||
|
if (caret_local > available_width - pad)
|
||||||
|
scroll = caret_offset - (available_width - pad);
|
||||||
|
else if (caret_local < pad)
|
||||||
|
scroll = caret_offset - pad;
|
||||||
|
scroll = std::clamp(
|
||||||
|
scroll, 0.0f, std::max(0.0f, full_metrics.x - available_width));
|
||||||
|
}
|
||||||
|
state.scroll_offset.y = 0.0f;
|
||||||
|
|
||||||
|
float const origin = rec.x + HORIZONTAL_PADDING - state.scroll_offset.x;
|
||||||
|
|
||||||
BeginScissorMode(rec.x, rec.y, rec.width, rec.height);
|
BeginScissorMode(rec.x, rec.y, rec.width, rec.height);
|
||||||
{
|
{
|
||||||
if (m_font.has_value() && m_text_renderer) {
|
if (m_font.has_value() && m_text_renderer) {
|
||||||
int const font_px = static_cast<int>(style().font_size);
|
|
||||||
Vector2 const base_pos {
|
|
||||||
rec.x + HORIZONTAL_PADDING - state.scroll_offset.x,
|
|
||||||
baseline_y,
|
|
||||||
};
|
|
||||||
Color const &text_color = style().text_color;
|
Color const &text_color = style().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(
|
std::string display;
|
||||||
*m_font, left_view, base_pos, font_px, text_color);
|
display.reserve(
|
||||||
|
str.size() + (has_preedit ? state.preedit_text.size() : 0));
|
||||||
|
display.append(std::string_view(str.data(), caret_byte));
|
||||||
|
if (has_preedit)
|
||||||
|
display.append(state.preedit_text);
|
||||||
|
display.append(std::string_view(
|
||||||
|
str.data() + caret_byte, str.size() - caret_byte));
|
||||||
|
|
||||||
|
m_text_renderer->draw_text(*m_font,
|
||||||
|
std::string_view(display.data(), display.size()),
|
||||||
|
{ origin, base_y }, font_px, text_color);
|
||||||
|
|
||||||
|
if (state.has_selection(state.current_rune_idx)) {
|
||||||
|
auto [sb, se] = selection_range_bytes(
|
||||||
|
state.sel_anchor_idx, state.current_rune_idx);
|
||||||
|
|
||||||
|
Vector2 sel_prefix = m_text_renderer->measure_text(
|
||||||
|
*m_font, std::string_view(str.data(), sb), font_px);
|
||||||
|
Vector2 sel_width = m_text_renderer->measure_text(*m_font,
|
||||||
|
std::string_view(str.data() + sb, se - sb), font_px);
|
||||||
|
|
||||||
|
Rectangle sel_rect { std::floor(origin + sel_prefix.x + 0.5f),
|
||||||
|
std::floor(caret_top + 0.5f),
|
||||||
|
std::max(1.0f, sel_width.x) + 1,
|
||||||
|
std::max(1.0f, std::round(caret_height)) };
|
||||||
|
DrawRectangleRec(sel_rect, style().selection_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 { style().selection_color };
|
|
||||||
DrawRectangleRec(sel_rect, highlight);
|
|
||||||
}
|
|
||||||
Color const &preedit_color { selection_metrics.x > 0.0f
|
|
||||||
? style().selection_text_color
|
|
||||||
: style().preedit_color };
|
|
||||||
m_text_renderer->draw_text(*m_font,
|
m_text_renderer->draw_text(*m_font,
|
||||||
std::string_view(
|
std::string_view(str.data() + sb, se - sb),
|
||||||
state.preedit_text.data(), state.preedit_text.size()),
|
{ origin + sel_prefix.x, base_y }, font_px,
|
||||||
preedit_pos, font_px, preedit_color);
|
style().selection_text_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) {
|
if (m_focused_id == id && state.caret_visible) {
|
||||||
Rectangle caret_rect = state.caret_rect;
|
float const caret_x = std::floor(origin + caret_offset + 0.5f);
|
||||||
caret_rect.x = std::round(caret_rect.x);
|
Vector2 p0 { caret_x, std::floor(caret_top + 0.5f) };
|
||||||
Color const caret_color { text_color };
|
Vector2 p1 { caret_x,
|
||||||
DrawRectangleRec(caret_rect, caret_color);
|
std::floor((caret_top + caret_height) + 0.5f) };
|
||||||
|
DrawLineV(p0, p1, text_color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,6 +112,18 @@ private:
|
|||||||
usize caret_byte { 0 };
|
usize caret_byte { 0 };
|
||||||
Rectangle caret_rect {};
|
Rectangle caret_rect {};
|
||||||
bool external_change { false };
|
bool external_change { false };
|
||||||
|
int sel_anchor_idx = -1;
|
||||||
|
bool selecting_all = false;
|
||||||
|
|
||||||
|
bool has_selection(int curr_idx) const
|
||||||
|
{
|
||||||
|
return sel_anchor_idx != -1 && sel_anchor_idx != curr_idx;
|
||||||
|
}
|
||||||
|
void clear_selection()
|
||||||
|
{
|
||||||
|
sel_anchor_idx = -1;
|
||||||
|
selecting_all = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ListViewState {
|
struct ListViewState {
|
||||||
|
|||||||
@@ -253,6 +253,9 @@ auto TextRenderer::generate_glyph(FontRuntime &rt, FontData &fd,
|
|||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
auto c1 { (int)std::round(msdf_bitmap(0, 0)[3]) };
|
||||||
|
auto c4 { (int)std::round(msdf_bitmap(bmp_w - 1, bmp_h - 1)[3]) };
|
||||||
|
|
||||||
auto sum_white = 0;
|
auto sum_white = 0;
|
||||||
auto sum_black = 0;
|
auto sum_black = 0;
|
||||||
for (int y = 0; y < bmp_h; ++y) {
|
for (int y = 0; y < bmp_h; ++y) {
|
||||||
@@ -266,6 +269,11 @@ auto TextRenderer::generate_glyph(FontRuntime &rt, FontData &fd,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bool flip { sum_white > sum_black && (float)bmp_w / (float)bmp_h > 0.6 };
|
||||||
|
if (c1 == c4) {
|
||||||
|
flip = false;
|
||||||
|
}
|
||||||
|
|
||||||
// This really isn't the most accurate thing in the world but should work
|
// This really isn't the most accurate thing in the world but should work
|
||||||
// for now. Things like commas might be fucked.
|
// for now. Things like commas might be fucked.
|
||||||
for (int y = 0; y < bmp_h; ++y) {
|
for (int y = 0; y < bmp_h; ++y) {
|
||||||
@@ -273,7 +281,7 @@ auto TextRenderer::generate_glyph(FontRuntime &rt, FontData &fd,
|
|||||||
for (int x = 0; x < bmp_w; ++x) {
|
for (int x = 0; x < bmp_w; ++x) {
|
||||||
float const *px = msdf_bitmap(x, y);
|
float const *px = msdf_bitmap(x, y);
|
||||||
auto const r = msdfgen::pixelFloatToByte(px[0]);
|
auto const r = msdfgen::pixelFloatToByte(px[0]);
|
||||||
if (sum_white > sum_black && (float)bmp_w / (float)bmp_h > 0.6) {
|
if (flip) {
|
||||||
buffer[static_cast<usize>(dst_y) * bmp_w + x] = Color { 255,
|
buffer[static_cast<usize>(dst_y) * bmp_w + x] = Color { 255,
|
||||||
255, 255, static_cast<unsigned char>(255 - r) };
|
255, 255, static_cast<unsigned char>(255 - r) };
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
3
todo.txt
3
todo.txt
@@ -1,5 +1,4 @@
|
|||||||
- [ ] Fix text moving left and right based on cursor position (right side of cursor is too left)
|
- [ ] Fix text moving left and right based on cursor position (right side of cursor is too left)
|
||||||
- [ ] Integrate IME directly into ImGui
|
|
||||||
- [ ] Implement selection in ImGui::text_edit
|
- [ ] Implement selection in ImGui::text_edit
|
||||||
- [ ] Implement clipboard copy/paste
|
- [-] Implement clipboard copy/paste
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user