144
src/App.cpp
144
src/App.cpp
@@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr std::size_t MAX_SURROUNDING_BYTES = 4000;
|
constexpr usize MAX_SURROUNDING_BYTES = 4000;
|
||||||
|
|
||||||
inline auto is_utf8_continuation(char c) -> bool
|
inline auto is_utf8_continuation(char c) -> bool
|
||||||
{
|
{
|
||||||
@@ -44,8 +44,8 @@ inline auto is_utf8_continuation(char c) -> bool
|
|||||||
inline auto adjust_utf8_backward(std::string const &text, int index) -> int
|
inline auto adjust_utf8_backward(std::string const &text, int index) -> int
|
||||||
{
|
{
|
||||||
index = std::clamp(index, 0, static_cast<int>(text.size()));
|
index = std::clamp(index, 0, static_cast<int>(text.size()));
|
||||||
while (index > 0
|
while (
|
||||||
&& is_utf8_continuation(text[static_cast<std::size_t>(index - 1)]))
|
index > 0 && is_utf8_continuation(text[static_cast<usize>(index - 1)]))
|
||||||
--index;
|
--index;
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
@@ -54,8 +54,8 @@ inline auto adjust_utf8_forward(std::string const &text, int index) -> int
|
|||||||
{
|
{
|
||||||
int const size = static_cast<int>(text.size());
|
int const size = static_cast<int>(text.size());
|
||||||
index = std::clamp(index, 0, size);
|
index = std::clamp(index, 0, size);
|
||||||
while (index < size
|
while (
|
||||||
&& is_utf8_continuation(text[static_cast<std::size_t>(index)]))
|
index < size && is_utf8_continuation(text[static_cast<usize>(index)]))
|
||||||
++index;
|
++index;
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
@@ -221,9 +221,9 @@ auto App::init_wayland() -> void
|
|||||||
|
|
||||||
static wl_keyboard_listener keyboard_listener {};
|
static wl_keyboard_listener keyboard_listener {};
|
||||||
{
|
{
|
||||||
auto kb_keymap = [](void *data, wl_keyboard *, u32 format, i32 fd,
|
auto kb_keymap { [](void *data, wl_keyboard *, u32 format, i32 fd,
|
||||||
u32 size) -> void {
|
u32 size) -> void {
|
||||||
auto *app = static_cast<App *>(data);
|
auto *app { static_cast<App *>(data) };
|
||||||
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
|
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
|
||||||
close(fd);
|
close(fd);
|
||||||
return;
|
return;
|
||||||
@@ -247,18 +247,18 @@ auto App::init_wayland() -> void
|
|||||||
: nullptr;
|
: nullptr;
|
||||||
munmap(map, size);
|
munmap(map, size);
|
||||||
close(fd);
|
close(fd);
|
||||||
};
|
} };
|
||||||
|
|
||||||
auto kb_enter = [](void *, wl_keyboard *, u32, wl_surface *,
|
auto kb_enter { [](void *, wl_keyboard *, u32, wl_surface *,
|
||||||
wl_array *) -> void { };
|
wl_array *) -> void { } };
|
||||||
auto kb_leave
|
auto kb_leave
|
||||||
= [](void *data, wl_keyboard *, u32, wl_surface *) -> void {
|
= [](void *data, wl_keyboard *, u32, wl_surface *) -> void {
|
||||||
static_cast<App *>(data)->m_kbd.held.clear();
|
static_cast<App *>(data)->m_kbd.held.clear();
|
||||||
};
|
};
|
||||||
|
|
||||||
auto kb_key = [](void *data, wl_keyboard *, u32, u32, u32 key,
|
auto kb_key { [](void *data, wl_keyboard *, u32, u32, u32 key,
|
||||||
u32 state) -> void {
|
u32 state) -> void {
|
||||||
auto *app = static_cast<App *>(data);
|
auto *app { static_cast<App *>(data) };
|
||||||
if (!app->m_kbd.xkb_state_v)
|
if (!app->m_kbd.xkb_state_v)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -336,18 +336,18 @@ auto App::init_wayland() -> void
|
|||||||
app->m_kbd.held.erase(key);
|
app->m_kbd.held.erase(key);
|
||||||
xkb_state_update_key(app->m_kbd.xkb_state_v, kc, XKB_KEY_UP);
|
xkb_state_update_key(app->m_kbd.xkb_state_v, kc, XKB_KEY_UP);
|
||||||
}
|
}
|
||||||
};
|
} };
|
||||||
|
|
||||||
auto kb_mods = [](void *data, wl_keyboard *, u32, u32 depressed,
|
auto kb_mods { [](void *data, wl_keyboard *, u32, u32 depressed,
|
||||||
u32 latched, u32 locked, u32 group) -> void {
|
u32 latched, u32 locked, u32 group) -> void {
|
||||||
auto *app = static_cast<App *>(data);
|
auto *app { static_cast<App *>(data) };
|
||||||
if (!app->m_kbd.xkb_state_v)
|
if (!app->m_kbd.xkb_state_v)
|
||||||
return;
|
return;
|
||||||
xkb_state_update_mask(app->m_kbd.xkb_state_v, depressed, latched,
|
xkb_state_update_mask(app->m_kbd.xkb_state_v, depressed, latched,
|
||||||
locked, 0, 0, group);
|
locked, 0, 0, group);
|
||||||
};
|
} };
|
||||||
|
|
||||||
auto kb_repeat_info = [](void *, wl_keyboard *, i32, i32) -> void { };
|
auto kb_repeat_info { [](void *, wl_keyboard *, i32, i32) -> void { } };
|
||||||
|
|
||||||
keyboard_listener = { kb_keymap, kb_enter, kb_leave, kb_key, kb_mods,
|
keyboard_listener = { kb_keymap, kb_enter, kb_leave, kb_key, kb_mods,
|
||||||
kb_repeat_info };
|
kb_repeat_info };
|
||||||
@@ -357,7 +357,7 @@ auto App::init_wayland() -> void
|
|||||||
{
|
{
|
||||||
auto ti_enter
|
auto ti_enter
|
||||||
= [](void *data, zwp_text_input_v3 *, wl_surface *surface) -> void {
|
= [](void *data, zwp_text_input_v3 *, wl_surface *surface) -> void {
|
||||||
auto *app = static_cast<App *>(data);
|
auto *app { static_cast<App *>(data) };
|
||||||
bool const focused_surface
|
bool const focused_surface
|
||||||
= surface && surface == app->m_wayland.surface;
|
= surface && surface == app->m_wayland.surface;
|
||||||
app->m_ime.seat_focus = focused_surface;
|
app->m_ime.seat_focus = focused_surface;
|
||||||
@@ -373,9 +373,9 @@ auto App::init_wayland() -> void
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
auto ti_leave
|
auto ti_leave { [](void *data, zwp_text_input_v3 *,
|
||||||
= [](void *data, zwp_text_input_v3 *, wl_surface *) -> void {
|
wl_surface *) -> void {
|
||||||
auto *app = static_cast<App *>(data);
|
auto *app { static_cast<App *>(data) };
|
||||||
app->m_ime.seat_focus = false;
|
app->m_ime.seat_focus = false;
|
||||||
app->m_ime.enabled = false;
|
app->m_ime.enabled = false;
|
||||||
app->m_ime.pending = {};
|
app->m_ime.pending = {};
|
||||||
@@ -383,39 +383,39 @@ auto App::init_wayland() -> void
|
|||||||
app->m_ime.last_surrounding.clear();
|
app->m_ime.last_surrounding.clear();
|
||||||
if (app->m_gui)
|
if (app->m_gui)
|
||||||
app->m_gui->ime_clear_preedit();
|
app->m_gui->ime_clear_preedit();
|
||||||
};
|
} };
|
||||||
|
|
||||||
auto ti_preedit
|
auto ti_preedit { [](void *data, zwp_text_input_v3 *, char const *text,
|
||||||
= [](void *data, zwp_text_input_v3 *, char const *text,
|
int32_t cursor_begin,
|
||||||
int32_t cursor_begin, int32_t cursor_end) -> void {
|
int32_t cursor_end) -> void {
|
||||||
auto *app = static_cast<App *>(data);
|
auto *app { static_cast<App *>(data) };
|
||||||
auto &pending = app->m_ime.pending;
|
auto &pending { app->m_ime.pending };
|
||||||
pending.has_preedit = true;
|
pending.has_preedit = true;
|
||||||
pending.preedit_text = text ? text : "";
|
pending.preedit_text = text ? text : "";
|
||||||
pending.cursor_begin = cursor_begin;
|
pending.cursor_begin = cursor_begin;
|
||||||
pending.cursor_end = cursor_end;
|
pending.cursor_end = cursor_end;
|
||||||
};
|
} };
|
||||||
|
|
||||||
auto ti_commit
|
auto ti_commit { [](void *data, zwp_text_input_v3 *,
|
||||||
= [](void *data, zwp_text_input_v3 *, char const *text) -> void {
|
char const *text) -> void {
|
||||||
auto *app = static_cast<App *>(data);
|
auto *app { static_cast<App *>(data) };
|
||||||
auto &pending = app->m_ime.pending;
|
auto &pending { app->m_ime.pending };
|
||||||
pending.has_commit = true;
|
pending.has_commit = true;
|
||||||
pending.commit_text = text ? text : "";
|
pending.commit_text = text ? text : "";
|
||||||
};
|
} };
|
||||||
|
|
||||||
auto ti_delete = [](void *data, zwp_text_input_v3 *, uint32_t before,
|
auto ti_delete { [](void *data, zwp_text_input_v3 *, uint32_t before,
|
||||||
uint32_t after) -> void {
|
uint32_t after) -> void {
|
||||||
auto *app = static_cast<App *>(data);
|
auto *app { static_cast<App *>(data) };
|
||||||
auto &pending = app->m_ime.pending;
|
auto &pending { app->m_ime.pending };
|
||||||
pending.has_delete = true;
|
pending.has_delete = true;
|
||||||
pending.before = before;
|
pending.before = before;
|
||||||
pending.after = after;
|
pending.after = after;
|
||||||
};
|
} };
|
||||||
|
|
||||||
auto ti_done
|
auto ti_done
|
||||||
= [](void *data, zwp_text_input_v3 *, uint32_t serial) -> void {
|
= [](void *data, zwp_text_input_v3 *, uint32_t serial) -> void {
|
||||||
auto *app = static_cast<App *>(data);
|
auto *app { static_cast<App *>(data) };
|
||||||
app->m_ime.pending_done = true;
|
app->m_ime.pending_done = true;
|
||||||
app->m_ime.pending_serial = serial;
|
app->m_ime.pending_serial = serial;
|
||||||
app->m_ime.surrounding_dirty = true;
|
app->m_ime.surrounding_dirty = true;
|
||||||
@@ -425,7 +425,7 @@ auto App::init_wayland() -> void
|
|||||||
= { ti_enter, ti_leave, ti_preedit, ti_commit, ti_delete, ti_done };
|
= { ti_enter, ti_leave, ti_preedit, ti_commit, ti_delete, ti_done };
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto ensure_text_input = +[](App *app) -> void {
|
static auto ensure_text_input { +[](App *app) -> void {
|
||||||
if (!app->m_wayland.text_input_mgr || !app->m_wayland.seat
|
if (!app->m_wayland.text_input_mgr || !app->m_wayland.seat
|
||||||
|| app->m_wayland.text_input)
|
|| app->m_wayland.text_input)
|
||||||
return;
|
return;
|
||||||
@@ -439,12 +439,12 @@ auto App::init_wayland() -> void
|
|||||||
app->m_ime.enabled = false;
|
app->m_ime.enabled = false;
|
||||||
app->m_ime.last_surrounding.clear();
|
app->m_ime.last_surrounding.clear();
|
||||||
app->m_ime.sent_serial = 0;
|
app->m_ime.sent_serial = 0;
|
||||||
};
|
} };
|
||||||
|
|
||||||
auto handle_registry_global
|
auto handle_registry_global
|
||||||
= [](void *data, wl_registry *registry, u32 name, char const *interface,
|
= [](void *data, wl_registry *registry, u32 name, char const *interface,
|
||||||
u32 version) -> void {
|
u32 version) -> void {
|
||||||
auto *app = static_cast<App *>(data);
|
auto *app { static_cast<App *>(data) };
|
||||||
if (std::strcmp(interface, wl_compositor_interface.name) == 0) {
|
if (std::strcmp(interface, wl_compositor_interface.name) == 0) {
|
||||||
app->m_wayland.compositor = static_cast<wl_compositor *>(
|
app->m_wayland.compositor = static_cast<wl_compositor *>(
|
||||||
wl_registry_bind(registry, name, &wl_compositor_interface, 4));
|
wl_registry_bind(registry, name, &wl_compositor_interface, 4));
|
||||||
@@ -454,7 +454,7 @@ auto App::init_wayland() -> void
|
|||||||
static struct wl_seat_listener const seat_listener = {
|
static struct wl_seat_listener const seat_listener = {
|
||||||
.capabilities =
|
.capabilities =
|
||||||
[](void *data, struct wl_seat *seat, u32 caps) {
|
[](void *data, struct wl_seat *seat, u32 caps) {
|
||||||
auto *app = static_cast<App *>(data);
|
auto *app { static_cast<App *>(data) };
|
||||||
if (caps & WL_SEAT_CAPABILITY_KEYBOARD) {
|
if (caps & WL_SEAT_CAPABILITY_KEYBOARD) {
|
||||||
app->m_wayland.kbd = wl_seat_get_keyboard(seat);
|
app->m_wayland.kbd = wl_seat_get_keyboard(seat);
|
||||||
wl_keyboard_add_listener(
|
wl_keyboard_add_listener(
|
||||||
@@ -530,11 +530,11 @@ auto App::init_egl() -> void
|
|||||||
|
|
||||||
m_tr = std::make_shared<TextRenderer>();
|
m_tr = std::make_shared<TextRenderer>();
|
||||||
m_gui = std::make_shared<ImGui>(m_tr);
|
m_gui = std::make_shared<ImGui>(m_tr);
|
||||||
auto const font = find_font_path();
|
auto const font { find_font_path() };
|
||||||
assert(font && "Could not find font");
|
assert(font && "Could not find font");
|
||||||
std::vector<std::filesystem::path> fallback_paths;
|
std::vector<std::filesystem::path> fallback_paths;
|
||||||
std::unordered_set<std::string> seen_paths;
|
std::unordered_set<std::string> seen_paths;
|
||||||
auto const primary_path_str = font->string();
|
auto const primary_path_str { font->string() };
|
||||||
|
|
||||||
constexpr char const *fallback_candidates[] = {
|
constexpr char const *fallback_candidates[] = {
|
||||||
"Noto Sans CJK JP:style=Regular",
|
"Noto Sans CJK JP:style=Regular",
|
||||||
@@ -546,10 +546,11 @@ auto App::init_egl() -> void
|
|||||||
"sans-serif:lang=zh-cn",
|
"sans-serif:lang=zh-cn",
|
||||||
"sans-serif:lang=zh-tw",
|
"sans-serif:lang=zh-tw",
|
||||||
"sans-serif:lang=zh-hk",
|
"sans-serif:lang=zh-hk",
|
||||||
|
"Noto Color Emoji:style=Regular",
|
||||||
};
|
};
|
||||||
for (auto const *name : fallback_candidates) {
|
for (auto const *name : fallback_candidates) {
|
||||||
if (auto fallback = find_font_path(name)) {
|
if (auto fallback { find_font_path(name) }) {
|
||||||
auto const path_str = fallback->string();
|
auto const path_str { fallback->string() };
|
||||||
if (path_str == primary_path_str)
|
if (path_str == primary_path_str)
|
||||||
continue;
|
continue;
|
||||||
if (!seen_paths.emplace(path_str).second)
|
if (!seen_paths.emplace(path_str).second)
|
||||||
@@ -561,7 +562,8 @@ auto App::init_egl() -> void
|
|||||||
TraceLog(LOG_WARNING,
|
TraceLog(LOG_WARNING,
|
||||||
"No fallback fonts found; some glyphs may render as missing");
|
"No fallback fonts found; some glyphs may render as missing");
|
||||||
}
|
}
|
||||||
auto const font_handle = m_tr->load_font(*font, std::span(fallback_paths));
|
auto const font_handle { m_tr->load_font(
|
||||||
|
*font, std::span(fallback_paths)) };
|
||||||
assert(font_handle && "Could not load font");
|
assert(font_handle && "Could not load font");
|
||||||
m_font = *font_handle;
|
m_font = *font_handle;
|
||||||
m_gui->set_font(m_font);
|
m_gui->set_font(m_font);
|
||||||
@@ -588,7 +590,7 @@ auto App::init_signal() -> void
|
|||||||
void App::on_settings_changed(XdpSettings * /*self*/, char const *ns,
|
void App::on_settings_changed(XdpSettings * /*self*/, char const *ns,
|
||||||
char const *key, GVariant * /*value*/, gpointer data)
|
char const *key, GVariant * /*value*/, gpointer data)
|
||||||
{
|
{
|
||||||
auto *app = static_cast<App *>(data);
|
auto *app { static_cast<App *>(data) };
|
||||||
if (g_strcmp0(ns, "org.freedesktop.appearance") == 0) {
|
if (g_strcmp0(ns, "org.freedesktop.appearance") == 0) {
|
||||||
if (g_strcmp0(key, "color-scheme") == 0) {
|
if (g_strcmp0(key, "color-scheme") == 0) {
|
||||||
guint v = xdp_settings_read_uint(app->m_xdp.settings,
|
guint v = xdp_settings_read_uint(app->m_xdp.settings,
|
||||||
@@ -599,8 +601,8 @@ void App::on_settings_changed(XdpSettings * /*self*/, char const *ns,
|
|||||||
else
|
else
|
||||||
app->m_active_theme = Theme::Light;
|
app->m_active_theme = Theme::Light;
|
||||||
} else if (g_strcmp0(key, "accent-color") == 0) {
|
} else if (g_strcmp0(key, "accent-color") == 0) {
|
||||||
auto val = xdp_settings_read_value(app->m_xdp.settings,
|
auto val { xdp_settings_read_value(app->m_xdp.settings,
|
||||||
"org.freedesktop.appearance", "accent-color", NULL, NULL);
|
"org.freedesktop.appearance", "accent-color", NULL, NULL) };
|
||||||
if (val) {
|
if (val) {
|
||||||
gdouble r, g, b;
|
gdouble r, g, b;
|
||||||
g_variant_get(val, "(ddd)", &r, &g, &b);
|
g_variant_get(val, "(ddd)", &r, &g, &b);
|
||||||
@@ -625,8 +627,8 @@ auto App::init_theme_portal() -> void
|
|||||||
else
|
else
|
||||||
m_active_theme = Theme::Light;
|
m_active_theme = Theme::Light;
|
||||||
|
|
||||||
auto val = xdp_settings_read_value(m_xdp.settings,
|
auto val { xdp_settings_read_value(m_xdp.settings,
|
||||||
"org.freedesktop.appearance", "accent-color", NULL, NULL);
|
"org.freedesktop.appearance", "accent-color", NULL, NULL) };
|
||||||
if (val) {
|
if (val) {
|
||||||
gdouble r, g, b;
|
gdouble r, g, b;
|
||||||
g_variant_get(val, "(ddd)", &r, &g, &b);
|
g_variant_get(val, "(ddd)", &r, &g, &b);
|
||||||
@@ -689,9 +691,9 @@ auto App::create_layer_surface() -> void
|
|||||||
ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND);
|
ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto handle_layer_configure = [](void *data, zwlr_layer_surface_v1 *ls,
|
auto handle_layer_configure { [](void *data, zwlr_layer_surface_v1 *ls,
|
||||||
u32 serial, u32 w, u32 h) -> void {
|
u32 serial, u32 w, u32 h) -> void {
|
||||||
auto *app = static_cast<App *>(data);
|
auto *app { static_cast<App *>(data) };
|
||||||
if (w)
|
if (w)
|
||||||
app->m_win_w = static_cast<int>(w);
|
app->m_win_w = static_cast<int>(w);
|
||||||
if (h)
|
if (h)
|
||||||
@@ -714,11 +716,11 @@ auto App::create_layer_surface() -> void
|
|||||||
|
|
||||||
if (app->m_wayland.surface)
|
if (app->m_wayland.surface)
|
||||||
wl_surface_commit(app->m_wayland.surface);
|
wl_surface_commit(app->m_wayland.surface);
|
||||||
};
|
} };
|
||||||
|
|
||||||
auto handle_layer_closed = [](void *data, zwlr_layer_surface_v1 *) -> void {
|
auto handle_layer_closed { [](void *data, zwlr_layer_surface_v1 *) -> void {
|
||||||
static_cast<App *>(data)->m_running = false;
|
static_cast<App *>(data)->m_running = false;
|
||||||
};
|
} };
|
||||||
|
|
||||||
static zwlr_layer_surface_v1_listener const lsl = {
|
static zwlr_layer_surface_v1_listener const lsl = {
|
||||||
.configure = handle_layer_configure,
|
.configure = handle_layer_configure,
|
||||||
@@ -837,7 +839,7 @@ auto App::process_pending_text_input() -> void
|
|||||||
if (!m_wayland.text_input)
|
if (!m_wayland.text_input)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto focused = m_gui->focused_text_input();
|
auto focused { m_gui->focused_text_input() };
|
||||||
if (!focused || *focused != m_ime.bound_id) {
|
if (!focused || *focused != m_ime.bound_id) {
|
||||||
m_ime.pending = {};
|
m_ime.pending = {};
|
||||||
m_ime.pending_done = false;
|
m_ime.pending_done = false;
|
||||||
@@ -868,16 +870,16 @@ auto App::process_pending_text_input() -> void
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto App::update_text_input_state(
|
auto App::update_text_input_state(
|
||||||
std::pmr::string const &text, std::size_t id, Rectangle field_rect) -> void
|
std::pmr::string const &text, usize id, Rectangle field_rect) -> void
|
||||||
{
|
{
|
||||||
if (!m_wayland.text_input || !m_ime.supported || !m_gui)
|
if (!m_wayland.text_input || !m_ime.supported || !m_gui)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_ime.bound_rect = field_rect;
|
m_ime.bound_rect = field_rect;
|
||||||
|
|
||||||
auto focused = m_gui->focused_text_input();
|
auto focused { m_gui->focused_text_input() };
|
||||||
bool const has_focus = focused && (*focused == id);
|
bool const has_focus { focused && (*focused == id) };
|
||||||
bool const should_enable = has_focus && m_ime.seat_focus;
|
bool const should_enable { has_focus && m_ime.seat_focus };
|
||||||
|
|
||||||
if (!should_enable) {
|
if (!should_enable) {
|
||||||
if (m_ime.enabled) {
|
if (m_ime.enabled) {
|
||||||
@@ -902,7 +904,7 @@ auto App::update_text_input_state(
|
|||||||
state_dirty = true;
|
state_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto info = m_gui->text_input_surrounding(id, text)) {
|
if (auto info { m_gui->text_input_surrounding(id, text) }) {
|
||||||
auto slice
|
auto slice
|
||||||
= clamp_surrounding_text(info->text, info->cursor, info->anchor);
|
= clamp_surrounding_text(info->text, info->cursor, info->anchor);
|
||||||
if (m_ime.surrounding_dirty || slice.text != m_ime.last_surrounding
|
if (m_ime.surrounding_dirty || slice.text != m_ime.last_surrounding
|
||||||
@@ -917,7 +919,7 @@ auto App::update_text_input_state(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto cursor_info = m_gui->text_input_cursor(id)) {
|
if (auto cursor_info { m_gui->text_input_cursor(id) }) {
|
||||||
Rectangle rect = cursor_info->rect;
|
Rectangle rect = cursor_info->rect;
|
||||||
int32_t const x = static_cast<int32_t>(std::round(rect.x));
|
int32_t const x = static_cast<int32_t>(std::round(rect.x));
|
||||||
int32_t const y = static_cast<int32_t>(std::round(rect.y));
|
int32_t const y = static_cast<int32_t>(std::round(rect.y));
|
||||||
@@ -958,8 +960,8 @@ auto App::pump_events() -> void
|
|||||||
pollfd fds[2] { { wl_display_get_fd(m_wayland.display), POLLIN, 0 },
|
pollfd fds[2] { { wl_display_get_fd(m_wayland.display), POLLIN, 0 },
|
||||||
{ m_sfd, POLLIN, 0 } };
|
{ m_sfd, POLLIN, 0 } };
|
||||||
|
|
||||||
auto prepared = (wl_display_prepare_read(m_wayland.display) == 0);
|
auto prepared { (wl_display_prepare_read(m_wayland.display) == 0) };
|
||||||
auto ret = poll(fds, 2, 0);
|
auto ret { poll(fds, 2, 0) };
|
||||||
|
|
||||||
if (ret > 0 && (fds[0].revents & POLLIN)) {
|
if (ret > 0 && (fds[0].revents & POLLIN)) {
|
||||||
if (prepared) {
|
if (prepared) {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ private:
|
|||||||
auto ensure_egl_surface() -> void;
|
auto ensure_egl_surface() -> void;
|
||||||
auto update_blur_region() -> void;
|
auto update_blur_region() -> void;
|
||||||
auto process_pending_text_input() -> void;
|
auto process_pending_text_input() -> void;
|
||||||
auto update_text_input_state(std::pmr::string const &text, std::size_t id,
|
auto update_text_input_state(std::pmr::string const &text, usize id,
|
||||||
Rectangle field_rect) -> void;
|
Rectangle field_rect) -> void;
|
||||||
auto theme() const -> ColorScheme const &
|
auto theme() const -> ColorScheme const &
|
||||||
{
|
{
|
||||||
@@ -160,7 +160,7 @@ private:
|
|||||||
uint32_t sent_serial { 0 };
|
uint32_t sent_serial { 0 };
|
||||||
|
|
||||||
std::pmr::string *bound_text { nullptr };
|
std::pmr::string *bound_text { nullptr };
|
||||||
std::size_t bound_id { 0 };
|
usize bound_id { 0 };
|
||||||
Rectangle bound_rect {};
|
Rectangle bound_rect {};
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|||||||
181
src/ImGui.cpp
181
src/ImGui.cpp
@@ -15,20 +15,20 @@ namespace {
|
|||||||
|
|
||||||
struct CodepointSpan {
|
struct CodepointSpan {
|
||||||
u32 codepoint {};
|
u32 codepoint {};
|
||||||
std::size_t start {};
|
usize start {};
|
||||||
std::size_t end {};
|
usize end {};
|
||||||
};
|
};
|
||||||
|
|
||||||
auto decode_utf8(std::string_view text) -> std::vector<CodepointSpan>
|
auto decode_utf8(std::string_view text) -> std::vector<CodepointSpan>
|
||||||
{
|
{
|
||||||
std::vector<CodepointSpan> spans;
|
std::vector<CodepointSpan> spans;
|
||||||
std::size_t i = 0;
|
usize i = 0;
|
||||||
spans.reserve(text.size());
|
spans.reserve(text.size());
|
||||||
|
|
||||||
while (i < text.size()) {
|
while (i < text.size()) {
|
||||||
u8 const byte = static_cast<u8>(text[i]);
|
u8 const byte = static_cast<u8>(text[i]);
|
||||||
std::size_t const start = i;
|
usize const start = i;
|
||||||
std::size_t length = 1;
|
usize length = 1;
|
||||||
u32 cp = 0xFFFD;
|
u32 cp = 0xFFFD;
|
||||||
|
|
||||||
if (byte < 0x80) {
|
if (byte < 0x80) {
|
||||||
@@ -111,9 +111,9 @@ auto encode_utf8(u32 cp) -> std::string
|
|||||||
return std::string(buf, len);
|
return std::string(buf, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto rune_index_for_byte(std::string_view text, std::size_t byte_offset) -> int
|
auto rune_index_for_byte(std::string_view text, usize byte_offset) -> int
|
||||||
{
|
{
|
||||||
auto spans = decode_utf8(text);
|
auto spans { decode_utf8(text) };
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
for (auto const &span : spans) {
|
for (auto const &span : spans) {
|
||||||
if (span.start >= byte_offset)
|
if (span.start >= byte_offset)
|
||||||
@@ -125,15 +125,15 @@ auto rune_index_for_byte(std::string_view text, std::size_t byte_offset) -> int
|
|||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto clamp_preedit_index(int value, std::size_t text_size) -> std::size_t
|
auto clamp_preedit_index(int value, usize text_size) -> usize
|
||||||
{
|
{
|
||||||
if (value < 0)
|
if (value < 0)
|
||||||
return 0;
|
return 0;
|
||||||
auto const as_size = static_cast<std::size_t>(value);
|
auto const as_size { static_cast<usize>(value) };
|
||||||
return std::min(as_size, text_size);
|
return std::min(as_size, text_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto slice_bytes(std::string_view text, std::size_t begin, std::size_t end)
|
auto slice_bytes(std::string_view text, usize begin, usize end)
|
||||||
-> std::string_view
|
-> std::string_view
|
||||||
{
|
{
|
||||||
if (begin > text.size())
|
if (begin > text.size())
|
||||||
@@ -169,17 +169,17 @@ void ImGui::end() { }
|
|||||||
|
|
||||||
void ImGui::set_font(FontHandle font) { m_font = font; }
|
void ImGui::set_font(FontHandle font) { m_font = font; }
|
||||||
|
|
||||||
auto ImGui::focused_text_input() const -> std::optional<std::size_t>
|
auto ImGui::focused_text_input() const -> std::optional<usize>
|
||||||
{
|
{
|
||||||
if (m_focused_id == 0)
|
if (m_focused_id == 0)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
return m_focused_id;
|
return m_focused_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ImGui::text_input_surrounding(std::size_t id,
|
auto ImGui::text_input_surrounding(usize id, std::pmr::string const &str) const
|
||||||
std::pmr::string const &str) const -> std::optional<TextInputSurrounding>
|
-> std::optional<TextInputSurrounding>
|
||||||
{
|
{
|
||||||
auto it = m_ti_states.find(id);
|
auto it { m_ti_states.find(id) };
|
||||||
if (it == m_ti_states.end())
|
if (it == m_ti_states.end())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
TextInputSurrounding info;
|
TextInputSurrounding info;
|
||||||
@@ -190,10 +190,9 @@ auto ImGui::text_input_surrounding(std::size_t id,
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ImGui::text_input_cursor(std::size_t id) const
|
auto ImGui::text_input_cursor(usize id) const -> std::optional<TextInputCursor>
|
||||||
-> std::optional<TextInputCursor>
|
|
||||||
{
|
{
|
||||||
auto it = m_ti_states.find(id);
|
auto it { m_ti_states.find(id) };
|
||||||
if (it == m_ti_states.end())
|
if (it == m_ti_states.end())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
TextInputCursor cursor;
|
TextInputCursor cursor;
|
||||||
@@ -207,11 +206,11 @@ void ImGui::ime_commit_text(std::pmr::string &str, std::string_view text)
|
|||||||
{
|
{
|
||||||
if (m_focused_id == 0)
|
if (m_focused_id == 0)
|
||||||
return;
|
return;
|
||||||
auto it = m_ti_states.find(m_focused_id);
|
auto it { m_ti_states.find(m_focused_id) };
|
||||||
if (it == m_ti_states.end())
|
if (it == m_ti_states.end())
|
||||||
return;
|
return;
|
||||||
auto &state = it->second;
|
auto &state { it->second };
|
||||||
std::size_t insert_pos = std::min(state.caret_byte, str.size());
|
usize insert_pos = std::min(state.caret_byte, str.size());
|
||||||
if (!text.empty())
|
if (!text.empty())
|
||||||
str.insert(insert_pos, text);
|
str.insert(insert_pos, text);
|
||||||
state.caret_byte = insert_pos + text.size();
|
state.caret_byte = insert_pos + text.size();
|
||||||
@@ -223,17 +222,17 @@ void ImGui::ime_commit_text(std::pmr::string &str, std::string_view text)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ImGui::ime_delete_surrounding(
|
void ImGui::ime_delete_surrounding(
|
||||||
std::pmr::string &str, std::size_t before, std::size_t after)
|
std::pmr::string &str, usize before, usize after)
|
||||||
{
|
{
|
||||||
if (m_focused_id == 0)
|
if (m_focused_id == 0)
|
||||||
return;
|
return;
|
||||||
auto it = m_ti_states.find(m_focused_id);
|
auto it { m_ti_states.find(m_focused_id) };
|
||||||
if (it == m_ti_states.end())
|
if (it == m_ti_states.end())
|
||||||
return;
|
return;
|
||||||
auto &state = it->second;
|
auto &state { it->second };
|
||||||
std::size_t caret_byte = std::min(state.caret_byte, str.size());
|
usize caret_byte = std::min(state.caret_byte, str.size());
|
||||||
std::size_t start = before > caret_byte ? 0 : caret_byte - before;
|
usize start = before > caret_byte ? 0 : caret_byte - before;
|
||||||
std::size_t end = std::min(caret_byte + after, str.size());
|
usize end = std::min(caret_byte + after, str.size());
|
||||||
if (end > start) {
|
if (end > start) {
|
||||||
str.erase(start, end - start);
|
str.erase(start, end - start);
|
||||||
state.caret_byte = start;
|
state.caret_byte = start;
|
||||||
@@ -249,19 +248,19 @@ void ImGui::ime_set_preedit(std::string text, int cursor_begin, int cursor_end)
|
|||||||
{
|
{
|
||||||
if (m_focused_id == 0)
|
if (m_focused_id == 0)
|
||||||
return;
|
return;
|
||||||
auto it = m_ti_states.find(m_focused_id);
|
auto it { m_ti_states.find(m_focused_id) };
|
||||||
if (it == m_ti_states.end())
|
if (it == m_ti_states.end())
|
||||||
return;
|
return;
|
||||||
auto &state = it->second;
|
auto &state { it->second };
|
||||||
state.preedit_text = std::move(text);
|
state.preedit_text = std::move(text);
|
||||||
state.preedit_cursor_hidden = (cursor_begin == -1 && cursor_end == -1);
|
state.preedit_cursor_hidden = (cursor_begin == -1 && cursor_end == -1);
|
||||||
std::size_t const size = state.preedit_text.size();
|
usize const size = state.preedit_text.size();
|
||||||
if (state.preedit_cursor_hidden) {
|
if (state.preedit_cursor_hidden) {
|
||||||
state.preedit_cursor_begin = 0;
|
state.preedit_cursor_begin = 0;
|
||||||
state.preedit_cursor_end = 0;
|
state.preedit_cursor_end = 0;
|
||||||
} else {
|
} else {
|
||||||
auto begin_clamped = clamp_preedit_index(cursor_begin, size);
|
auto begin_clamped { clamp_preedit_index(cursor_begin, size) };
|
||||||
auto end_clamped = clamp_preedit_index(cursor_end, size);
|
auto end_clamped { clamp_preedit_index(cursor_end, size) };
|
||||||
state.preedit_cursor_begin = static_cast<int>(begin_clamped);
|
state.preedit_cursor_begin = static_cast<int>(begin_clamped);
|
||||||
state.preedit_cursor_end = static_cast<int>(end_clamped);
|
state.preedit_cursor_end = static_cast<int>(end_clamped);
|
||||||
}
|
}
|
||||||
@@ -277,10 +276,10 @@ void ImGui::ime_clear_preedit()
|
|||||||
{
|
{
|
||||||
if (m_focused_id == 0)
|
if (m_focused_id == 0)
|
||||||
return;
|
return;
|
||||||
auto it = m_ti_states.find(m_focused_id);
|
auto it { m_ti_states.find(m_focused_id) };
|
||||||
if (it == m_ti_states.end())
|
if (it == m_ti_states.end())
|
||||||
return;
|
return;
|
||||||
auto &state = it->second;
|
auto &state { it->second };
|
||||||
state.preedit_text.clear();
|
state.preedit_text.clear();
|
||||||
state.preedit_cursor_begin = 0;
|
state.preedit_cursor_begin = 0;
|
||||||
state.preedit_cursor_end = 0;
|
state.preedit_cursor_end = 0;
|
||||||
@@ -290,7 +289,7 @@ void ImGui::ime_clear_preedit()
|
|||||||
state.caret_timer = 0.0;
|
state.caret_timer = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ImGui::text_input(std::size_t id, std::pmr::string &str, Rectangle rec,
|
auto ImGui::text_input(usize id, std::pmr::string &str, Rectangle rec,
|
||||||
TextInputOptions options) -> std::bitset<2>
|
TextInputOptions options) -> std::bitset<2>
|
||||||
{
|
{
|
||||||
assert(id != 0);
|
assert(id != 0);
|
||||||
@@ -300,7 +299,7 @@ auto ImGui::text_input(std::size_t id, std::pmr::string &str, Rectangle rec,
|
|||||||
bool submitted { false };
|
bool submitted { false };
|
||||||
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.");
|
||||||
|
|
||||||
@@ -316,44 +315,44 @@ auto ImGui::text_input(std::size_t 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 = [&]() -> std::size_t {
|
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[state.current_rune_idx].start;
|
||||||
};
|
} };
|
||||||
|
|
||||||
std::size_t 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 = [&](std::size_t byte_begin, std::size_t 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;
|
||||||
}
|
}
|
||||||
};
|
} };
|
||||||
|
|
||||||
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 = [&]() {
|
auto handle_backspace { [&]() {
|
||||||
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 > static_cast<int>(spans.size()))
|
||||||
return;
|
return;
|
||||||
@@ -362,31 +361,28 @@ auto ImGui::text_input(std::size_t id, std::pmr::string &str, Rectangle rec,
|
|||||||
int idx = state.current_rune_idx;
|
int idx = state.current_rune_idx;
|
||||||
int scan = idx - 1;
|
int scan = idx - 1;
|
||||||
while (scan >= 0
|
while (scan >= 0
|
||||||
&& is_space(
|
&& is_space(spans[static_cast<usize>(scan)].codepoint))
|
||||||
spans[static_cast<std::size_t>(scan)].codepoint))
|
|
||||||
scan--;
|
scan--;
|
||||||
while (scan >= 0
|
while (scan >= 0
|
||||||
&& !is_space(
|
&& !is_space(spans[static_cast<usize>(scan)].codepoint))
|
||||||
spans[static_cast<std::size_t>(scan)].codepoint))
|
|
||||||
scan--;
|
scan--;
|
||||||
int start_idx = std::max(scan + 1, 0);
|
int start_idx = std::max(scan + 1, 0);
|
||||||
std::size_t byte_begin
|
usize byte_begin = spans[static_cast<usize>(start_idx)].start;
|
||||||
= spans[static_cast<std::size_t>(start_idx)].start;
|
usize byte_end = (idx >= static_cast<int>(spans.size()))
|
||||||
std::size_t byte_end = (idx >= static_cast<int>(spans.size()))
|
|
||||||
? str.size()
|
? str.size()
|
||||||
: spans[static_cast<std::size_t>(idx)].start;
|
: spans[static_cast<usize>(idx)].start;
|
||||||
erase_range(byte_begin, byte_end);
|
erase_range(byte_begin, byte_end);
|
||||||
state.current_rune_idx = start_idx;
|
state.current_rune_idx = start_idx;
|
||||||
} else {
|
} else {
|
||||||
auto const &prev = spans[static_cast<std::size_t>(
|
auto const &prev
|
||||||
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 >= static_cast<int>(spans.size())) {
|
||||||
if (!m_ctrl)
|
if (!m_ctrl)
|
||||||
@@ -397,26 +393,24 @@ auto ImGui::text_input(std::size_t id, std::pmr::string &str, Rectangle rec,
|
|||||||
if (m_ctrl) {
|
if (m_ctrl) {
|
||||||
int scan = idx;
|
int scan = idx;
|
||||||
while (scan < static_cast<int>(spans.size())
|
while (scan < static_cast<int>(spans.size())
|
||||||
&& is_space(
|
&& is_space(spans[static_cast<usize>(scan)].codepoint))
|
||||||
spans[static_cast<std::size_t>(scan)].codepoint))
|
|
||||||
scan++;
|
scan++;
|
||||||
while (scan < static_cast<int>(spans.size())
|
while (scan < static_cast<int>(spans.size())
|
||||||
&& !is_space(
|
&& !is_space(spans[static_cast<usize>(scan)].codepoint))
|
||||||
spans[static_cast<std::size_t>(scan)].codepoint))
|
|
||||||
scan++;
|
scan++;
|
||||||
std::size_t byte_begin = (idx < static_cast<int>(spans.size()))
|
usize byte_begin = (idx < static_cast<int>(spans.size()))
|
||||||
? spans[static_cast<std::size_t>(idx)].start
|
? spans[static_cast<usize>(idx)].start
|
||||||
: str.size();
|
: str.size();
|
||||||
std::size_t byte_end = (scan < static_cast<int>(spans.size()))
|
usize byte_end = (scan < static_cast<int>(spans.size()))
|
||||||
? spans[static_cast<std::size_t>(scan)].start
|
? spans[static_cast<usize>(scan)].start
|
||||||
: str.size();
|
: str.size();
|
||||||
erase_range(byte_begin, byte_end);
|
erase_range(byte_begin, byte_end);
|
||||||
} else if (idx < static_cast<int>(spans.size())) {
|
} else if (idx < static_cast<int>(spans.size())) {
|
||||||
auto const &curr = spans[static_cast<std::size_t>(idx)];
|
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;
|
||||||
};
|
} };
|
||||||
|
|
||||||
switch (m_rune) {
|
switch (m_rune) {
|
||||||
case 1: // Left (H)
|
case 1: // Left (H)
|
||||||
@@ -424,13 +418,13 @@ auto ImGui::text_input(std::size_t id, std::pmr::string &str, Rectangle rec,
|
|||||||
state.current_rune_idx--;
|
state.current_rune_idx--;
|
||||||
if (m_ctrl) {
|
if (m_ctrl) {
|
||||||
while (state.current_rune_idx > 0
|
while (state.current_rune_idx > 0
|
||||||
&& is_space(spans[static_cast<std::size_t>(
|
&& is_space(
|
||||||
state.current_rune_idx)]
|
spans[static_cast<usize>(state.current_rune_idx)]
|
||||||
.codepoint))
|
.codepoint))
|
||||||
state.current_rune_idx--;
|
state.current_rune_idx--;
|
||||||
while (state.current_rune_idx > 0
|
while (state.current_rune_idx > 0
|
||||||
&& !is_space(spans[static_cast<std::size_t>(
|
&& !is_space(
|
||||||
state.current_rune_idx)]
|
spans[static_cast<usize>(state.current_rune_idx)]
|
||||||
.codepoint))
|
.codepoint))
|
||||||
state.current_rune_idx--;
|
state.current_rune_idx--;
|
||||||
}
|
}
|
||||||
@@ -443,13 +437,13 @@ auto ImGui::text_input(std::size_t id, std::pmr::string &str, Rectangle rec,
|
|||||||
if (m_ctrl) {
|
if (m_ctrl) {
|
||||||
while (
|
while (
|
||||||
state.current_rune_idx < static_cast<int>(spans.size())
|
state.current_rune_idx < static_cast<int>(spans.size())
|
||||||
&& is_space(spans[static_cast<std::size_t>(
|
&& is_space(spans[static_cast<usize>(
|
||||||
state.current_rune_idx - 1)]
|
state.current_rune_idx - 1)]
|
||||||
.codepoint))
|
.codepoint))
|
||||||
state.current_rune_idx++;
|
state.current_rune_idx++;
|
||||||
while (
|
while (
|
||||||
state.current_rune_idx < static_cast<int>(spans.size())
|
state.current_rune_idx < static_cast<int>(spans.size())
|
||||||
&& !is_space(spans[static_cast<std::size_t>(
|
&& !is_space(spans[static_cast<usize>(
|
||||||
state.current_rune_idx - 1)]
|
state.current_rune_idx - 1)]
|
||||||
.codepoint))
|
.codepoint))
|
||||||
state.current_rune_idx++;
|
state.current_rune_idx++;
|
||||||
@@ -474,7 +468,7 @@ auto ImGui::text_input(std::size_t id, std::pmr::string &str, Rectangle rec,
|
|||||||
case '\r':
|
case '\r':
|
||||||
case '\n':
|
case '\n':
|
||||||
if (options.multiline) {
|
if (options.multiline) {
|
||||||
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);
|
||||||
state.current_rune_idx++;
|
state.current_rune_idx++;
|
||||||
@@ -487,7 +481,7 @@ auto ImGui::text_input(std::size_t id, std::pmr::string &str, Rectangle rec,
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (m_rune >= 0x20) {
|
if (m_rune >= 0x20) {
|
||||||
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);
|
||||||
state.current_rune_idx++;
|
state.current_rune_idx++;
|
||||||
@@ -556,13 +550,15 @@ auto ImGui::text_input(std::size_t id, std::pmr::string &str, Rectangle rec,
|
|||||||
preedit_metrics
|
preedit_metrics
|
||||||
= m_text_renderer->measure_text(*m_font, preedit_view, font_px);
|
= m_text_renderer->measure_text(*m_font, preedit_view, font_px);
|
||||||
|
|
||||||
auto caret_idx = clamp_preedit_index(
|
auto caret_idx { clamp_preedit_index(
|
||||||
state.preedit_cursor_end, preedit_view.size());
|
state.preedit_cursor_end, preedit_view.size()) };
|
||||||
caret_preedit_metrics = m_text_renderer->measure_text(
|
caret_preedit_metrics = m_text_renderer->measure_text(
|
||||||
*m_font, slice_bytes(preedit_view, 0, caret_idx), font_px);
|
*m_font, slice_bytes(preedit_view, 0, caret_idx), font_px)
|
||||||
|
|
||||||
if (!state.preedit_cursor_hidden
|
if (!state.preedit_cursor_hidden
|
||||||
&& state.preedit_cursor_begin != state.preedit_cursor_end) {
|
&& state.preedit_cursor_begin
|
||||||
|
!= state.preedit_cursor_end)
|
||||||
|
{
|
||||||
auto sel_begin
|
auto sel_begin
|
||||||
= clamp_preedit_index(std::min(state.preedit_cursor_begin,
|
= clamp_preedit_index(std::min(state.preedit_cursor_begin,
|
||||||
state.preedit_cursor_end),
|
state.preedit_cursor_end),
|
||||||
@@ -717,3 +713,26 @@ auto ImGui::text_input(std::size_t id, std::pmr::string &str, Rectangle rec,
|
|||||||
return std::bitset<2> { static_cast<unsigned long long>(
|
return std::bitset<2> { static_cast<unsigned long long>(
|
||||||
(submitted ? 1 : 0) | (changed ? 2 : 0)) };
|
(submitted ? 1 : 0) | (changed ? 2 : 0)) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto ImGui::list_view(usize id, Rectangle bounds, usize elements,
|
||||||
|
std::function<Vector2(usize i)> draw_cb, ListViewOptions options) -> bool
|
||||||
|
{
|
||||||
|
auto &state { m_lv_states[id] };
|
||||||
|
|
||||||
|
bool submitted { false };
|
||||||
|
|
||||||
|
bool select_next = m_next_lv_next;
|
||||||
|
m_next_lv_next = false;
|
||||||
|
bool select_previous = m_next_lv_previous;
|
||||||
|
m_next_lv_previous = false;
|
||||||
|
bool select_clear = m_next_lv_clear;
|
||||||
|
m_next_lv_clear = false;
|
||||||
|
|
||||||
|
BeginScissorMode(bounds.x, bounds.y, bounds.width, bounds.height);
|
||||||
|
|
||||||
|
EndScissorMode();
|
||||||
|
|
||||||
|
m_prev_lv_selected_item = state.selected_item;
|
||||||
|
|
||||||
|
return submitted;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
@@ -17,6 +18,10 @@ struct TextInputOptions {
|
|||||||
bool multiline { false };
|
bool multiline { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ListViewOptions {
|
||||||
|
bool selectable { false };
|
||||||
|
};
|
||||||
|
|
||||||
struct ImGui {
|
struct ImGui {
|
||||||
struct Style {
|
struct Style {
|
||||||
float font_size { DEFAULT_FONT_SIZE };
|
float font_size { DEFAULT_FONT_SIZE };
|
||||||
@@ -38,14 +43,14 @@ struct ImGui {
|
|||||||
|
|
||||||
// Bit 0 -> Submitted
|
// Bit 0 -> Submitted
|
||||||
// Bit 1 -> String changed
|
// Bit 1 -> String changed
|
||||||
auto text_input(std::size_t id, std::pmr::string &str, Rectangle rec,
|
auto text_input(usize id, std::pmr::string &str, Rectangle rec,
|
||||||
TextInputOptions options = {}) -> std::bitset<2>;
|
TextInputOptions options = {}) -> std::bitset<2>;
|
||||||
|
|
||||||
struct TextInputSurrounding {
|
struct TextInputSurrounding {
|
||||||
std::string text;
|
std::string text;
|
||||||
int cursor { 0 };
|
int cursor { 0 };
|
||||||
int anchor { 0 };
|
int anchor { 0 };
|
||||||
std::size_t caret_byte { 0 };
|
usize caret_byte { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TextInputCursor {
|
struct TextInputCursor {
|
||||||
@@ -53,18 +58,24 @@ struct ImGui {
|
|||||||
bool visible { true };
|
bool visible { true };
|
||||||
};
|
};
|
||||||
|
|
||||||
auto focused_text_input() const -> std::optional<std::size_t>;
|
auto focused_text_input() const -> std::optional<usize>;
|
||||||
auto text_input_surrounding(
|
auto text_input_surrounding(usize id, std::pmr::string const &str) const
|
||||||
std::size_t id, std::pmr::string const &str) const
|
|
||||||
-> std::optional<TextInputSurrounding>;
|
-> std::optional<TextInputSurrounding>;
|
||||||
auto text_input_cursor(std::size_t id) const
|
auto text_input_cursor(usize id) const -> std::optional<TextInputCursor>;
|
||||||
-> std::optional<TextInputCursor>;
|
|
||||||
void ime_commit_text(std::pmr::string &str, std::string_view text);
|
void ime_commit_text(std::pmr::string &str, std::string_view text);
|
||||||
void ime_delete_surrounding(
|
void ime_delete_surrounding(
|
||||||
std::pmr::string &str, std::size_t before, std::size_t after);
|
std::pmr::string &str, usize before, usize after);
|
||||||
void ime_set_preedit(std::string text, int cursor_begin, int cursor_end);
|
void ime_set_preedit(std::string text, int cursor_begin, int cursor_end);
|
||||||
void ime_clear_preedit();
|
void ime_clear_preedit();
|
||||||
|
|
||||||
|
auto list_view(usize id, Rectangle bounds, usize elements,
|
||||||
|
std::function<Vector2(usize i)> draw_cb, ListViewOptions options = {})
|
||||||
|
-> bool;
|
||||||
|
auto prev_list_view_selected_item() const -> std::optional<usize>
|
||||||
|
{
|
||||||
|
return m_prev_lv_selected_item;
|
||||||
|
}
|
||||||
|
|
||||||
void set_font(FontHandle font);
|
void set_font(FontHandle font);
|
||||||
|
|
||||||
auto style() -> Style & { return m_styles.back(); }
|
auto style() -> Style & { return m_styles.back(); }
|
||||||
@@ -75,7 +86,7 @@ struct ImGui {
|
|||||||
}
|
}
|
||||||
auto push_style() -> Style & { return push_style(style()); }
|
auto push_style() -> Style & { return push_style(style()); }
|
||||||
|
|
||||||
[[nodiscard]] inline auto id(std::string_view const str) -> std::size_t
|
[[nodiscard]] inline auto id(std::string_view const str) -> usize
|
||||||
{
|
{
|
||||||
std::hash<std::string_view> hasher;
|
std::hash<std::string_view> hasher;
|
||||||
return hasher(str);
|
return hasher(str);
|
||||||
@@ -84,8 +95,11 @@ struct ImGui {
|
|||||||
private:
|
private:
|
||||||
struct TextInputState {
|
struct TextInputState {
|
||||||
int current_rune_idx { 0 };
|
int current_rune_idx { 0 };
|
||||||
Vector2 scroll_offset; // y not used if multiline == false
|
|
||||||
Vector2 cursor_position; // y not used if multiline == false
|
// y not used if multiline == false
|
||||||
|
Vector2 scroll_offset { 0, 0 };
|
||||||
|
Vector2 cursor_position { 0, 0 };
|
||||||
|
|
||||||
bool caret_visible { true };
|
bool caret_visible { true };
|
||||||
double caret_timer { 0.0 };
|
double caret_timer { 0.0 };
|
||||||
std::string preedit_text;
|
std::string preedit_text;
|
||||||
@@ -93,13 +107,23 @@ private:
|
|||||||
int preedit_cursor_end { 0 };
|
int preedit_cursor_end { 0 };
|
||||||
bool preedit_active { false };
|
bool preedit_active { false };
|
||||||
bool preedit_cursor_hidden { false };
|
bool preedit_cursor_hidden { false };
|
||||||
std::size_t caret_byte { 0 };
|
usize caret_byte { 0 };
|
||||||
Rectangle caret_rect {};
|
Rectangle caret_rect {};
|
||||||
bool external_change { false };
|
bool external_change { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unordered_map<std::size_t, TextInputState> m_ti_states;
|
struct ListViewState {
|
||||||
std::size_t m_focused_id {};
|
float scroll_offset_y { 0 };
|
||||||
|
std::optional<usize> selected_item { std::nullopt };
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<usize, ListViewState> m_lv_states;
|
||||||
|
std::unordered_map<usize, TextInputState> m_ti_states;
|
||||||
|
bool m_next_lv_next { false };
|
||||||
|
bool m_next_lv_previous { false };
|
||||||
|
bool m_next_lv_clear { false };
|
||||||
|
std::optional<usize> m_prev_lv_selected_item { std::nullopt };
|
||||||
|
usize m_focused_id {};
|
||||||
u32 m_rune {}; // 1234 <-> hjkl arrow keys
|
u32 m_rune {}; // 1234 <-> hjkl arrow keys
|
||||||
bool m_ctrl {};
|
bool m_ctrl {};
|
||||||
bool m_shift {};
|
bool m_shift {};
|
||||||
|
|||||||
@@ -67,18 +67,18 @@ auto ft_library() -> FT_Library
|
|||||||
|
|
||||||
struct CodepointSpan {
|
struct CodepointSpan {
|
||||||
uint32_t codepoint {};
|
uint32_t codepoint {};
|
||||||
size_t start {};
|
usize start {};
|
||||||
size_t end {};
|
usize end {};
|
||||||
};
|
};
|
||||||
|
|
||||||
auto decode_utf8(std::string_view text) -> std::vector<CodepointSpan>
|
auto decode_utf8(std::string_view text) -> std::vector<CodepointSpan>
|
||||||
{
|
{
|
||||||
std::vector<CodepointSpan> spans;
|
std::vector<CodepointSpan> spans;
|
||||||
size_t i = 0;
|
usize i = 0;
|
||||||
while (i < text.size()) {
|
while (i < text.size()) {
|
||||||
u8 const byte = static_cast<u8>(text[i]);
|
u8 const byte = static_cast<u8>(text[i]);
|
||||||
size_t const start = i;
|
usize const start = i;
|
||||||
size_t length = 1;
|
usize length = 1;
|
||||||
uint32_t cp = 0xFFFD;
|
uint32_t cp = 0xFFFD;
|
||||||
if (byte < 0x80) {
|
if (byte < 0x80) {
|
||||||
cp = byte;
|
cp = byte;
|
||||||
@@ -239,7 +239,7 @@ auto TextRenderer::generate_glyph(FontRuntime &rt, FontData &fd,
|
|||||||
msdfgen::generateMSDF(
|
msdfgen::generateMSDF(
|
||||||
msdf_bitmap, shape, rt.px_range, scale_vec, translate);
|
msdf_bitmap, shape, rt.px_range, scale_vec, translate);
|
||||||
|
|
||||||
std::vector<Color> buffer(static_cast<size_t>(bmp_w) * bmp_h);
|
std::vector<Color> buffer(static_cast<usize>(bmp_w) * bmp_h);
|
||||||
// FIXME: Figure out shader
|
// FIXME: Figure out shader
|
||||||
// for (int y = 0; y < bmp_h; ++y) {
|
// for (int y = 0; y < bmp_h; ++y) {
|
||||||
// int const dst_y = bmp_h - 1 - y;
|
// int const dst_y = bmp_h - 1 - y;
|
||||||
@@ -248,7 +248,7 @@ auto TextRenderer::generate_glyph(FontRuntime &rt, FontData &fd,
|
|||||||
// auto const r = msdfgen::pixelFloatToByte(px[0]);
|
// auto const r = msdfgen::pixelFloatToByte(px[0]);
|
||||||
// auto const g = msdfgen::pixelFloatToByte(px[1]);
|
// auto const g = msdfgen::pixelFloatToByte(px[1]);
|
||||||
// auto const b = msdfgen::pixelFloatToByte(px[2]);
|
// auto const b = msdfgen::pixelFloatToByte(px[2]);
|
||||||
// buffer[static_cast<size_t>(dst_y) * bmp_w + x]
|
// buffer[static_cast<usize>(dst_y) * bmp_w + x]
|
||||||
// = Color { r, g, b, 255 };
|
// = Color { r, g, b, 255 };
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
@@ -274,10 +274,10 @@ auto TextRenderer::generate_glyph(FontRuntime &rt, FontData &fd,
|
|||||||
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 (sum_white > sum_black && (float)bmp_w / (float)bmp_h > 0.6) {
|
||||||
buffer[static_cast<size_t>(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 {
|
||||||
buffer[static_cast<size_t>(dst_y) * bmp_w + x]
|
buffer[static_cast<usize>(dst_y) * bmp_w + x]
|
||||||
= Color { 255, 255, 255, r };
|
= Color { 255, 255, 255, r };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -338,11 +338,11 @@ auto TextRenderer::ensure_glyph(FontRuntime &rt, FontData &fd, u32 glyph_index,
|
|||||||
TextRenderer::TextRenderer()
|
TextRenderer::TextRenderer()
|
||||||
{
|
{
|
||||||
static char const msdf_vs_data[] {
|
static char const msdf_vs_data[] {
|
||||||
#embed "base.vs"
|
#embed "base.vert"
|
||||||
, 0
|
, 0
|
||||||
};
|
};
|
||||||
static char const msdf_fs_data[] {
|
static char const msdf_fs_data[] {
|
||||||
#embed "msdf.fs"
|
#embed "msdf.frag"
|
||||||
, 0
|
, 0
|
||||||
};
|
};
|
||||||
m_msdf_shader = LoadShaderFromMemory(msdf_vs_data, msdf_fs_data);
|
m_msdf_shader = LoadShaderFromMemory(msdf_vs_data, msdf_fs_data);
|
||||||
@@ -661,9 +661,9 @@ auto TextRenderer::shape_text(FontHandle const font,
|
|||||||
|
|
||||||
constexpr usize kNoFont = std::numeric_limits<usize>::max();
|
constexpr usize kNoFont = std::numeric_limits<usize>::max();
|
||||||
std::vector<usize> selections(codepoints.size(), kNoFont);
|
std::vector<usize> selections(codepoints.size(), kNoFont);
|
||||||
for (size_t i = 0; i < codepoints.size(); ++i) {
|
for (usize i = 0; i < codepoints.size(); ++i) {
|
||||||
bool matched = false;
|
bool matched = false;
|
||||||
for (size_t candidate = 0; candidate < font_set.font_indices.size();
|
for (usize candidate = 0; candidate < font_set.font_indices.size();
|
||||||
++candidate) {
|
++candidate) {
|
||||||
usize runtime_index = font_set.font_indices[candidate];
|
usize runtime_index = font_set.font_indices[candidate];
|
||||||
if (runtime_index >= m_font_runtime.size())
|
if (runtime_index >= m_font_runtime.size())
|
||||||
@@ -683,9 +683,9 @@ auto TextRenderer::shape_text(FontHandle const font,
|
|||||||
selections[i] = kNoFont;
|
selections[i] = kNoFont;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t idx = 0;
|
usize idx = 0;
|
||||||
while (idx < codepoints.size()) {
|
while (idx < codepoints.size()) {
|
||||||
size_t font_choice = selections[idx];
|
usize font_choice = selections[idx];
|
||||||
if (font_choice == kNoFont) {
|
if (font_choice == kNoFont) {
|
||||||
++idx;
|
++idx;
|
||||||
continue;
|
continue;
|
||||||
@@ -700,9 +700,9 @@ auto TextRenderer::shape_text(FontHandle const font,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t segment_start = codepoints[idx].start;
|
usize segment_start = codepoints[idx].start;
|
||||||
size_t segment_end = codepoints[idx].end;
|
usize segment_end = codepoints[idx].end;
|
||||||
size_t end_idx = idx + 1;
|
usize end_idx = idx + 1;
|
||||||
while (
|
while (
|
||||||
end_idx < codepoints.size() && selections[end_idx] == font_choice) {
|
end_idx < codepoints.size() && selections[end_idx] == font_choice) {
|
||||||
segment_end = codepoints[end_idx].end;
|
segment_end = codepoints[end_idx].end;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
auto App::tick() -> void
|
auto App::tick() -> void
|
||||||
{
|
{
|
||||||
static std::pmr::string text_input_data;
|
static std::pmr::string text_input_data {};
|
||||||
m_ime.bound_text = &text_input_data;
|
m_ime.bound_text = &text_input_data;
|
||||||
m_ime.bound_id = 1;
|
m_ime.bound_id = 1;
|
||||||
process_pending_text_input();
|
process_pending_text_input();
|
||||||
|
|||||||
@@ -11,22 +11,22 @@ template<class E>
|
|||||||
concept EnumLike = std::is_enum_v<E>;
|
concept EnumLike = std::is_enum_v<E>;
|
||||||
|
|
||||||
template<EnumLike E>
|
template<EnumLike E>
|
||||||
constexpr std::size_t enum_count_v
|
constexpr usize enum_count_v
|
||||||
= static_cast<std::size_t>(enum_traits<E>::last)
|
= static_cast<usize>(enum_traits<E>::last)
|
||||||
- static_cast<std::size_t>(enum_traits<E>::first) + 1;
|
- static_cast<usize>(enum_traits<E>::first) + 1;
|
||||||
|
|
||||||
template<EnumLike E, class T> struct enum_array {
|
template<EnumLike E, class T> struct enum_array {
|
||||||
using value_type = T;
|
using value_type = T;
|
||||||
using enum_type = E;
|
using enum_type = E;
|
||||||
using underlying_index_type = std::size_t;
|
using underlying_index_type = usize;
|
||||||
|
|
||||||
static constexpr E first = enum_traits<E>::first;
|
static constexpr E first = enum_traits<E>::first;
|
||||||
static constexpr E last = enum_traits<E>::last;
|
static constexpr E last = enum_traits<E>::last;
|
||||||
static constexpr std::size_t size_value = enum_count_v<E>;
|
static constexpr usize size_value = enum_count_v<E>;
|
||||||
|
|
||||||
std::array<T, size_value> _data {};
|
std::array<T, size_value> _data {};
|
||||||
|
|
||||||
static constexpr std::size_t size() noexcept { return size_value; }
|
static constexpr usize size() noexcept { return size_value; }
|
||||||
constexpr T *data() noexcept { return _data.data(); }
|
constexpr T *data() noexcept { return _data.data(); }
|
||||||
constexpr T const *data() const noexcept { return _data.data(); }
|
constexpr T const *data() const noexcept { return _data.data(); }
|
||||||
constexpr T *begin() noexcept { return _data.begin().operator->(); }
|
constexpr T *begin() noexcept { return _data.begin().operator->(); }
|
||||||
@@ -61,9 +61,9 @@ template<EnumLike E, class T> struct enum_array {
|
|||||||
constexpr void fill(T const &v) { _data.fill(v); }
|
constexpr void fill(T const &v) { _data.fill(v); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr std::size_t to_index(E e) noexcept
|
static constexpr usize to_index(E e) noexcept
|
||||||
{
|
{
|
||||||
return static_cast<std::size_t>(e) - static_cast<std::size_t>(first);
|
return static_cast<usize>(e) - static_cast<usize>(first);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user