diff options
-rw-r--r-- | README.md | 14 | ||||
-rw-r--r-- | completions/tofi | 22 | ||||
-rw-r--r-- | doc/config | 81 | ||||
-rw-r--r-- | doc/tofi.5.md | 163 | ||||
-rw-r--r-- | doc/tofi.5.scd | 129 | ||||
-rw-r--r-- | src/config.c | 148 | ||||
-rw-r--r-- | src/entry.c | 57 | ||||
-rw-r--r-- | src/entry.h | 29 | ||||
-rw-r--r-- | src/entry_backend/harfbuzz.c | 298 | ||||
-rw-r--r-- | src/entry_backend/pango.c | 231 | ||||
-rw-r--r-- | src/main.c | 27 |
11 files changed, 977 insertions, 222 deletions
@@ -91,10 +91,12 @@ See the main [manpage](doc/tofi.1.md) for more info. ### Theming -Tofi supports a fair number of theming options - see the config file -[manpage](doc/tofi.5.md) for a complete description. Theming is based on the -box model shown below: +Tofi supports a fair number of theming options - see the default [config +file](doc/config) config file or the config file [manpage](doc/tofi.5.md) for a +complete description. Theming is based on the box model shown below: +  + This consists of a box with a border, border outlines and optionally rounded corners. Text inside the box can either be laid out vertically: ``` @@ -111,6 +113,8 @@ or horizontally: ║ prompt input result 1 result 2 ... ║ ╚═══════════════════════════════════════════╝ ``` +Each piece of text can have its colour customised, and be surrounded by a box +with optionally rounded corners, A few example themes are included and shown below. Note that you may need to tweak them to look correct on your display. @@ -165,8 +169,8 @@ In roughly descending order, the most important options for performance are: results will speed this up, but since this likely only applies to dmenu-like themes (which are already very quick) it's probably not worth setting this. -* `--selection-match-color`, `--selection-background` - Passing either of these - options causes some more complex rendering to take place, again leading to a +* `--*-background` - Drawing background boxes around text effectively requires + drawing the text twice, so specifying a lot of these options can lead to a couple of ms slowdown. * `--hint-font` - Getting really into it now, one of the remaining slow points diff --git a/completions/tofi b/completions/tofi index 0e50af2..999e83d 100644 --- a/completions/tofi +++ b/completions/tofi @@ -21,14 +21,34 @@ _tofi() --num-results --selection-color --selection-match-color - --selection-padding --selection-background + --selection-background-padding + --selection-background-corner-radius --outline-width --outline-color --prompt-text --prompt-padding + --prompt-color + --prompt-background + --prompt-background-padding + --prompt-background-corner-radius --placeholder-text --placeholder-color + --placeholder-background + --placeholder-background-padding + --placeholder-background-corner-radius + --input-color + --input-background + --input-background-padding + --input-background-corner-radius + --default-result-color + --default-result-background + --default-result-background-padding + --default-result-background-corner-radius + --alternate-result-color + --alternate-result-background + --alternate-result-background-padding + --alternate-result-background-corner-radius --result-spacing --min-input-width --border-width @@ -45,32 +45,71 @@ hint-font = true # -### Colors +### Text theming # - # Window background - background-color = #1B1D1E - - # Border outlines - outline-color = #080800 + # Default text color + # + # All text defaults to this color if not otherwise specified. + text-color = #FFFFFF - # Border - border-color = #F92672 + # All pieces of text have the same theming attributes available: + # + # *-color + # Foreground color + # + # *-background + # Background color + # + # *-background-padding + # Background padding in pixels (comma-delimited, CSS-style list). + # See "DIRECTIONAL VALUES" under `man 5 tofi` for more info. + # + # *-background-corner-radius + # Radius of background box corners in pixels - # Default text - text-color = #FFFFFF + # Prompt text theme + # prompt-color = #FFFFFF + prompt-background = #00000000 + prompt-background-padding = 0 + prompt-background-corner-radius = 0 - # Placeholder input text + # Placeholder text theme placeholder-color = #FFFFFFA8 + placeholder-background = #00000000 + placeholder-background-padding = 0 + placeholder-background-corner-radius = 0 + + # Input text theme + # input-color = #FFFFFF + input-background = #00000000 + input-background-padding = 0 + input-background-corner-radius = 0 + + # Default result text theme + # default-result-color = #FFFFFF + default-result-background = #00000000 + default-result-background-padding = 0 + default-result-background-corner-radius = 0 + + # Alternate (even-numbered) result text theme + # + # If unspecified, these all default to the corresponding + # default-result-* attribute. + # + # alternate-result-color = #FFFFFF + # alternate-result-background = #00000000 + # alternate-result-background-padding = 0 + # alternate-result-background-corner-radius = 0 # Selection text selection-color = #F92672 + selection-background = #00000000 + selection-background-padding = 0 + selection-background-corner-radius = 0 # Matching portion of selection text selection-match-color = #00000000 - # Selection background - selection-background = #00000000 - # ### Text layout # @@ -96,22 +135,28 @@ # Minimum width of input in horizontal mode. min-input-width = 0 - # Extra horizontal padding of the selection background in pixels. - selection-padding = 0 - # -### Window layout +### Window theming # # Width and height of the window. Can be pixels or a percentage. width = 1280 height = 720 + # Window background color + background-color = #1B1D1E + # Width of the border outlines in pixels. outline-width = 4 + # Border outline color + outline-color = #080800 + # Width of the border in pixels. border-width = 12 + # Border color + border-color = #F92672 + # Radius of window corners in pixels. corner-radius = 0 diff --git a/doc/tofi.5.md b/doc/tofi.5.md index e5f947b..d87e232 100644 --- a/doc/tofi.5.md +++ b/doc/tofi.5.md @@ -222,6 +222,31 @@ options. > > Default: 0 +**prompt-color**=*color* + +> Color of prompt text. See **COLORS** for more information. +> +> Default: Same as **text-color** + +**prompt-background**=*color* + +> Background color of prompt. See **COLORS** for more information. +> +> Default: \#00000000 + +**prompt-background-padding**=*directional* + +> Extra padding of the prompt background. See **DIRECTIONAL VALUES** for +> more information. +> +> Default: 0 + +**prompt-background-corner-radius**=*px* + +> Corner radius of the prompt background. +> +> Default: 0 + **placeholder-text**=*string* > Placeholder input text. @@ -234,6 +259,104 @@ options. > > Default: \#FFFFFFA8 +**placeholder-background**=*color* + +> Background color of placeholder input text. See **COLORS** for more +> information. +> +> Default: \#00000000 + +**placeholder-background-padding**=*directional* + +> Extra padding of the placeholder input text background. See +> **DIRECTIONAL** VALUES for more information. +> +> Default: 0 + +**placeholder-background-corner-radius**=*px* + +> Corner radius of the placeholder input text background. +> +> Default: 0 + +**input-color**=*color* + +> Color of input text. See **COLORS** for more information. +> +> Default: Same as **text-color** + +**input-background**=*color* + +> Background color of input. See **COLORS** for more information. +> +> Default: \#00000000 + +**input-background-padding**=*directional* + +> Extra padding of the input background. See **DIRECTIONAL VALUES** for +> more information. +> +> Default: 0 + +**input-background-corner-radius**=*px* + +> Corner radius of the input background. +> +> Default: 0 + +**default-result-color**=*color* + +> Default color of result text. See **COLORS** for more information. +> +> Default: Same as **text-color** + +**default-result-background**=*color* + +> Default background color of results. See **COLORS** for more +> information. +> +> Default: \#00000000 + +**default-result-background-padding**=*directional* + +> Default extra padding of result backgrounds. See **DIRECTIONAL +> VALUES** for more information. +> +> Default: 0 + +**default-result-background-corner-radius**=*px* + +> Default corner radius of result backgrounds. +> +> Default: 0 + +**alternate-result-color**=*color* + +> Color of alternate (even-numbered) result text. See **COLORS** for +> more information. +> +> Default: same as **default-result-color** + +**alternate-result-background**=*color* + +> Background color of alternate (even-numbered) results. See **COLORS** +> for more information. +> +> Default: same as **default-result-background** + +**alternate-result-background-padding**=*directional* + +> Extra padding of alternate (even-numbered) result backgrounds. See +> **DIRECTIONAL VALUES** for more information. +> +> Default: same as **default-result-background-padding** + +**alternate-result-background-corner-radius**=*px* + +> Corner radius of alternate (even-numbered) result backgrounds. +> +> Default: same as **default-result-background-corner-radius** + **num-results**=*n* > Maximum number of results to display. If *n* = 0, tofi will draw as @@ -258,6 +381,10 @@ options. **selection-padding**=*px* +> **WARNING**: This option is deprecated, and will be removed in a +> future version of tofi. You should use the +> **selection-background-padding** option instead. +> > Extra horizontal padding of the selection background. If *px* = -1, > the padding will fill the whole window width. > @@ -270,6 +397,17 @@ options. > > Default: \#00000000 +**selection-background-padding**=*directional* + +> Extra padding of the selected result background. See **DIRECTIONAL +> VALUES** for more information. +> +> Default: 0 + +**selection-background-corner-radius**=*px* + +> Corner radius of the selected result background. Default: 0 + **result-spacing**=*px* > Spacing between results. Can be negative. @@ -421,6 +559,31 @@ Some pixel values can optionally have a % suffix, like so: This will be interpreted as a percentage of the screen resolution in the relevant direction. +# DIRECTIONAL VALUES + +The background box padding of a type of text can be specified by one to +four comma separated values, with meanings similar to the CSS padding +property: + +> · +> +> One value sets all edges. + +> · +> +> Two values set (top & bottom), (left & right) edges. + +> · +> +> Three values set (top), (left & right), (bottom) edges. + +> · +> +> Four values set (top), (right), (bottom), (left) edges. + +Specifying -1 for any of the values will pad as far as possible in that +direction. + # AUTHORS Philip Jones \<philj56@gmail.com\> diff --git a/doc/tofi.5.scd b/doc/tofi.5.scd index acdb16a..53bdc68 100644 --- a/doc/tofi.5.scd +++ b/doc/tofi.5.scd @@ -191,6 +191,27 @@ options. Default: 0 +*prompt-color*=_color_ + Color of prompt text. See *COLORS* for more information. + + Default: Same as *text-color* + +*prompt-background*=_color_ + Background color of prompt. See *COLORS* for more information. + + Default: #00000000 + +*prompt-background-padding*=_directional_ + Extra padding of the prompt background. See *DIRECTIONAL VALUES* for more + information. + + Default: 0 + +*prompt-background-corner-radius*=_px_ + Corner radius of the prompt background. + + Default: 0 + *placeholder-text*=_string_ Placeholder input text. @@ -201,6 +222,88 @@ options. Default: #FFFFFFA8 +*placeholder-background*=_color_ + Background color of placeholder input text. See *COLORS* for more + information. + + Default: #00000000 + +*placeholder-background-padding*=_directional_ + Extra padding of the placeholder input text background. See *DIRECTIONAL + VALUES* for more information. + + Default: 0 + +*placeholder-background-corner-radius*=_px_ + Corner radius of the placeholder input text background. + + Default: 0 + +*input-color*=_color_ + Color of input text. See *COLORS* for more information. + + Default: Same as *text-color* + +*input-background*=_color_ + Background color of input. See *COLORS* for more information. + + Default: #00000000 + +*input-background-padding*=_directional_ + Extra padding of the input background. See *DIRECTIONAL VALUES* for more + information. + + Default: 0 + +*input-background-corner-radius*=_px_ + Corner radius of the input background. + + Default: 0 + +*default-result-color*=_color_ + Default color of result text. See *COLORS* for more information. + + Default: Same as *text-color* + +*default-result-background*=_color_ + Default background color of results. See *COLORS* for more information. + + Default: #00000000 + +*default-result-background-padding*=_directional_ + Default extra padding of result backgrounds. See *DIRECTIONAL VALUES* for + more information. + + Default: 0 + +*default-result-background-corner-radius*=_px_ + Default corner radius of result backgrounds. + + Default: 0 + +*alternate-result-color*=_color_ + Color of alternate (even-numbered) result text. See *COLORS* for more + information. + + Default: same as *default-result-color* + +*alternate-result-background*=_color_ + Background color of alternate (even-numbered) results. See *COLORS* for more + information. + + Default: same as *default-result-background* + +*alternate-result-background-padding*=_directional_ + Extra padding of alternate (even-numbered) result backgrounds. See + *DIRECTIONAL VALUES* for more information. + + Default: same as *default-result-background-padding* + +*alternate-result-background-corner-radius*=_px_ + Corner radius of alternate (even-numbered) result backgrounds. + + Default: same as *default-result-background-corner-radius* + *num-results*=_n_ Maximum number of results to display. If _n_ = 0, tofi will draw as many results as it can fit in the window. @@ -221,6 +324,9 @@ options. Default: #00000000 *selection-padding*=_px_ + *WARNING*: This option is deprecated, and will be removed in a future version + of tofi. You should use the *selection-background-padding* option instead. + Extra horizontal padding of the selection background. If _px_ = -1, the padding will fill the whole window width. @@ -231,6 +337,16 @@ options. Default: #00000000 +*selection-background-padding*=_directional_ + Extra padding of the selected result background. See *DIRECTIONAL VALUES* for + more information. + + Default: 0 + +*selection-background-corner-radius*=_px_ + Corner radius of the selected result background. + Default: 0 + *result-spacing*=_px_ Spacing between results. Can be negative. @@ -362,6 +478,19 @@ Some pixel values can optionally have a % suffix, like so: This will be interpreted as a percentage of the screen resolution in the relevant direction. +# DIRECTIONAL VALUES + +The background box padding of a type of text can be specified by one to four +comma separated values, with meanings similar to the CSS padding property: + +- One value sets all edges. +- Two values set (top & bottom), (left & right) edges. +- Three values set (top), (left & right), (bottom) edges. +- Four values set (top), (right), (bottom), (left) edges. + +Specifying -1 for any of the values will pad as far as possible in that +direction. + # AUTHORS Philip Jones <philj56@gmail.com> diff --git a/src/config.c b/src/config.c index 3a65d3d..3f2873d 100644 --- a/src/config.c +++ b/src/config.c @@ -83,6 +83,7 @@ static struct color parse_color(const char *filename, size_t lineno, const char static uint32_t parse_uint32(const char *filename, size_t lineno, const char *str, bool *err); static int32_t parse_int32(const char *filename, size_t lineno, const char *str, bool *err); static struct uint32_percent parse_uint32_percent(const char *filename, size_t lineno, const char *str, bool *err); +static struct directional parse_directional(const char *filename, size_t lineno, const char *str, bool *err); /* * Function-like macro. Yuck. @@ -339,10 +340,68 @@ bool parse_option(struct tofi *tofi, const char *filename, size_t lineno, const snprintf(tofi->window.entry.prompt_text, N_ELEM(tofi->window.entry.prompt_text), "%s", value); } else if (strcasecmp(option, "prompt-padding") == 0) { tofi->window.entry.prompt_padding = parse_uint32(filename, lineno, value, &err); + } else if (strcasecmp(option, "prompt-color") == 0) { + tofi->window.entry.prompt_theme.foreground_color = parse_color(filename, lineno, value, &err); + tofi->window.entry.prompt_theme.foreground_specified = true; + } else if (strcasecmp(option, "prompt-background") == 0) { + tofi->window.entry.prompt_theme.background_color = parse_color(filename, lineno, value, &err); + tofi->window.entry.prompt_theme.background_specified = true; + } else if (strcasecmp(option, "prompt-background-padding") == 0) { + tofi->window.entry.prompt_theme.padding = parse_directional(filename, lineno, value, &err); + tofi->window.entry.prompt_theme.padding_specified = true; + } else if (strcasecmp(option, "prompt-background-corner-radius") == 0) { + tofi->window.entry.prompt_theme.background_corner_radius = parse_uint32(filename, lineno, value, &err); + tofi->window.entry.prompt_theme.radius_specified = true; } else if (strcasecmp(option, "placeholder-text") == 0) { snprintf(tofi->window.entry.placeholder_text, N_ELEM(tofi->window.entry.placeholder_text), "%s", value); } else if (strcasecmp(option, "placeholder-color") == 0) { - tofi->window.entry.placeholder_color = parse_color(filename, lineno, value, &err); + tofi->window.entry.placeholder_theme.foreground_color = parse_color(filename, lineno, value, &err); + tofi->window.entry.placeholder_theme.foreground_specified = true; + } else if (strcasecmp(option, "placeholder-background") == 0) { + tofi->window.entry.placeholder_theme.background_color = parse_color(filename, lineno, value, &err); + tofi->window.entry.placeholder_theme.background_specified = true; + } else if (strcasecmp(option, "placeholder-background-padding") == 0) { + tofi->window.entry.placeholder_theme.padding = parse_directional(filename, lineno, value, &err); + tofi->window.entry.placeholder_theme.padding_specified = true; + } else if (strcasecmp(option, "placeholder-background-corner-radius") == 0) { + tofi->window.entry.placeholder_theme.background_corner_radius = parse_uint32(filename, lineno, value, &err); + tofi->window.entry.placeholder_theme.radius_specified = true; + } else if (strcasecmp(option, "input-color") == 0) { + tofi->window.entry.input_theme.foreground_color = parse_color(filename, lineno, value, &err); + tofi->window.entry.input_theme.foreground_specified = true; + } else if (strcasecmp(option, "input-background") == 0) { + tofi->window.entry.input_theme.background_color = parse_color(filename, lineno, value, &err); + tofi->window.entry.input_theme.background_specified = true; + } else if (strcasecmp(option, "input-background-padding") == 0) { + tofi->window.entry.input_theme.padding = parse_directional(filename, lineno, value, &err); + tofi->window.entry.input_theme.padding_specified = true; + } else if (strcasecmp(option, "input-background-corner-radius") == 0) { + tofi->window.entry.input_theme.background_corner_radius = parse_uint32(filename, lineno, value, &err); + tofi->window.entry.input_theme.radius_specified = true; + } else if (strcasecmp(option, "default-result-color") == 0) { + tofi->window.entry.default_result_theme.foreground_color = parse_color(filename, lineno, value, &err); + tofi->window.entry.default_result_theme.foreground_specified = true; + } else if (strcasecmp(option, "default-result-background") == 0) { + tofi->window.entry.default_result_theme.background_color = parse_color(filename, lineno, value, &err); + tofi->window.entry.default_result_theme.background_specified = true; + } else if (strcasecmp(option, "default-result-background-padding") == 0) { + tofi->window.entry.default_result_theme.padding = parse_directional(filename, lineno, value, &err); + tofi->window.entry.default_result_theme.padding_specified = true; + } else if (strcasecmp(option, "default-result-background-corner-radius") == 0) { + tofi->window.entry.default_result_theme.background_corner_radius = parse_uint32(filename, lineno, value, &err); + tofi->window.entry.default_result_theme.radius_specified = true; + } else if (strcasecmp(option, "alternate-result-color") == 0) { + tofi->window.entry.alternate_result_theme.foreground_color = parse_color(filename, lineno, value, &err); + tofi->window.entry.alternate_result_theme.foreground_specified = true; + } else if (strcasecmp(option, "alternate-result-background") == 0) { + tofi->window.entry.alternate_result_theme.background_color = parse_color(filename, lineno, value, &err); + tofi->window.entry.alternate_result_theme.background_specified = true; + } else if (strcasecmp(option, "alternate-result-background-padding") == 0) { + tofi->window.entry.alternate_result_theme.padding = parse_directional(filename, lineno, value, &err); + tofi->window.entry.alternate_result_theme.padding_specified = true; + } else if (strcasecmp(option, "alternate-result-background-corner-radius") == 0) { + tofi->window.entry.alternate_result_theme.background_corner_radius = parse_uint32(filename, lineno, value, &err); + tofi->window.entry.alternate_result_theme.radius_specified = true; } else if (strcasecmp(option, "min-input-width") == 0) { tofi->window.entry.input_width = parse_uint32(filename, lineno, value, &err); } else if (strcasecmp(option, "result-spacing") == 0) { @@ -354,13 +413,25 @@ bool parse_option(struct tofi *tofi, const char *filename, size_t lineno, const } else if (strcasecmp(option, "text-color") == 0) { tofi->window.entry.foreground_color = parse_color(filename, lineno, value, &err); } else if (strcasecmp(option, "selection-color") == 0) { - tofi->window.entry.selection_foreground_color = parse_color(filename, lineno, value, &err); + tofi->window.entry.selection_theme.foreground_color = parse_color(filename, lineno, value, &err); + tofi->window.entry.selection_theme.foreground_specified = true; } else if (strcasecmp(option, "selection-match-color") == 0) { tofi->window.entry.selection_highlight_color = parse_color(filename, lineno, value, &err); } else if (strcasecmp(option, "selection-padding") == 0) { - tofi->window.entry.selection_background_padding = parse_int32(filename, lineno, value, &err); + log_warning("The \"selection-padding\" option is deprecated, and will be removed in future. Please switch to \"selection-background-padding\".\n"); + int32_t val = parse_int32(filename, lineno, value, &err); + tofi->window.entry.selection_theme.padding.left = val; + tofi->window.entry.selection_theme.padding.right = val; + tofi->window.entry.selection_theme.padding_specified = true; } else if (strcasecmp(option, "selection-background") == 0) { - tofi->window.entry.selection_background_color = parse_color(filename, lineno, value, &err); + tofi->window.entry.selection_theme.background_color = parse_color(filename, lineno, value, &err); + tofi->window.entry.selection_theme.background_specified = true; + } else if (strcasecmp(option, "selection-background-padding") == 0) { + tofi->window.entry.selection_theme.padding = parse_directional(filename, lineno, value, &err); + tofi->window.entry.selection_theme.padding_specified = true; + } else if (strcasecmp(option, "selection-background-corner-radius") == 0) { + tofi->window.entry.selection_theme.background_corner_radius = parse_uint32(filename, lineno, value, &err); + tofi->window.entry.selection_theme.radius_specified = true; } else if (strcasecmp(option, "exclusive-zone") == 0) { if (strcmp(value, "-1") == 0) { tofi->window.exclusive_zone = -1; @@ -735,3 +806,72 @@ struct uint32_percent parse_uint32_percent(const char *filename, size_t lineno, } return (struct uint32_percent){ val, percent }; } + +struct directional parse_directional(const char *filename, size_t lineno, const char *str, bool *err) +{ + int32_t values[4]; + char *saveptr = NULL; + char *tmp = xstrdup(str); + char *val = strtok_r(tmp, ",", &saveptr); + + size_t n; + + for (n = 0; n < N_ELEM(values) && val != NULL; n++) { + values[n] = parse_int32(filename, lineno, val, err); + if (err && *err) { + break; + } + val = strtok_r(NULL, ",", &saveptr); + } + free(tmp); + + struct directional ret = {0}; + if (err && *err) { + return ret; + } + + switch (n) { + case 0: + break; + case 1: + ret = (struct directional) { + .top = values[0], + .right = values[0], + .bottom = values[0], + .left = values[0], + }; + break; + case 2: + ret = (struct directional) { + .top = values[0], + .right = values[1], + .bottom = values[0], + .left = values[1], + }; + break; + case 3: + ret = (struct directional) { + .top = values[0], + .right = values[1], + .bottom = values[2], + .left = values[1], + }; + break; + case 4: + ret = (struct directional) { + .top = values[0], + .right = values[1], + .bottom = values[2], + .left = values[3], + }; + break; + default: + PARSE_ERROR(filename, lineno, "Too many values in \"%s\" for directional.\n", str); + if (err) { + *err = true; + } + break; + }; + + return ret; +} diff --git a/src/entry.c b/src/entry.c index 70f4525..9e05f57 100644 --- a/src/entry.c +++ b/src/entry.c @@ -27,6 +27,38 @@ static void rounded_rectangle(cairo_t *cr, uint32_t width, uint32_t height, uint cairo_close_path(cr); } +static void apply_text_theme_fallback(struct text_theme *theme, const struct text_theme *fallback) +{ + if (!theme->foreground_specified) { + theme->foreground_color = fallback->foreground_color; + } + if (!theme->background_specified) { + theme->background_color = fallback->background_color; + } + if (!theme->padding_specified) { + theme->padding = fallback->padding; + } + if (!theme->radius_specified) { + theme->background_corner_radius = fallback->background_corner_radius; + } +} + +static void fixup_padding_sizes(struct directional *padding, uint32_t clip_width, uint32_t clip_height) +{ + if (padding->top == -1) { + padding->top = clip_height; + } + if (padding->bottom == -1) { + padding->bottom = clip_height; + } + if (padding->left == -1) { + padding->left = clip_width; + } + if (padding->right == -1) { + padding->right = clip_width; + } +} + void entry_init(struct entry *entry, uint8_t *restrict buffer, uint32_t width, uint32_t height) { entry->image.width = width; @@ -139,6 +171,31 @@ void entry_init(struct entry *entry, uint8_t *restrict buffer, uint32_t width, u entry->clip_width = width; entry->clip_height = height; + /* + * Before we render any text, ensure all text themes are fully + * specified. + */ + const struct text_theme default_theme = { + .foreground_color = entry->foreground_color, + .background_color = (struct color) { .a = 0 }, + .padding = (struct directional) {0}, + .background_corner_radius = 0 + }; + + apply_text_theme_fallback(&entry->prompt_theme, &default_theme); + apply_text_theme_fallback(&entry->input_theme, &default_theme); + apply_text_theme_fallback(&entry->placeholder_theme, &default_theme); + apply_text_theme_fallback(&entry->default_result_theme, &default_theme); + apply_text_theme_fallback(&entry->alternate_result_theme, &entry->default_result_theme); + apply_text_theme_fallback(&entry->selection_theme, &default_theme); + + fixup_padding_sizes(&entry->prompt_theme.padding, width, height); + fixup_padding_sizes(&entry->input_theme.padding, width, height); + fixup_padding_sizes(&entry->placeholder_theme.padding, width, height); + fixup_padding_sizes(&entry->default_result_theme.padding, width, height); + fixup_padding_sizes(&entry->alternate_result_theme.padding, width, height); + fixup_padding_sizes(&entry->selection_theme.padding, width, height); + /* * Perform an initial render of the text. * This is done here rather than by calling entry_update to avoid the diff --git a/src/entry.h b/src/entry.h index 8e9d2b4..05e73ab 100644 --- a/src/entry.h +++ b/src/entry.h @@ -19,6 +19,25 @@ #define MAX_FONT_FEATURES_LENGTH 128 #define MAX_FONT_VARIATIONS_LENGTH 128 +struct directional { + int32_t top; + int32_t right; + int32_t bottom; + int32_t left; +}; + +struct text_theme { + struct color foreground_color; + struct color background_color; + struct directional padding; + uint32_t background_corner_radius; + + bool foreground_specified; + bool background_specified; + bool padding_specified; + bool radius_specified; +}; + struct entry { struct image image; struct entry_backend_harfbuzz harfbuzz; @@ -79,12 +98,16 @@ struct entry { uint32_t outline_width; struct color foreground_color; struct color background_color; - struct color placeholder_color; struct color selection_highlight_color; - struct color selection_foreground_color; - struct color selection_background_color; struct color border_color; struct color outline_color; + + struct text_theme prompt_theme; + struct text_theme input_theme; + struct text_theme placeholder_theme; + struct text_theme default_result_theme; + struct text_theme alternate_result_theme; + struct text_theme selection_theme; }; void entry_init(struct entry *entry, uint8_t *restrict buffer, uint32_t width, uint32_t height); diff --git a/src/entry_backend/harfbuzz.c b/src/entry_backend/harfbuzz.c index f038578..59c0d11 100644 --- a/src/entry_backend/harfbuzz.c +++ b/src/entry_backend/harfbuzz.c @@ -30,6 +30,25 @@ const struct { #undef MIN #define MIN(a, b) ((a) < (b) ? (a) : (b)) +static void rounded_rectangle(cairo_t *cr, uint32_t width, uint32_t height, uint32_t r) +{ + cairo_new_path(cr); + + /* Top-left */ + cairo_arc(cr, r, r, r, -M_PI, -M_PI_2); + + /* Top-right */ + cairo_arc(cr, width - r, r, r, -M_PI_2, 0); + + /* Bottom-right */ + cairo_arc(cr, width - r, height - r, r, 0, M_PI_2); + + /* Bottom-left */ + cairo_arc(cr, r, height - r, r, M_PI_2, M_PI); + + cairo_close_path(cr); +} + static const char *get_ft_error_string(int err_code) { for (size_t i = 0; i < N_ELEM(ft_errors); i++) { @@ -60,8 +79,8 @@ static void setup_hb_buffer(hb_buffer_t *buffer) /* - * Render a hb_buffer with Cairo, and return the width of the rendered area in - * Cairo units. + * Render a hb_buffer with Cairo, and return the extents of the rendered text + * in Cairo units. */ static cairo_text_extents_t render_hb_buffer(cairo_t *cr, hb_buffer_t *buffer) { @@ -109,6 +128,10 @@ static cairo_text_extents_t render_hb_buffer(cairo_t *cr, hb_buffer_t *buffer) return extents; } +/* + * Clear the harfbuzz buffer, shape some text and render it with Cairo, + * returning the extents of the rendered text in Cairo units. + */ static cairo_text_extents_t render_text( cairo_t *cr, struct entry_backend_harfbuzz *hb, @@ -121,6 +144,62 @@ static cairo_text_extents_t render_text( return render_hb_buffer(cr, hb->hb_buffer); } + +/* + * Render some text with an optional background box, using settings from the + * given theme. + */ +static cairo_text_extents_t render_text_themed( + cairo_t *cr, + struct entry_backend_harfbuzz *hb, + const char *text, + const struct text_theme *theme) +{ + cairo_font_extents_t font_extents; + cairo_font_extents(cr, &font_extents); + struct directional padding = theme->padding; + + /* + * I previously thought rendering the text to a group, measuring it, + * drawing the box on the main canvas and then drawing the group would + * be the most efficient way of doing this. I was wrong. + * + * It turns out to be much quicker to just draw the text to the canvas, + * paint over it with the box, and then draw the text again. This is + * fine, as long as the box is always bigger than the text (which it is + * unless the user sets some extreme values for the corner radius). + */ + struct color color = theme->foreground_color; + cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); + cairo_text_extents_t extents = render_text(cr, hb, text); + + if (theme->background_color.a == 0) { + /* No background to draw, we're done. */ + return extents; + } + + 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 + extents.x_bearing), + -padding.top); + rounded_rectangle( + cr, + ceil(extents.width + padding.left + padding.right), + ceil(font_extents.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); + render_text(cr, hb, text); + return extents; +} + static bool size_overflows(struct entry *entry, uint32_t width, uint32_t height) { cairo_t *cr = entry->cairo[entry->index].cr; @@ -274,44 +353,40 @@ void entry_backend_harfbuzz_destroy(struct entry *entry) void entry_backend_harfbuzz_update(struct entry *entry) { cairo_t *cr = entry->cairo[entry->index].cr; - hb_buffer_t *buffer = entry->harfbuzz.hb_buffer; cairo_text_extents_t extents; cairo_save(cr); - struct color color = entry->foreground_color; - cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); /* Render the prompt */ - extents = render_text(cr, &entry->harfbuzz, entry->prompt_text); + extents = render_text_themed(cr, &entry->harfbuzz, entry->prompt_text, &entry->prompt_theme); cairo_translate(cr, extents.x_advance, 0); cairo_translate(cr, entry->prompt_padding, 0); /* Render the entry text */ if (entry->input_utf8_length == 0) { - color = entry->placeholder_color; - cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); - extents = render_text(cr, &entry->harfbuzz, entry->placeholder_text); + extents = render_text_themed(cr, &entry->harfbuzz, entry->placeholder_text, &entry->placeholder_theme); } else if (entry->hide_input) { - hb_buffer_clear_contents(buffer); - setup_hb_buffer(buffer); - size_t char_len = N_ELEM(entry->hidden_character_utf8); - for (size_t i = 0; i < entry->input_utf32_length; i++) { - hb_buffer_add_utf8(buffer, entry->hidden_character_utf8, char_len, 0, char_len); + size_t nchars = entry->input_utf32_length; + size_t char_size = entry->hidden_character_utf8_length; + char *buf = xmalloc(1 + nchars * char_size); + for (size_t i = 0; i < nchars; i++) { + for (size_t j = 0; j < char_size; j++) { + buf[i * char_size + j] = entry->hidden_character_utf8[j]; + } } - hb_shape(entry->harfbuzz.hb_font, buffer, entry->harfbuzz.hb_features, entry->harfbuzz.num_features); - extents = render_hb_buffer(cr, buffer); + buf[char_size * nchars] = '\0'; + + extents = render_text_themed(cr, &entry->harfbuzz, buf, &entry->input_theme); + free(buf); } else { - extents = render_text(cr, &entry->harfbuzz, entry->input_utf8); + extents = render_text_themed(cr, &entry->harfbuzz, entry->input_utf8, &entry->input_theme); } extents.x_advance = MAX(extents.x_advance, entry->input_width); cairo_font_extents_t font_extents; cairo_font_extents(cr, &font_extents); - color = entry->foreground_color; - cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); - uint32_t num_results; if (entry->num_results == 0) { num_results = entry->results.count; @@ -347,25 +422,24 @@ void entry_backend_harfbuzz_update(struct entry *entry) const char *result = entry->results.buf[index].string; /* * If this isn't the selected result, or it is but we're not - * doing any fancy match-highlighting or backgrounds, just - * print as normal. + * doing any fancy match-highlighting, just print as normal. */ - if (i != entry->selection - || (entry->selection_highlight_color.a == 0 - && entry->selection_background_color.a == 0)) { + if (i != entry->selection || (entry->selection_highlight_color.a == 0)) { + const struct text_theme *theme; if (i == entry->selection) { - color = entry->selection_foreground_color; + theme = &entry->selection_theme; + } else if (index % 2) { + theme = &entry->alternate_result_theme;; } else { - color = entry->foreground_color; + theme = &entry->default_result_theme;; } - cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); if (entry->num_results > 0) { /* * We're not auto-detecting how many results we * can fit, so just render the text. */ - extents = render_text(cr, &entry->harfbuzz, result); + extents = render_text_themed(cr, &entry->harfbuzz, result, theme); } else if (!entry->horizontal) { /* * The height of the text doesn't change, so @@ -375,19 +449,19 @@ void entry_backend_harfbuzz_update(struct entry *entry) entry->num_results_drawn = i; break; } else { - extents = render_text(cr, &entry->harfbuzz, result); + extents = render_text_themed(cr, &entry->harfbuzz, result, theme); } } else { /* * The difficult case: we're auto-detecting how * many results to draw, but we can't know - * whether this results will fit without + * whether this result will fit without * drawing it! To solve this, draw to a * temporary group, measure that, then copy it * to the main canvas only if it will fit. */ cairo_push_group(cr); - extents = render_text(cr, &entry->harfbuzz, result); + extents = render_text_themed(cr, &entry->harfbuzz, result, theme); cairo_pattern_t *group = cairo_pop_group(cr); if (size_overflows(entry, extents.x_advance, 0)) { @@ -404,21 +478,18 @@ void entry_backend_harfbuzz_update(struct entry *entry) } } else { /* - * For the selected result, there's a bit more to do. - * - * First, we need to use a different foreground color - - * simple enough. + * For match highlighting, there's a bit more to do. * - * Next, we may need to draw a background box - this - * involves rendering to a cairo group, measuring the - * size of the text, drawing the background on the main - * canvas, then finally drawing the group on top of - * that. + * We need to split the text into prematch, match and + * postmatch chunks, and draw each separately. * - * Finally, we may need to highlight the matching - * portion of text - this is achieved simply by - * splitting the text into prematch, match and - * postmatch chunks, and drawing each separately. + * However, we only want one background box around them + * all (if we're drawing one). To do this, we have to + * do the rendering part of render_text_themed() + * manually, with the same method of: + * - Draw the text and measure it + * - Draw the box + * - Draw the text again * * N.B. The size_overflows check isn't necessary here, * as it's currently not possible for the selection to @@ -429,7 +500,6 @@ void entry_backend_harfbuzz_update(struct entry *entry) char *prematch = xstrdup(result); char *match = NULL; char *postmatch = NULL; - cairo_text_extents_t subextents; if (entry->input_utf8_length > 0 && entry->selection_highlight_color.a != 0) { char *match_pos = utf8_strcasestr(prematch, entry->input_utf8); if (match_pos != NULL) { @@ -444,38 +514,53 @@ void entry_backend_harfbuzz_update(struct entry *entry) } } - cairo_push_group(cr); - color = entry->selection_foreground_color; - cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); - - subextents = render_text(cr, &entry->harfbuzz, prematch); - extents = subextents; - - free(prematch); - prematch = NULL; - - if (match != NULL) { - cairo_translate(cr, subextents.x_advance, 0); - color = entry->selection_highlight_color; + for (int pass = 0; pass < 2; pass++) { + cairo_save(cr); + struct color color = entry->selection_theme.foreground_color; cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); - subextents = render_text(cr, &entry->harfbuzz, &match[prematch_len]); + cairo_text_extents_t subextents = render_text(cr, &entry->harfbuzz, prematch); + extents = subextents; + + if (match != NULL) { + cairo_translate(cr, subextents.x_advance, 0); + color = entry->selection_highlight_color; + cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); + + subextents = render_text(cr, &entry->harfbuzz, &match[prematch_len]); + + if (prematch_len == 0) { + extents = subextents; + } else { + /* + * This calculation is a little + * complex, but it's basically: + * + * (distance from leftmost pixel of + * prematch to logical end of prematch) + * + * + + * + * (distance from logical start of match + * to rightmost pixel of match). + */ + extents.width = extents.x_advance + - extents.x_bearing + + subextents.x_bearing + + subextents.width; + extents.x_advance += subextents.x_advance; + } + } + + if (postmatch != NULL) { + cairo_translate(cr, subextents.x_advance, 0); + color = entry->selection_theme.foreground_color; + cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); + subextents = render_text( + cr, + &entry->harfbuzz, + &postmatch[entry->input_utf8_length + prematch_len]); - if (prematch_len == 0) { - extents = subextents; - } else { - /* - * This calculation is a little - * complex, but it's basically: - * - * (distance from leftmost pixel of - * prematch to logical end of prematch) - * - * + - * - * (distance from logical start of match - * to rightmost pixel of match). - */ extents.width = extents.x_advance - extents.x_bearing + subextents.x_bearing @@ -483,43 +568,42 @@ void entry_backend_harfbuzz_update(struct entry *entry) extents.x_advance += subextents.x_advance; } - free(match); - match = NULL; + cairo_restore(cr); + + if (entry->selection_theme.background_color.a == 0) { + /* No background box, we're done. */ + break; + } else if (pass == 0) { + /* + * First pass, paint over the text with + * our background box. + */ + struct directional padding = entry->selection_theme.padding; + cairo_save(cr); + color = entry->selection_theme.background_color; + cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); + cairo_translate( + cr, + floor(-padding.left + extents.x_bearing), + -padding.top); + rounded_rectangle( + cr, + ceil(extents.width + padding.left + padding.right), + ceil(font_extents.height + padding.top + padding.bottom), + entry->selection_theme.background_corner_radius + ); + cairo_fill(cr); + cairo_restore(cr); + } } + free(prematch); + if (match != NULL) { + free(match); + } if (postmatch != NULL) { - cairo_translate(cr, subextents.x_advance, 0); - color = entry->selection_foreground_color; - cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); - subextents = render_text( - cr, - &entry->harfbuzz, - &postmatch[entry->input_utf8_length + prematch_len]); - - extents.width = extents.x_advance - - extents.x_bearing - + subextents.x_bearing - + subextents.width; - extents.x_advance += subextents.x_advance; - - free(postmatch); - postmatch = NULL; - } - - cairo_pop_group_to_source(cr); - cairo_save(cr); - color = entry->selection_background_color; - cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); - int32_t pad = entry->selection_background_padding; - if (pad < 0) { - pad = entry->clip_width; } - cairo_translate(cr, floor(-pad + extents.x_bearing), 0); - cairo_rectangle(cr, 0, 0, ceil(extents.width + pad * 2), ceil(font_extents.height)); - cairo_fill(cr); - cairo_restore(cr); - cairo_paint(cr); } } entry->num_results_drawn = i; diff --git a/src/entry_backend/pango.c b/src/entry_backend/pango.c index 7a63d1a..82c93ee 100644 --- a/src/entry_backend/pango.c +++ b/src/entry_backend/pango.c @@ -13,6 +13,70 @@ #undef MIN #define MIN(a, b) ((a) < (b) ? (a) : (b)) +static void rounded_rectangle(cairo_t *cr, uint32_t width, uint32_t height, uint32_t r) +{ + cairo_new_path(cr); + + /* Top-left */ + cairo_arc(cr, r, r, r, -M_PI, -M_PI_2); + + /* Top-right */ + cairo_arc(cr, width - r, r, r, -M_PI_2, 0); + + /* Bottom-right */ + cairo_arc(cr, width - r, height - r, r, 0, M_PI_2); + + /* Bottom-left */ + cairo_arc(cr, r, height - r, r, M_PI_2, M_PI); + + cairo_close_path(cr); +} + +static void render_text_themed( + cairo_t *cr, + PangoLayout *layout, + const char *text, + const struct text_theme *theme, + PangoRectangle *ink_rect, + PangoRectangle *logical_rect) +{ + 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); + + if (theme->background_color.a == 0) { + /* No background to draw, we're done. */ + return; + } + + struct directional padding = theme->padding; + + 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 + 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); +} + void entry_backend_pango_init(struct entry *entry, uint32_t *width, uint32_t *height) { cairo_t *cr = entry->cairo[0].cr; @@ -85,44 +149,34 @@ void entry_backend_pango_update(struct entry *entry) cairo_save(cr); struct color color = entry->foreground_color; - cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); /* Render the prompt */ - pango_layout_set_text(layout, entry->prompt_text, -1); - pango_cairo_update_layout(cr, layout); - pango_cairo_show_layout(cr, layout); - PangoRectangle ink_rect; PangoRectangle logical_rect; - pango_layout_get_pixel_extents(entry->pango.layout, &ink_rect, &logical_rect); - cairo_translate(cr, logical_rect.width + logical_rect.x, 0); + render_text_themed(cr, layout, entry->prompt_text, &entry->prompt_theme, &ink_rect, &logical_rect); + cairo_translate(cr, logical_rect.width + logical_rect.x, 0); cairo_translate(cr, entry->prompt_padding, 0); /* Render the entry text */ if (entry->input_utf8_length == 0) { - color = entry->placeholder_color; - cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); - pango_layout_set_text(layout, entry->placeholder_text, -1); + render_text_themed(cr, layout, entry->placeholder_text, &entry->placeholder_theme, &ink_rect, &logical_rect); } else if (entry->hide_input) { - /* - * Pango needs to be passed the whole text at once, so we need - * to manually replicate the replacement character in a buffer. - */ - static char buf[sizeof(entry->input_utf8)]; - uint32_t char_len = entry->hidden_character_utf8_length; - for (size_t i = 0; i < entry->input_utf32_length; i++) { - for (size_t j = 0; j < char_len; j++) { - buf[i * char_len + j] = entry->hidden_character_utf8[j]; + size_t nchars = entry->input_utf32_length; + size_t char_size = entry->hidden_character_utf8_length; + char *buf = xmalloc(1 + nchars * char_size); + for (size_t i = 0; i < nchars; i++) { + for (size_t j = 0; j < char_size; j++) { + buf[i * char_size + j] = entry->hidden_character_utf8[j]; } } - pango_layout_set_text(layout, buf, char_len * entry->input_utf32_length); + buf[char_size * nchars] = '\0'; + + render_text_themed(cr, layout, buf, &entry->placeholder_theme, &ink_rect, &logical_rect); + free(buf); } else { - pango_layout_set_text(layout, entry->input_utf8, -1); + render_text_themed(cr, layout, entry->input_utf8, &entry->input_theme, &ink_rect, &logical_rect); } - pango_cairo_update_layout(cr, layout); - pango_cairo_show_layout(cr, layout); - pango_layout_get_pixel_extents(entry->pango.layout, &ink_rect, &logical_rect); logical_rect.width = MAX(logical_rect.width, (int)entry->input_width); color = entry->foreground_color; @@ -164,25 +218,29 @@ void entry_backend_pango_update(struct entry *entry) } else { str = ""; } - if (i != entry->selection) { - pango_layout_set_text(layout, str, -1); - pango_cairo_update_layout(cr, layout); + if (i != entry->selection || (entry->selection_highlight_color.a == 0)) { + const struct text_theme *theme; + if (i == entry->selection) { + theme = &entry->selection_theme; + } else if (index % 2) { + theme = &entry->alternate_result_theme;; + } else { + theme = &entry->default_result_theme;; + } if (entry->num_results > 0) { - pango_cairo_show_layout(cr, layout); - pango_layout_get_pixel_extents(entry->pango.layout, &ink_rect, &logical_rect); + render_text_themed(cr, layout, str, theme, &ink_rect, &logical_rect); } else if (!entry->horizontal) { if (size_overflows(entry, 0, logical_rect.height)) { entry->num_results_drawn = i; break; } else { - pango_cairo_show_layout(cr, layout); - pango_layout_get_pixel_extents(entry->pango.layout, &ink_rect, &logical_rect); + render_text_themed(cr, layout, str, theme, &ink_rect, &logical_rect); } } else { cairo_push_group(cr); - pango_cairo_show_layout(cr, layout); - pango_layout_get_pixel_extents(entry->pango.layout, &ink_rect, &logical_rect); + render_text_themed(cr, layout, str, theme, &ink_rect, &logical_rect); + cairo_pattern_t *group = cairo_pop_group(cr); if (size_overflows(entry, logical_rect.width, 0)) { entry->num_results_drawn = i; @@ -213,68 +271,77 @@ void entry_backend_pango_update(struct entry *entry) } } - cairo_push_group(cr); - color = entry->selection_foreground_color; - cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); - - pango_layout_set_text(layout, str, prematch_len); - pango_cairo_update_layout(cr, layout); - pango_cairo_show_layout(cr, layout); - pango_layout_get_pixel_extents(entry->pango.layout, &ink_subrect, &logical_subrect); - ink_rect = ink_subrect; - logical_rect = logical_subrect; - - if (prematch_len != -1) { - cairo_translate(cr, logical_subrect.x + logical_subrect.width, 0); - color = entry->selection_highlight_color; + for (int pass = 0; pass < 2; pass++) { + cairo_save(cr); + color = entry->selection_theme.foreground_color; cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); - pango_layout_set_text(layout, &str[prematch_len], match_len); + + pango_layout_set_text(layout, str, prematch_len); pango_cairo_update_layout(cr, layout); pango_cairo_show_layout(cr, layout); pango_layout_get_pixel_extents(entry->pango.layout, &ink_subrect, &logical_subrect); - if (prematch_len == 0) { - ink_rect = ink_subrect; - logical_rect = logical_subrect; - } else { + ink_rect = ink_subrect; + logical_rect = logical_subrect; + + if (prematch_len != -1) { + cairo_translate(cr, logical_subrect.x + logical_subrect.width, 0); + color = entry->selection_highlight_color; + cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); + pango_layout_set_text(layout, &str[prematch_len], match_len); + pango_cairo_update_layout(cr, layout); + pango_cairo_show_layout(cr, layout); + pango_layout_get_pixel_extents(entry->pango.layout, &ink_subrect, &logical_subrect); + if (prematch_len == 0) { + ink_rect = ink_subrect; + logical_rect = logical_subrect; + } else { + ink_rect.width = logical_rect.width + - ink_rect.x + + ink_subrect.x + + ink_subrect.width; + logical_rect.width += logical_subrect.x + logical_subrect.width; + } + } + + if (postmatch_len != -1) { + cairo_translate(cr, logical_subrect.x + logical_subrect.width, 0); + color = entry->selection_theme.foreground_color; + cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); + pango_layout_set_text(layout, &str[prematch_len + match_len], -1); + pango_cairo_update_layout(cr, layout); + pango_cairo_show_layout(cr, layout); + pango_layout_get_pixel_extents(entry->pango.layout, &ink_subrect, &logical_subrect); ink_rect.width = logical_rect.width - ink_rect.x + ink_subrect.x + ink_subrect.width; logical_rect.width += logical_subrect.x + logical_subrect.width; - } - } - if (postmatch_len != -1) { - cairo_translate(cr, logical_subrect.x + logical_subrect.width, 0); - color = entry->selection_foreground_color; - cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); - pango_layout_set_text(layout, &str[prematch_len + match_len], -1); - pango_cairo_update_layout(cr, layout); - pango_cairo_show_layout(cr, layout); - pango_layout_get_pixel_extents(entry->pango.layout, &ink_subrect, &logical_subrect); - ink_rect.width = logical_rect.width - - ink_rect.x - + ink_subrect.x - + ink_subrect.width; - logical_rect.width += logical_subrect.x + logical_subrect.width; + } - } + cairo_restore(cr); - cairo_pop_group_to_source(cr); - cairo_save(cr); - color = entry->selection_background_color; - cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); - int32_t pad = entry->selection_background_padding; - if (pad < 0) { - pad = entry->clip_width; + if (entry->selection_theme.background_color.a == 0) { + break; + } else if (pass == 0) { + struct directional padding = entry->selection_theme.padding; + cairo_save(cr); + color = entry->selection_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 + padding.left + padding.right), + ceil(logical_rect.height + padding.top + padding.bottom), + entry->selection_theme.background_corner_radius + ); + cairo_fill(cr); + cairo_restore(cr); + } } - cairo_translate(cr, floor(-pad + ink_rect.x), 0); - cairo_rectangle(cr, 0, 0, ceil(ink_rect.width + pad * 2), ceil(logical_rect.height)); - cairo_fill(cr); - cairo_restore(cr); - cairo_paint(cr); - color = entry->foreground_color; - cairo_set_source_rgba(cr, color.r, color.g, color.b, color.a); } } entry->num_results_drawn = i; @@ -790,12 +790,33 @@ const struct option long_options[] = { {"selection-match-color", required_argument, NULL, 0}, {"selection-padding", required_argument, NULL, 0}, {"selection-background", required_argument, NULL, 0}, + {"selection-background-padding", required_argument, NULL, 0}, + {"selection-background-corner-radius", required_argument, NULL, 0}, {"outline-width", required_argument, NULL, 0}, {"outline-color", required_argument, NULL, 0}, {"prompt-text", required_argument, NULL, 0}, {"prompt-padding", required_argument, NULL, 0}, + {"prompt-color", required_argument, NULL, 0}, + {"prompt-background", required_argument, NULL, 0}, + {"prompt-background-padding", required_argument, NULL, 0}, + {"prompt-background-corner-radius", required_argument, NULL, 0}, {"placeholder-text", required_argument, NULL, 0}, {"placeholder-color", required_argument, NULL, 0}, + {"placeholder-background", required_argument, NULL, 0}, + {"placeholder-background-padding", required_argument, NULL, 0}, + {"placeholder-background-corner-radius", required_argument, NULL, 0}, + {"input-color", required_argument, NULL, 0}, + {"input-background", required_argument, NULL, 0}, + {"input-background-padding", required_argument, NULL, 0}, + {"input-background-corner-radius", required_argument, NULL, 0}, + {"default-result-color", required_argument, NULL, 0}, + {"default-result-background", required_argument, NULL, 0}, + {"default-result-background-padding", required_argument, NULL, 0}, + {"default-result-background-corner-radius", required_argument, NULL, 0}, + {"alternate-result-color", required_argument, NULL, 0}, + {"alternate-result-background", required_argument, NULL, 0}, + {"alternate-result-background-padding", required_argument, NULL, 0}, + {"alternate-result-background-corner-radius", required_argument, NULL, 0}, {"result-spacing", required_argument, NULL, 0}, {"min-input-width", required_argument, NULL, 0}, {"border-width", required_argument, NULL, 0}, @@ -1046,10 +1067,12 @@ int main(int argc, char *argv[]) .outline_width = 4, .background_color = {0.106f, 0.114f, 0.118f, 1.0f}, .foreground_color = {1.0f, 1.0f, 1.0f, 1.0f}, - .placeholder_color = {1.0f, 1.0f, 1.0f, 0.66f}, - .selection_foreground_color = {0.976f, 0.149f, 0.447f, 1.0f}, .border_color = {0.976f, 0.149f, 0.447f, 1.0f}, .outline_color = {0.031f, 0.031f, 0.0f, 1.0f}, + .placeholder_theme.foreground_color = {1.0f, 1.0f, 1.0f, 0.66f}, + .placeholder_theme.foreground_specified = true, + .selection_theme.foreground_color = {0.976f, 0.149f, 0.447f, 1.0f}, + .selection_theme.foreground_specified = true } }, .anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |