From e4d8b941025fcdd232e748227634529f752381b7 Mon Sep 17 00:00:00 2001 From: Albrecht Schlosser Date: Sun, 7 Nov 2021 00:20:44 +0100 Subject: [PATCH] Add fl_draw_check() to draw better check marks (issue #68) This new function can and should be used to draw check marks in widgets that need it, e.g. Fl_Check_Browser (issue #68) and Fl_Check_Button. --- FL/fl_draw.H | 13 +++-- src/Fl_Check_Browser.cxx | 15 +++--- src/Fl_Light_Button.cxx | 43 ++++++++++------ src/fl_draw.cxx | 108 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+), 28 deletions(-) diff --git a/FL/fl_draw.H b/FL/fl_draw.H index 9e2e4b3dd..3d3a54400 100644 --- a/FL/fl_draw.H +++ b/FL/fl_draw.H @@ -1,7 +1,7 @@ // // Portable drawing function header file for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2020 by Bill Spitzak and others. +// Copyright 1998-2021 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this @@ -22,8 +22,9 @@ #ifndef fl_draw_H #define fl_draw_H -#include // for the color names -#include // for fl_graphics_driver + Fl_Region +#include // color names +#include // fl_graphics_driver + Fl_Region +#include // Image class... class Fl_Image; @@ -749,10 +750,16 @@ FL_EXPORT void fl_draw(const char* str, int x, int y, int w, int h, Fl_Image* img=0, int draw_symbols = 1); // boxtypes: + FL_EXPORT void fl_frame(const char* s, int x, int y, int w, int h); FL_EXPORT void fl_frame2(const char* s, int x, int y, int w, int h); FL_EXPORT void fl_draw_box(Fl_Boxtype, int x, int y, int w, int h, Fl_Color); +// basic GUI objects (check marks, arrows, more to come ...): + +// Draw a check mark in the given color inside the bounding box bb. +void fl_draw_check(Fl_Rect bb, Fl_Color col); + // images: /** diff --git a/src/Fl_Check_Browser.cxx b/src/Fl_Check_Browser.cxx index 3b327312c..a05c4e409 100644 --- a/src/Fl_Check_Browser.cxx +++ b/src/Fl_Check_Browser.cxx @@ -184,20 +184,17 @@ void Fl_Check_Browser::item_draw(void *v, int X, int Y, int, int) const { int cy = Y + (tsize + 1 - CHECK_SIZE) / 2; X += 2; + // draw the check mark box (always) fl_color(active_r() ? FL_FOREGROUND_COLOR : fl_inactive(FL_FOREGROUND_COLOR)); fl_loop(X, cy, X, cy + CHECK_SIZE, X + CHECK_SIZE, cy + CHECK_SIZE, X + CHECK_SIZE, cy); + + // draw the check mark if (i->checked) { - int tx = X + 3; - int tw = CHECK_SIZE - 4; - int d1 = tw / 3; - int d2 = tw - d1; - int ty = cy + (CHECK_SIZE + d2) / 2 - d1 - 2; - for (int n = 0; n < 3; n++, ty++) { - fl_line(tx, ty, tx + d1, ty + d1); - fl_line(tx + d1, ty + d1, tx + tw - 1, ty + d1 - d2 + 1); - } + fl_draw_check(Fl_Rect(X + 1, cy + 1, CHECK_SIZE - 1, CHECK_SIZE - 1), fl_color()); } + + // draw the item text fl_font(textfont(), tsize); if (i->selected) { col = fl_contrast(col, selection_color()); diff --git a/src/Fl_Light_Button.cxx b/src/Fl_Light_Button.cxx index 4de85bc5f..71064d9fb 100644 --- a/src/Fl_Light_Button.cxx +++ b/src/Fl_Light_Button.cxx @@ -32,11 +32,15 @@ void Fl_Light_Button::draw() { Fl_Color col = value() ? (active_r() ? selection_color() : fl_inactive(selection_color())) : color(); - int W = labelsize(); + int W = labelsize(); // check mark box size + if (W > 25) W = 25; // limit box size int bx = Fl::box_dx(box()); // box frame width - int dx = bx + 2; // relative position of check mark etc. + int dx = bx + 2; // relative position of check mark box int dy = (h() - W) / 2; // neg. offset o.k. for vertical centering int lx = 0; // relative label position (STR #3237) + int cx = x() + dx; // check mark box x-position + int cy = y() + dy; // check mark box y-position + int cw = 0; // check mark box width and height if (down_box()) { // draw other down_box() styles: @@ -46,22 +50,17 @@ void Fl_Light_Button::draw() { case _FL_PLASTIC_DOWN_BOX : case _FL_PLASTIC_UP_BOX : // Check box... - draw_box(down_box(), x()+dx, y()+dy, W, W, FL_BACKGROUND2_COLOR); + draw_box(down_box(), cx, cy, W, W, FL_BACKGROUND2_COLOR); if (value()) { + // Check mark... if (Fl::is_scheme("gtk+")) { - fl_color(FL_SELECTION_COLOR); - } else { - fl_color(col); - } - int tx = x() + dx + 3; - int tw = W - 6; - int d1 = tw/3; - int d2 = tw-d1; - int ty = y() + dy + (W+d2)/2-d1-2; - for (int n = 0; n < 3; n++, ty++) { - fl_line(tx, ty, tx+d1, ty+d1); - fl_line(tx+d1, ty+d1, tx+tw-1, ty+d1-d2+1); + col = FL_SELECTION_COLOR; } + // Calculate box position and size + cx += Fl::box_dx(down_box()); + cy += Fl::box_dy(down_box()); + cw = W - Fl::box_dw(down_box()); + fl_draw_check(Fl_Rect(cx, cy, cw, cw), col); } break; case _FL_ROUND_DOWN_BOX : @@ -149,7 +148,19 @@ int Fl_Light_Button::handle(int event) { /** Creates a new Fl_Light_Button widget using the given position, size, and label string. -

The destructor deletes the check button. + + The default box type is \p FL_UP_BOX and the default down box + type down_box() is \p FL_NO_BOX (0). + + The selection_color() sets the color of the "light". + Default is FL_YELLOW. + + The default label alignment is \p 'FL_ALIGN_LEFT|FL_ALIGN_INSIDE' so + the label is drawn inside the button area right of the "light". + + \note Do not change the default box types of Fl_Light_Button. The + box types determine how the button is drawn. If you change the + down_box() type the drawing behavior is undefined. */ Fl_Light_Button::Fl_Light_Button(int X, int Y, int W, int H, const char* l) : Fl_Button(X, Y, W, H, l) { diff --git a/src/fl_draw.cxx b/src/fl_draw.cxx index ba33db96e..efa7a14a6 100644 --- a/src/fl_draw.cxx +++ b/src/fl_draw.cxx @@ -475,3 +475,111 @@ float fl_override_scale() { void fl_restore_scale(float s) { fl_graphics_driver->restore_scale(s); } + +/** + Draw a check mark inside the given bounding box. + + The check mark is allowed to fill the entire box but the algorithm used + makes sure that a 1-pixel border is kept free if the box is large enough. + You need to calculate margins for box borders etc. yourself. + + The check mark size is limited (minimum and maximum size) and the check + mark is always centered in the given box. + + \note If the box is too small (bad GUI design) the check mark will be drawn + over the box borders. This is intentional for better user experience. + Otherwise users might not be able to recognize if a box is checked. + + The size limits are implementation details and may be changed at any time. + + \param[in] X,Y top left corner of the bounding box + \param[in] W,H width and height of the bounding box + + \since 1.4.0 +*/ + +void fl_draw_check(Fl_Rect bb, Fl_Color col) { + + const int md = 6; // max. d1 value: 3 * md + 1 pixels wide + int tx = bb.x(); + int ty = bb.y(); + int tw = bb.w(); + int th = bb.h(); + int lh = 3; // line height 3 means 4 pixels + int d1, d2; + + Fl_Color saved_color = fl_color(); + + // make sure there's a free 1-pixel border if the area is large enough + if (tw > 10) { + tx++; + tw -= 2; + } + if (th > 10) { + ty++; + th -= 2; + } + // calculate d1, the width and height of the left part of the check mark + d1 = tw / 3; + d2 = 2 * d1; + if (d1 > md) { + d1 = md; + d2 = 2 * d1; + } + // make sure the height fits + if (d2 + lh + 1 > th) { + d2 = th - lh - 1; + d1 = (d2+1) / 2; + } + // check minimal size (box too small) + if (d1 < 2) { + d1 = 2; + d2 = 4; + } + // reduce line height (width) for small sizes + if (d1 < 4) + lh = 2; + + tw = d1 + d2 + 1; // total width + th = d2 + lh + 1; // total height + + tx = bb.x() + (bb.w() - tw + 1) / 2; // x position (centered) + ty = bb.y() + (bb.h() - th + 1) / 2; // y position (centered) + + // Set DEBUG_FRAME to 1 - 3 for debugging (0 otherwise) + // Bit 1 set: draws a green background (the entire given box) + // Bit 2 set: draws a red frame around the check mark (the bounding box) + // The background (1) can be used to test correct positioning by the widget code + +#define DEBUG_FRAME (3) +#if (DEBUG_FRAME) + if (DEBUG_FRAME & 1) { // 1 = background + fl_color(0x88dd8800); + fl_rectf(bb.x(), bb.y(), bb.w(), bb.h()); + } + if (DEBUG_FRAME & 2) { // 2 = bounding box + fl_color(0xff000000); + fl_rect(tx, ty, tw, th); + } +#endif + + // draw the check mark + + fl_color(col); + + fl_begin_complex_polygon(); + + ty += d2 - d1; // upper border of check mark: left to right + fl_vertex(tx, ty); + fl_vertex(tx + d1, ty + d1); + fl_vertex(tx + d1 + d2, ty + d1 - d2); + ty += lh; // lower border of check mark: right to left + fl_vertex(tx + d1 + d2, ty + d1 - d2); + fl_vertex(tx + d1, ty + d1); + fl_vertex(tx, ty); + + fl_end_complex_polygon(); + + fl_color(saved_color); + +} // fl_draw_check()