summaryrefslogtreecommitdiff
path: root/src/entry_backend/pango.c
diff options
context:
space:
mode:
authorPhil Jones <philj56@gmail.com>2022-12-20 23:53:20 +0000
committerPhil Jones <philj56@gmail.com>2022-12-21 00:15:16 +0000
commit6c47cf7892d0f212b04e7b798e53c120f51022d7 (patch)
treec44b910e059d5bdcf991b2239de8d29cb007bed6 /src/entry_backend/pango.c
parent108550fcf8d3ed8664c0e05defceaf20b4d2b49e (diff)
Add text cursor support.
This turned out to be much more complex than anticipated, and the potential for bugs is therefore quite high.
Diffstat (limited to 'src/entry_backend/pango.c')
-rw-r--r--src/entry_backend/pango.c185
1 files changed, 179 insertions, 6 deletions
diff --git a/src/entry_backend/pango.c b/src/entry_backend/pango.c
index 82c93ee..d274950 100644
--- a/src/entry_backend/pango.c
+++ b/src/entry_backend/pango.c
@@ -77,6 +77,127 @@ static void render_text_themed(
pango_cairo_show_layout(cr, layout);
}
+static void render_input(
+ cairo_t *cr,
+ PangoLayout *layout,
+ const char *text,
+ uint32_t text_length,
+ const struct text_theme *theme,
+ uint32_t cursor_position,
+ const struct cursor_theme *cursor_theme,
+ PangoRectangle *ink_rect,
+ PangoRectangle *logical_rect)
+{
+ struct directional padding = theme->padding;
+ struct color color = theme->foreground_color;
+ cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a);
+
+ pango_layout_set_text(layout, text, -1);
+ pango_cairo_update_layout(cr, layout);
+ pango_cairo_show_layout(cr, layout);
+
+ pango_layout_get_pixel_extents(layout, ink_rect, logical_rect);
+
+ double extra_cursor_advance = 0;
+ if (cursor_position == text_length && cursor_theme->show) {
+ switch (cursor_theme->style) {
+ case CURSOR_STYLE_BAR:
+ extra_cursor_advance = cursor_theme->thickness;
+ break;
+ case CURSOR_STYLE_BLOCK:
+ extra_cursor_advance = cursor_theme->em_width;
+ break;
+ case CURSOR_STYLE_UNDERSCORE:
+ extra_cursor_advance = cursor_theme->em_width;
+ break;
+ }
+ extra_cursor_advance += logical_rect->width
+ - logical_rect->x
+ - ink_rect->width;
+ }
+
+ if (theme->background_color.a != 0) {
+ cairo_save(cr);
+ color = theme->background_color;
+ cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a);
+ cairo_translate(
+ cr,
+ floor(-padding.left + ink_rect->x),
+ -padding.top);
+ rounded_rectangle(
+ cr,
+ ceil(ink_rect->width + extra_cursor_advance + padding.left + padding.right),
+ ceil(logical_rect->height + padding.top + padding.bottom),
+ theme->background_corner_radius
+ );
+ cairo_fill(cr);
+ cairo_restore(cr);
+
+ color = theme->foreground_color;
+ cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a);
+
+ pango_cairo_show_layout(cr, layout);
+ }
+
+ if (!cursor_theme->show) {
+ /* No cursor to draw, we're done. */
+ return;
+ }
+
+ double cursor_x;
+ double cursor_width;
+
+ if (cursor_position == text_length) {
+ cursor_x = logical_rect->width + logical_rect->x;
+ cursor_width = cursor_theme->em_width;
+ } else {
+ /*
+ * Pango wants a byte index rather than a character index for
+ * the cursor position, so we have to calculate that here.
+ */
+ const char *tmp = text;
+ for (size_t i = 0; i < cursor_position; i++) {
+ tmp = utf8_next_char(tmp);
+ }
+ uint32_t start_byte_index = tmp - text;
+ uint32_t end_byte_index = utf8_next_char(tmp) - text;
+ PangoRectangle start_pos;
+ PangoRectangle end_pos;
+ pango_layout_get_cursor_pos(layout, start_byte_index, &start_pos, NULL);
+ pango_layout_get_cursor_pos(layout, end_byte_index, &end_pos, NULL);
+ cursor_x = (double)start_pos.x / PANGO_SCALE;
+ cursor_width = (double)(end_pos.x - start_pos.x) / PANGO_SCALE;;
+ }
+
+ cairo_save(cr);
+ color = cursor_theme->color;
+ cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a);
+ cairo_translate(cr, cursor_x, 0);
+ switch (cursor_theme->style) {
+ case CURSOR_STYLE_BAR:
+ rounded_rectangle(cr, cursor_theme->thickness, logical_rect->height, cursor_theme->corner_radius);
+ cairo_fill(cr);
+ break;
+ case CURSOR_STYLE_BLOCK:
+ rounded_rectangle(cr, cursor_width, logical_rect->height, cursor_theme->corner_radius);
+ cairo_fill_preserve(cr);
+ cairo_clip(cr);
+ cairo_translate(cr, -cursor_x, 0);
+ color = cursor_theme->text_color;
+ cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a);
+ pango_cairo_show_layout(cr, layout);
+ break;
+ case CURSOR_STYLE_UNDERSCORE:
+ cairo_translate(cr, 0, cursor_theme->underline_depth);
+ rounded_rectangle(cr, cursor_width, cursor_theme->thickness, cursor_theme->corner_radius);
+ cairo_fill(cr);
+ break;
+ }
+
+ logical_rect->width += extra_cursor_advance;
+ cairo_restore(cr);
+}
+
void entry_backend_pango_init(struct entry *entry, uint32_t *width, uint32_t *height)
{
cairo_t *cr = entry->cairo[0].cr;
@@ -97,7 +218,6 @@ void entry_backend_pango_init(struct entry *entry, uint32_t *width, uint32_t *he
entry->font_variations);
}
pango_context_set_font_description(context, font_description);
- pango_font_description_free(font_description);
entry->pango.layout = pango_layout_new(context);
@@ -109,6 +229,33 @@ void entry_backend_pango_init(struct entry *entry, uint32_t *width, uint32_t *he
pango_layout_set_attributes(entry->pango.layout, attr_list);
}
+ log_debug("Loading Pango font.\n");
+ PangoFontMap *map = pango_cairo_font_map_get_default();
+ PangoFont *font = pango_font_map_load_font(map, context, font_description);
+ PangoFontMetrics *metrics = pango_font_get_metrics(font, NULL);
+ hb_font_t *hb_font = pango_font_get_hb_font(font);
+
+ uint32_t m_codepoint;
+ if (hb_font_get_glyph_from_name(hb_font, "m", -1, &m_codepoint)) {
+ entry->cursor_theme.em_width = (double)hb_font_get_glyph_h_advance(hb_font, m_codepoint) / PANGO_SCALE;
+ } else {
+ entry->cursor_theme.em_width = (double)pango_font_metrics_get_approximate_char_width(metrics) / PANGO_SCALE;
+ }
+
+ entry->cursor_theme.underline_depth = (double)
+ (
+ pango_font_metrics_get_ascent(metrics)
+ - pango_font_metrics_get_underline_position(metrics)
+ ) / PANGO_SCALE;
+ if (entry->cursor_theme.style == CURSOR_STYLE_UNDERSCORE && !entry->cursor_theme.thickness_specified) {
+ entry->cursor_theme.thickness = pango_font_metrics_get_underline_thickness(metrics) / PANGO_SCALE;
+ }
+
+ pango_font_metrics_unref(metrics);
+ g_object_unref(font);
+ log_debug("Loaded.\n");
+
+ pango_font_description_free(font_description);
entry->pango.context = context;
}
@@ -148,7 +295,6 @@ void entry_backend_pango_update(struct entry *entry)
PangoLayout *layout = entry->pango.layout;
cairo_save(cr);
- struct color color = entry->foreground_color;
/* Render the prompt */
PangoRectangle ink_rect;
@@ -160,7 +306,16 @@ void entry_backend_pango_update(struct entry *entry)
/* Render the entry text */
if (entry->input_utf8_length == 0) {
- render_text_themed(cr, layout, entry->placeholder_text, &entry->placeholder_theme, &ink_rect, &logical_rect);
+ render_input(
+ cr,
+ layout,
+ entry->placeholder_text,
+ utf8_strlen(entry->placeholder_text),
+ &entry->placeholder_theme,
+ 0,
+ &entry->cursor_theme,
+ &ink_rect,
+ &logical_rect);
} else if (entry->hide_input) {
size_t nchars = entry->input_utf32_length;
size_t char_size = entry->hidden_character_utf8_length;
@@ -172,14 +327,32 @@ void entry_backend_pango_update(struct entry *entry)
}
buf[char_size * nchars] = '\0';
- render_text_themed(cr, layout, buf, &entry->placeholder_theme, &ink_rect, &logical_rect);
+ render_input(
+ cr,
+ layout,
+ buf,
+ entry->input_utf32_length,
+ &entry->input_theme,
+ entry->cursor_position,
+ &entry->cursor_theme,
+ &ink_rect,
+ &logical_rect);
free(buf);
} else {
- render_text_themed(cr, layout, entry->input_utf8, &entry->input_theme, &ink_rect, &logical_rect);
+ render_input(
+ cr,
+ layout,
+ entry->input_utf8,
+ entry->input_utf32_length,
+ &entry->input_theme,
+ entry->cursor_position,
+ &entry->cursor_theme,
+ &ink_rect,
+ &logical_rect);
}
logical_rect.width = MAX(logical_rect.width, (int)entry->input_width);
- color = entry->foreground_color;
+ struct color color = entry->foreground_color;
cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a);
uint32_t num_results;