summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md14
-rw-r--r--completions/tofi22
-rw-r--r--doc/config81
-rw-r--r--doc/tofi.5.md163
-rw-r--r--doc/tofi.5.scd129
-rw-r--r--src/config.c148
-rw-r--r--src/entry.c57
-rw-r--r--src/entry.h29
-rw-r--r--src/entry_backend/harfbuzz.c298
-rw-r--r--src/entry_backend/pango.c231
-rw-r--r--src/main.c27
11 files changed, 977 insertions, 222 deletions
diff --git a/README.md b/README.md
index f3f8e5e..6344599 100644
--- a/README.md
+++ b/README.md
@@ -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:
+
![Default theme screenshot](screenshot_default.png)
+
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
diff --git a/doc/config b/doc/config
index 3e28eac..a008fbd 100644
--- a/doc/config
+++ b/doc/config
@@ -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;
diff --git a/src/main.c b/src/main.c
index 500edfb..f9291ee 100644
--- a/src/main.c
+++ b/src/main.c
@@ -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