2 Copyright (C) 2003-2006 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 #include <gdkmm/rectangle.h>
29 #include <gtkmm2ext/fastmeter.h>
30 #include <gtkmm2ext/utils.h>
32 #define UINT_TO_RGB(u,r,g,b) { (*(r)) = ((u)>>16)&0xff; (*(g)) = ((u)>>8)&0xff; (*(b)) = (u)&0xff; }
33 #define UINT_TO_RGBA(u,r,g,b,a) { UINT_TO_RGB(((u)>>8),r,g,b); (*(a)) = (u)&0xff; }
37 using namespace Gtkmm2ext;
40 int FastMeter::min_pattern_metric_size = 16;
41 int FastMeter::max_pattern_metric_size = 1024;
42 bool FastMeter::no_rgba_overlay = false;
44 FastMeter::Pattern10Map FastMeter::vm_pattern_cache;
45 FastMeter::PatternBgMap FastMeter::vb_pattern_cache;
47 FastMeter::Pattern10Map FastMeter::hm_pattern_cache;
48 FastMeter::PatternBgMap FastMeter::hb_pattern_cache;
50 FastMeter::FastMeter (long hold, unsigned long dimen, Orientation o, int len,
51 int clr0, int clr1, int clr2, int clr3,
52 int clr4, int clr5, int clr6, int clr7,
56 float stp0, float stp1,
57 float stp2, float stp3,
67 last_peak_rect.width = 0;
68 last_peak_rect.height = 0;
71 no_rgba_overlay = ! Glib::getenv("NO_METER_SHADE").empty();
95 _styleflags = styleflags;
97 set_events (BUTTON_PRESS_MASK|BUTTON_RELEASE_MASK);
105 if (orientation == Vertical) {
108 fgpattern = request_vertical_meter(pixwidth + 2, pixheight + 2, _clr, _stp, _styleflags);
109 bgpattern = request_vertical_background (pixwidth + 2, pixheight + 2, _bgc, false);
114 fgpattern = request_horizontal_meter(pixwidth + 2, pixheight + 2, _clr, _stp, _styleflags);
115 bgpattern = request_horizontal_background (pixwidth + 2, pixheight + 2, _bgc, false);
118 pixrect.width = pixwidth;
119 pixrect.height = pixheight;
121 request_width = pixrect.width + 2;
122 request_height= pixrect.height + 2;
127 FastMeter::~FastMeter ()
131 Cairo::RefPtr<Cairo::Pattern>
132 FastMeter::generate_meter_pattern (
133 int width, int height, int *clr, float *stp, int styleflags, bool horiz)
137 const double soft = 3.0 / (double) height;
138 const double offs = -1.0 / (double) height;
140 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
143 Cairo coordinate space goes downwards as y value goes up, so invert
144 knee-based positions by using (1.0 - y)
147 UINT_TO_RGBA (clr[9], &r, &g, &b, &a); // top/clip
148 cairo_pattern_add_color_stop_rgb (pat, 0.0,
149 r/255.0, g/255.0, b/255.0);
151 knee = offs + stp[3] / 115.0f; // -0dB
153 UINT_TO_RGBA (clr[8], &r, &g, &b, &a);
154 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
155 r/255.0, g/255.0, b/255.0);
157 UINT_TO_RGBA (clr[7], &r, &g, &b, &a);
158 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
159 r/255.0, g/255.0, b/255.0);
161 knee = offs + stp[2]/ 115.0f; // -3dB || -2dB
163 UINT_TO_RGBA (clr[6], &r, &g, &b, &a);
164 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
165 r/255.0, g/255.0, b/255.0);
167 UINT_TO_RGBA (clr[5], &r, &g, &b, &a);
168 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
169 r/255.0, g/255.0, b/255.0);
171 knee = offs + stp[1] / 115.0f; // -9dB
173 UINT_TO_RGBA (clr[4], &r, &g, &b, &a);
174 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
175 r/255.0, g/255.0, b/255.0);
177 UINT_TO_RGBA (clr[3], &r, &g, &b, &a);
178 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
179 r/255.0, g/255.0, b/255.0);
181 knee = offs + stp[0] / 115.0f; // -18dB
183 UINT_TO_RGBA (clr[2], &r, &g, &b, &a);
184 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee,
185 r/255.0, g/255.0, b/255.0);
187 UINT_TO_RGBA (clr[1], &r, &g, &b, &a);
188 cairo_pattern_add_color_stop_rgb (pat, 1.0 - knee + soft,
189 r/255.0, g/255.0, b/255.0);
191 UINT_TO_RGBA (clr[0], &r, &g, &b, &a); // bottom
192 cairo_pattern_add_color_stop_rgb (pat, 1.0,
193 r/255.0, g/255.0, b/255.0);
195 if ((styleflags & 1) && !no_rgba_overlay) {
196 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
197 cairo_pattern_add_color_stop_rgba (shade_pattern, 0, 0.0, 0.0, 0.0, 0.15);
198 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.4, 1.0, 1.0, 1.0, 0.05);
199 cairo_pattern_add_color_stop_rgba (shade_pattern, 1, 0.0, 0.0, 0.0, 0.25);
201 cairo_surface_t* surface;
203 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
204 tc = cairo_create (surface);
205 cairo_set_source (tc, pat);
206 cairo_rectangle (tc, 0, 0, width, height);
208 cairo_pattern_destroy (pat);
210 cairo_set_source (tc, shade_pattern);
211 cairo_rectangle (tc, 0, 0, width, height);
213 cairo_pattern_destroy (shade_pattern);
215 if (styleflags & 2) { // LED stripes
217 cairo_set_line_width(tc, 1.0);
218 cairo_set_source_rgba(tc, .0, .0, .0, 0.4);
219 //cairo_set_operator (tc, CAIRO_OPERATOR_SOURCE);
220 for (float y=0.5; y < height; y+= 2.0) {
221 cairo_move_to(tc, 0, y);
222 cairo_line_to(tc, width, y);
228 pat = cairo_pattern_create_for_surface (surface);
230 cairo_surface_destroy (surface);
234 cairo_surface_t* surface;
236 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, height, width);
237 tc = cairo_create (surface);
240 cairo_matrix_init_rotate (&m, -M_PI/2.0);
241 cairo_matrix_translate (&m, -height, 0);
242 cairo_pattern_set_matrix (pat, &m);
243 cairo_set_source (tc, pat);
244 cairo_rectangle (tc, 0, 0, height, width);
246 cairo_pattern_destroy (pat);
247 pat = cairo_pattern_create_for_surface (surface);
249 cairo_surface_destroy (surface);
251 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
257 Cairo::RefPtr<Cairo::Pattern>
258 FastMeter::generate_meter_background (
259 int width, int height, int *clr, bool shade, bool horiz)
261 guint8 r0,g0,b0,r1,g1,b1,a;
263 cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, height);
265 UINT_TO_RGBA (clr[0], &r0, &g0, &b0, &a);
266 UINT_TO_RGBA (clr[1], &r1, &g1, &b1, &a);
268 cairo_pattern_add_color_stop_rgb (pat, 0.0,
269 r1/255.0, g1/255.0, b1/255.0);
271 cairo_pattern_add_color_stop_rgb (pat, 1.0,
272 r0/255.0, g0/255.0, b0/255.0);
274 if (shade && !no_rgba_overlay) {
275 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, width, 0.0);
276 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1.0, 1.0, 1.0, 0.15);
277 cairo_pattern_add_color_stop_rgba (shade_pattern, 0.6, 0.0, 0.0, 0.0, 0.10);
278 cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 1.0, 1.0, 1.0, 0.20);
280 cairo_surface_t* surface;
282 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
283 tc = cairo_create (surface);
284 cairo_set_source (tc, pat);
285 cairo_rectangle (tc, 0, 0, width, height);
287 cairo_set_source (tc, shade_pattern);
288 cairo_rectangle (tc, 0, 0, width, height);
291 cairo_pattern_destroy (pat);
292 cairo_pattern_destroy (shade_pattern);
294 pat = cairo_pattern_create_for_surface (surface);
297 cairo_surface_destroy (surface);
301 cairo_surface_t* surface;
303 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, height, width);
304 tc = cairo_create (surface);
307 cairo_matrix_init_rotate (&m, -M_PI/2.0);
308 cairo_matrix_translate (&m, -height, 0);
309 cairo_pattern_set_matrix (pat, &m);
310 cairo_set_source (tc, pat);
311 cairo_rectangle (tc, 0, 0, height, width);
313 cairo_pattern_destroy (pat);
314 pat = cairo_pattern_create_for_surface (surface);
316 cairo_surface_destroy (surface);
319 Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
324 Cairo::RefPtr<Cairo::Pattern>
325 FastMeter::request_vertical_meter(
326 int width, int height, int *clr, float *stp, int styleflags)
328 height = max(height, min_pattern_metric_size);
329 height = min(height, max_pattern_metric_size);
331 const Pattern10MapKey key (width, height,
332 stp[0], stp[1], stp[2], stp[3],
333 clr[0], clr[1], clr[2], clr[3],
334 clr[4], clr[5], clr[6], clr[7],
335 clr[8], clr[9], styleflags);
337 Pattern10Map::iterator i;
338 if ((i = vm_pattern_cache.find (key)) != vm_pattern_cache.end()) {
341 // TODO flush pattern cache if it gets too large
343 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
344 width, height, clr, stp, styleflags, false);
345 vm_pattern_cache[key] = p;
350 Cairo::RefPtr<Cairo::Pattern>
351 FastMeter::request_vertical_background(
352 int width, int height, int *bgc, bool shade)
354 height = max(height, min_pattern_metric_size);
355 height = min(height, max_pattern_metric_size);
358 const PatternBgMapKey key (width, height, bgc[0], bgc[1], shade);
359 PatternBgMap::iterator i;
360 if ((i = vb_pattern_cache.find (key)) != vb_pattern_cache.end()) {
363 // TODO flush pattern cache if it gets too large
365 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
366 width, height, bgc, shade, false);
367 vb_pattern_cache[key] = p;
372 Cairo::RefPtr<Cairo::Pattern>
373 FastMeter::request_horizontal_meter(
374 int width, int height, int *clr, float *stp, int styleflags)
376 width = max(width, min_pattern_metric_size);
377 width = min(width, max_pattern_metric_size);
379 const Pattern10MapKey key (width, height,
380 stp[0], stp[1], stp[2], stp[3],
381 clr[0], clr[1], clr[2], clr[3],
382 clr[4], clr[5], clr[6], clr[7],
383 clr[8], clr[9], styleflags);
385 Pattern10Map::iterator i;
386 if ((i = hm_pattern_cache.find (key)) != hm_pattern_cache.end()) {
389 // TODO flush pattern cache if it gets too large
391 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_pattern (
392 height, width, clr, stp, styleflags, true);
394 hm_pattern_cache[key] = p;
398 Cairo::RefPtr<Cairo::Pattern>
399 FastMeter::request_horizontal_background(
400 int width, int height, int *bgc, bool shade)
402 width = max(width, min_pattern_metric_size);
403 width = min(width, max_pattern_metric_size);
406 const PatternBgMapKey key (width, height, bgc[0], bgc[1], shade);
407 PatternBgMap::iterator i;
408 if ((i = hb_pattern_cache.find (key)) != hb_pattern_cache.end()) {
411 // TODO flush pattern cache if it gets too large
413 Cairo::RefPtr<Cairo::Pattern> p = generate_meter_background (
414 height, width, bgc, shade, true);
416 hb_pattern_cache[key] = p;
424 FastMeter::set_hold_count (long val)
438 FastMeter::on_size_request (GtkRequisition* req)
440 if (orientation == Vertical) {
441 vertical_size_request (req);
443 horizontal_size_request (req);
448 FastMeter::vertical_size_request (GtkRequisition* req)
450 req->height = request_height;
451 req->height = max(req->height, min_pattern_metric_size);
452 req->height = min(req->height, max_pattern_metric_size);
455 req->width = request_width;
459 FastMeter::horizontal_size_request (GtkRequisition* req)
461 req->width = request_width;
462 req->width = max(req->width, min_pattern_metric_size);
463 req->width = min(req->width, max_pattern_metric_size);
466 req->height = request_height;
470 FastMeter::on_size_allocate (Gtk::Allocation &alloc)
472 if (orientation == Vertical) {
473 vertical_size_allocate (alloc);
475 horizontal_size_allocate (alloc);
481 FastMeter::vertical_size_allocate (Gtk::Allocation &alloc)
483 if (alloc.get_width() != request_width) {
484 alloc.set_width (request_width);
487 int h = alloc.get_height();
488 h = max (h, min_pattern_metric_size + 2);
489 h = min (h, max_pattern_metric_size + 2);
491 if (h != alloc.get_height()) {
492 alloc.set_height (h);
495 if (pixheight != h) {
496 fgpattern = request_vertical_meter (request_width, h, _clr, _stp, _styleflags);
497 bgpattern = request_vertical_background (request_width, h, highlight ? _bgh : _bgc, highlight);
499 pixwidth = request_width - 2;
502 DrawingArea::on_size_allocate (alloc);
506 FastMeter::horizontal_size_allocate (Gtk::Allocation &alloc)
508 if (alloc.get_height() != request_height) {
509 alloc.set_height (request_height);
512 int w = alloc.get_width();
513 w = max (w, min_pattern_metric_size + 2);
514 w = min (w, max_pattern_metric_size + 2);
516 if (w != alloc.get_width()) {
521 fgpattern = request_horizontal_meter (w, request_height, _clr, _stp, _styleflags);
522 bgpattern = request_horizontal_background (w, request_height, highlight ? _bgh : _bgc, highlight);
524 pixheight = request_height - 2;
527 DrawingArea::on_size_allocate (alloc);
531 FastMeter::on_expose_event (GdkEventExpose* ev)
533 if (orientation == Vertical) {
534 return vertical_expose (ev);
536 return horizontal_expose (ev);
541 FastMeter::vertical_expose (GdkEventExpose* ev)
543 Glib::RefPtr<Gdk::Window> win = get_window ();
545 GdkRectangle intersection;
546 GdkRectangle background;
548 cairo_t* cr = gdk_cairo_create (get_window ()->gobj());
550 cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
553 cairo_set_source_rgb (cr, 0, 0, 0); // black
554 rounded_rectangle (cr, 0, 0, pixwidth + 2, pixheight + 2, 2);
557 top_of_meter = (gint) floor (pixheight * current_level);
559 /* reset the height & origin of the rect that needs to show the pixbuf
562 pixrect.height = top_of_meter;
563 pixrect.y = 1 + pixheight - top_of_meter;
567 background.width = pixrect.width;
568 background.height = pixheight - top_of_meter;
570 if (gdk_rectangle_intersect (&background, &ev->area, &intersection)) {
571 cairo_set_source (cr, bgpattern->cobj());
572 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
576 if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersection)) {
577 // draw the part of the meter image that we need. the area we draw is bounded "in reverse" (top->bottom)
578 cairo_set_source (cr, fgpattern->cobj());
579 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
586 last_peak_rect.x = 1;
587 last_peak_rect.width = pixwidth;
588 last_peak_rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
589 if (bright_hold || (_styleflags & 2)) {
590 last_peak_rect.height = max(0, min(3, pixheight - last_peak_rect.y -1 ));
592 last_peak_rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
595 cairo_set_source (cr, fgpattern->cobj());
596 cairo_rectangle (cr, last_peak_rect.x, last_peak_rect.y, last_peak_rect.width, last_peak_rect.height);
598 if (bright_hold && !no_rgba_overlay) {
599 cairo_fill_preserve (cr);
600 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
605 last_peak_rect.width = 0;
606 last_peak_rect.height = 0;
615 FastMeter::horizontal_expose (GdkEventExpose* ev)
617 Glib::RefPtr<Gdk::Window> win = get_window ();
619 GdkRectangle intersection;
620 GdkRectangle background;
622 cairo_t* cr = gdk_cairo_create (get_window ()->gobj());
624 cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
627 cairo_set_source_rgb (cr, 0, 0, 0); // black
628 rounded_rectangle (cr, 0, 0, pixwidth + 2, pixheight + 2, 2);
631 right_of_meter = (gint) floor (pixwidth * current_level);
633 /* reset the height & origin of the rect that needs to show the pixbuf
636 pixrect.width = right_of_meter;
638 background.x = 1 + right_of_meter;
640 background.width = pixwidth - right_of_meter;
641 background.height = pixheight;
643 if (gdk_rectangle_intersect (&background, &ev->area, &intersection)) {
644 cairo_set_source (cr, bgpattern->cobj());
645 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
649 if (gdk_rectangle_intersect (&pixrect, &ev->area, &intersection)) {
650 cairo_set_source (cr, fgpattern->cobj());
651 cairo_rectangle (cr, intersection.x, intersection.y, intersection.width, intersection.height);
658 last_peak_rect.y = 1;
659 last_peak_rect.height = pixheight;
660 const int xpos = floor (pixwidth * current_peak);
662 last_peak_rect.width = min(4, xpos );
664 last_peak_rect.width = min(2, xpos );
666 last_peak_rect.x = 1 + max(0, xpos - last_peak_rect.width);
668 cairo_set_source (cr, fgpattern->cobj());
669 cairo_rectangle (cr, last_peak_rect.x, last_peak_rect.y, last_peak_rect.width, last_peak_rect.height);
671 if (bright_hold && !no_rgba_overlay) {
672 cairo_fill_preserve (cr);
673 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.3);
678 last_peak_rect.width = 0;
679 last_peak_rect.height = 0;
688 FastMeter::set (float lvl, float peak)
690 float old_level = current_level;
691 float old_peak = current_peak;
694 if (lvl >= current_peak) {
696 hold_state = hold_cnt;
699 if (hold_state > 0) {
700 if (--hold_state == 0) {
713 if (current_level == old_level && current_peak == old_peak && (hold_state == 0 || peak != -1)) {
717 Glib::RefPtr<Gdk::Window> win;
719 if ((win = get_window()) == 0) {
724 if (orientation == Vertical) {
725 queue_vertical_redraw (win, old_level);
727 queue_horizontal_redraw (win, old_level);
732 FastMeter::queue_vertical_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
736 gint new_top = (gint) floor (pixheight * current_level);
739 rect.width = pixwidth;
740 rect.height = new_top;
741 rect.y = 1 + pixheight - new_top;
743 if (current_level > old_level) {
744 /* colored/pixbuf got larger, just draw the new section */
745 /* rect.y stays where it is because of X coordinates */
746 /* height of invalidated area is between new.y (smaller) and old.y
748 X coordinates just make my brain hurt.
750 rect.height = pixrect.y - rect.y;
752 /* it got smaller, compute the difference */
753 /* rect.y becomes old.y (the smaller value) */
755 /* rect.height is the old.y (smaller) minus the new.y (larger)
757 rect.height = pixrect.height - rect.height;
760 GdkRegion* region = 0;
763 if (rect.height != 0) {
765 /* ok, first region to draw ... */
767 region = gdk_region_rectangle (&rect);
771 /* redraw the last place where the last peak hold bar was;
772 the next expose will draw the new one whether its part of
773 expose region or not.
776 if (last_peak_rect.width * last_peak_rect.height != 0) {
778 region = gdk_region_new ();
781 gdk_region_union_with_rect (region, &last_peak_rect);
784 if (hold_state && current_peak > 0) {
786 region = gdk_region_new ();
790 rect.y = max(1, 1 + pixheight - (gint) floor (pixheight * current_peak));
792 rect.height = max(0, min(4, pixheight - last_peak_rect.y -1 ));
794 rect.height = max(0, min(2, pixheight - last_peak_rect.y -1 ));
796 rect.width = pixwidth;
797 gdk_region_union_with_rect (region, &rect);
801 gdk_window_invalidate_region (win->gobj(), region, true);
804 gdk_region_destroy(region);
810 FastMeter::queue_horizontal_redraw (const Glib::RefPtr<Gdk::Window>& win, float old_level)
814 gint new_right = (gint) floor (pixwidth * current_level);
816 rect.height = pixheight;
819 if (current_level > old_level) {
820 rect.x = 1 + pixrect.width;
821 /* colored/pixbuf got larger, just draw the new section */
822 rect.width = new_right - pixrect.width;
824 /* it got smaller, compute the difference */
825 rect.x = 1 + new_right;
826 /* rect.height is the old.x (smaller) minus the new.x (larger) */
827 rect.width = pixrect.width - new_right;
830 GdkRegion* region = 0;
833 if (rect.height != 0) {
835 /* ok, first region to draw ... */
837 region = gdk_region_rectangle (&rect);
841 /* redraw the last place where the last peak hold bar was;
842 the next expose will draw the new one whether its part of
843 expose region or not.
846 if (last_peak_rect.width * last_peak_rect.height != 0) {
848 region = gdk_region_new ();
851 gdk_region_union_with_rect (region, &last_peak_rect);
854 if (hold_state && current_peak > 0) {
856 region = gdk_region_new ();
860 rect.height = pixheight;
861 const int xpos = floor (pixwidth * current_peak);
863 rect.width = min(4, xpos);
865 rect.width = min(2, xpos);
867 rect.x = 1 + max(0, xpos - rect.width);
868 gdk_region_union_with_rect (region, &rect);
872 gdk_window_invalidate_region (win->gobj(), region, true);
875 gdk_region_destroy(region);
881 FastMeter::set_highlight (bool onoff)
883 if (highlight == onoff) {
887 if (orientation == Vertical) {
888 bgpattern = request_vertical_background (pixwidth + 2, pixheight + 2, highlight ? _bgh : _bgc, highlight);
890 bgpattern = request_horizontal_background (pixwidth + 2, pixheight + 2, highlight ? _bgh : _bgc, highlight);