From 5e8adebac2899d60fffc53d4692bc4972abcf795 Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Sun, 3 Sep 2023 00:09:32 +0200 Subject: [PATCH] Adds compact buttons feature to create keypads. See test/buttons for an example. --- CHANGES.txt | 4 +++ FL/Fl_Button.H | 9 +++++++ fluid/Fl_Button_Type.cxx | 28 ++++++++++++++++++++ fluid/Fl_Button_Type.h | 4 +++ fluid/Fl_Widget_Type.cxx | 34 ++++++++++++++++++++++++ fluid/alignment_panel.cxx | 3 +++ fluid/alignment_panel.fl | 10 +++---- fluid/widget_panel.cxx | 8 +++++- fluid/widget_panel.fl | 7 ++++- fluid/widget_panel.h | 1 + src/Fl.cxx | 4 +-- src/Fl_Button.cxx | 56 +++++++++++++++++++++++++++++++++++---- src/fl_boxtype.cxx | 4 +-- test/buttons.cxx | 21 ++++++++++++++- test/inactive.fl | 34 +++++++++++++++--------- 15 files changed, 197 insertions(+), 30 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 010a8c5e6..037c18397 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -51,7 +51,11 @@ Changes in FLTK 1.4.0 Released: ??? ?? 2022 - New Fl_ICO_Image class to read Windows .ico icon files. - New classes Fl_SVG_File_Surface and Fl_EPS_File_Surface to save any FLTK graphics to SVG or EPS files, respectively. + - Fl_Button now supports a compact flag that visually groups closely set + buttons into keypads. - Fl_Tabs now supports close buttons for individual tabs. + - Fl_Tabs now support four different modes for handling an + overflowing number of tabs. - Windows platform: added support for using a manifest to set the application's level of DPI awareness (issue #309). - class Fl_Native_File_Chooser on the X11/Wayland platform relies on external diff --git a/FL/Fl_Button.H b/FL/Fl_Button.H index 9908b9742..31b71a416 100644 --- a/FL/Fl_Button.H +++ b/FL/Fl_Button.H @@ -79,6 +79,7 @@ class FL_EXPORT Fl_Button : public Fl_Widget { char value_; char oldval; uchar down_box_; + uchar compact_; protected: @@ -167,6 +168,14 @@ public: /// (for backwards compatibility) void down_color(unsigned c) {selection_color(c);} + + // handle flag for compact buttons, documentation in source code + void compact(uchar v); + + /// Return true if buttons are rendered as compact buttons. + /// \return 0 if compact mode is off, 1 if it is on + /// \see compact(bool) + uchar compact() { return compact_; } }; #endif diff --git a/fluid/Fl_Button_Type.cxx b/fluid/Fl_Button_Type.cxx index 53355b7c3..ee12c731a 100644 --- a/fluid/Fl_Button_Type.cxx +++ b/fluid/Fl_Button_Type.cxx @@ -25,6 +25,7 @@ #include "Fl_Button_Type.h" #include "Fd_Snap_Action.h" +#include "file.h" #include #include @@ -34,6 +35,9 @@ #include #include +#include + + // ---- Button Types --------------------------------------------------- MARK: - @@ -61,6 +65,30 @@ Fl_Widget *Fl_Button_Type::widget(int x, int y, int w, int h) { return new Fl_Button(x, y, w, h, "Button"); } +void Fl_Button_Type::write_properties(Fd_Project_Writer &f) { + Fl_Widget_Type::write_properties(f); + Fl_Button *btn = (Fl_Button*)o; + if (btn->compact()) { + f.write_string("compact"); + f.write_string("%d", btn->compact()); + } +} + +void Fl_Button_Type::read_property(Fd_Project_Reader &f, const char *c) { + Fl_Button *btn = (Fl_Button*)o; + if (!strcmp(c, "compact")) { + btn->compact((uchar)atol(f.read_word())); + } else { + Fl_Widget_Type::read_property(f, c); + } +} + +void Fl_Button_Type::copy_properties() { + Fl_Widget_Type::copy_properties(); + Fl_Button *s = (Fl_Button*)o, *d = (Fl_Button*)live_widget; + d->compact(s->compact()); +} + Fl_Button_Type Fl_Button_type; diff --git a/fluid/Fl_Button_Type.h b/fluid/Fl_Button_Type.h index 743c1fdd5..c731ce7e7 100644 --- a/fluid/Fl_Button_Type.h +++ b/fluid/Fl_Button_Type.h @@ -35,6 +35,10 @@ public: int is_button() const FL_OVERRIDE { return 1; } ID id() const FL_OVERRIDE { return ID_Button; } bool is_a(ID inID) const FL_OVERRIDE { return (inID==ID_Button) ? true : super::is_a(inID); } + void write_properties(Fd_Project_Writer &f) FL_OVERRIDE; + void read_property(Fd_Project_Reader &f, const char *) FL_OVERRIDE; + void copy_properties() FL_OVERRIDE; + }; extern Fl_Button_Type Fl_Button_type; diff --git a/fluid/Fl_Widget_Type.cxx b/fluid/Fl_Widget_Type.cxx index 7418e65e1..3eddc890f 100644 --- a/fluid/Fl_Widget_Type.cxx +++ b/fluid/Fl_Widget_Type.cxx @@ -1081,6 +1081,39 @@ void down_box_cb(Fl_Choice* i, void *v) { } } +void compact_cb(Fl_Light_Button* i, void* v) { + if (v == LOAD) { + uchar n; + if (current_widget->is_a(Fl_Type::ID_Button)) { + n = ((Fl_Button*)(current_widget->o))->compact(); + i->value(n); + i->show(); + } else { + i->hide(); + } + } else { + int mod = 0; + uchar n = (uchar)i->value(); + for (Fl_Type *o = Fl_Type::first; o; o = o->next) { + if (o->selected && o->is_a(Fl_Type::ID_Button)) { + Fl_Widget_Type* q = (Fl_Widget_Type*)o; + uchar v = ((Fl_Button*)(q->o))->compact(); + if (n != v) { + if (!mod) { + mod = 1; + undo_checkpoint(); + } + ((Fl_Button*)(q->o))->compact(n); + q->redraw(); + } + } + } + if (mod) set_modflag(1); + } +} + + + //////////////////////////////////////////////////////////////// Fl_Menu_Item whenmenu[] = { @@ -3039,6 +3072,7 @@ void Fl_Widget_Type::write_widget_code(Fd_Code_Writer& f) { if (b->down_box()) f.write_c("%s%s->down_box(FL_%s);\n", f.indent(), var, boxname(b->down_box())); if (b->value()) f.write_c("%s%s->value(1);\n", f.indent(), var); + if (b->compact()) f.write_c("%s%s->compact(%d);\n", f.indent(), var, b->compact()); } else if (is_a(Fl_Type::ID_Input_Choice)) { Fl_Input_Choice* b = (Fl_Input_Choice*)o; if (b->down_box()) f.write_c("%s%s->down_box(FL_%s);\n", f.indent(), var, diff --git a/fluid/alignment_panel.cxx b/fluid/alignment_panel.cxx index b3df61cfe..29f21fd4c 100644 --- a/fluid/alignment_panel.cxx +++ b/fluid/alignment_panel.cxx @@ -1239,18 +1239,21 @@ ped using octal notation `\\0123`. If this option is checked, Fluid will write\ { preset_choice[0] = new Fl_Button(85, 107, 78, 20, "Application"); preset_choice[0]->type(102); preset_choice[0]->value(1); + preset_choice[0]->compact(1); preset_choice[0]->selection_color(FL_DARK2); preset_choice[0]->labelsize(11); preset_choice[0]->callback((Fl_Callback*)edit_layout_preset_cb, (void*)(0)); } // Fl_Button* preset_choice[0] { preset_choice[1] = new Fl_Button(163, 107, 79, 20, "Dialog"); preset_choice[1]->type(102); + preset_choice[1]->compact(1); preset_choice[1]->selection_color(FL_DARK2); preset_choice[1]->labelsize(11); preset_choice[1]->callback((Fl_Callback*)edit_layout_preset_cb, (void*)(1)); } // Fl_Button* preset_choice[1] { preset_choice[2] = new Fl_Button(242, 107, 78, 20, "Toolbox"); preset_choice[2]->type(102); + preset_choice[2]->compact(1); preset_choice[2]->selection_color(FL_DARK2); preset_choice[2]->labelsize(11); preset_choice[2]->callback((Fl_Callback*)edit_layout_preset_cb, (void*)(2)); diff --git a/fluid/alignment_panel.fl b/fluid/alignment_panel.fl index d6b9325b4..845d7b7f6 100644 --- a/fluid/alignment_panel.fl +++ b/fluid/alignment_panel.fl @@ -107,7 +107,7 @@ decl {void scheme_cb(Fl_Scheme_Choice *, void *);} {public local Function {make_settings_window()} {open } { Fl_Window settings_window { - label {FLUID Settings} open selected + label {FLUID Settings} open xywh {423 204 340 580} type Double align 80 non_modal visible } { Fl_Tabs w_settings_tabs { @@ -115,7 +115,7 @@ Function {make_settings_window()} {open xywh {10 10 320 530} selection_color 12 labelsize 11 labelcolor 255 } { Fl_Group {} { - label General open + label General open selected image {icons/general_64.png} compress_image 1 xywh {10 60 320 480} labelsize 11 code0 {o->image()->scale(36, 24);} } { @@ -471,19 +471,19 @@ g_layout_list.update_dialogs();} label Application user_data 0 user_data_type long callback edit_layout_preset_cb - xywh {85 107 78 20} type Radio value 1 selection_color 45 labelsize 11 + xywh {85 107 78 20} type Radio value 1 selection_color 45 labelsize 11 compact 1 } Fl_Button {preset_choice[1]} { label Dialog user_data 1 user_data_type long callback edit_layout_preset_cb - xywh {163 107 79 20} type Radio selection_color 45 labelsize 11 + xywh {163 107 79 20} type Radio selection_color 45 labelsize 11 compact 1 } Fl_Button {preset_choice[2]} { label Toolbox user_data 2 user_data_type long callback edit_layout_preset_cb - xywh {242 107 78 20} type Radio selection_color 45 labelsize 11 + xywh {242 107 78 20} type Radio selection_color 45 labelsize 11 compact 1 } } Fl_Box {} { diff --git a/fluid/widget_panel.cxx b/fluid/widget_panel.cxx index 4bb22b797..12976268b 100644 --- a/fluid/widget_panel.cxx +++ b/fluid/widget_panel.cxx @@ -803,10 +803,16 @@ sized to fit the container."); } // Fl_Menu_Button* o o->end(); } // Fl_Group* o - { Fl_Box* o = new Fl_Box(95, 140, 300, 40); + { Fl_Box* o = new Fl_Box(95, 165, 300, 40); o->labelsize(11); Fl_Group::current()->resizable(o); } // Fl_Box* o + { Fl_Light_Button* o = new Fl_Light_Button(95, 140, 90, 20, "Compact"); + o->tooltip("use compact box types for closely set buttons"); + o->selection_color((Fl_Color)1); + o->labelsize(11); + o->callback((Fl_Callback*)compact_cb); + } // Fl_Light_Button* o o->end(); } // Fl_Group* o { Fl_Group* o = new Fl_Group(10, 30, 400, 330, "C++"); diff --git a/fluid/widget_panel.fl b/fluid/widget_panel.fl index 1ebbaa7d6..139dbe716 100644 --- a/fluid/widget_panel.fl +++ b/fluid/widget_panel.fl @@ -655,7 +655,12 @@ Use Ctrl-J for newlines.} xywh {95 285 310 20} labelfont 1 labelsize 11 textsize } {} } Fl_Box {} { - xywh {95 140 300 40} labelsize 11 resizable + xywh {95 165 300 40} labelsize 11 resizable + } + Fl_Light_Button {} { + label Compact + callback compact_cb + tooltip {use compact box types for closely set buttons} xywh {95 140 90 20} selection_color 1 labelsize 11 } } Fl_Group {} { diff --git a/fluid/widget_panel.h b/fluid/widget_panel.h index ae15583b0..196e10632 100644 --- a/fluid/widget_panel.h +++ b/fluid/widget_panel.h @@ -113,6 +113,7 @@ extern void textsize_cb(Fl_Value_Input*, void*); extern void textcolor_cb(Fl_Button*, void*); extern Fl_Button *w_textcolor; extern void textcolor_menu_cb(Fl_Menu_Button*, void*); +extern void compact_cb(Fl_Light_Button*, void*); extern void subclass_cb(Fl_Input*, void*); extern void subtype_cb(Fl_Choice*, void*); extern void name_cb(Fl_Input*, void*); diff --git a/src/Fl.cxx b/src/Fl.cxx index c08eb4930..e204a1cd5 100644 --- a/src/Fl.cxx +++ b/src/Fl.cxx @@ -1143,12 +1143,12 @@ void fl_throw_focus(Fl_Widget *o) { // the inactive widget and all inactive parent groups. // // This is used to send FL_SHORTCUT events to the Fl::belowmouse() widget -// in case the target widget itself is inactive_r(). In this case the event +// in case the target widget itself is !active_r(). In this case the event // is sent to the first active_r() parent. // // This prevents sending events to inactive widgets that might get the // input focus otherwise. The search is fast and light and avoids calling -// inactive_r() multiple times. +// !active_r() multiple times. // See STR #3216. // // Returns: first active_r() widget "above" the widget wi or NULL if diff --git a/src/Fl_Button.cxx b/src/Fl_Button.cxx index dec92d483..b15b33fbf 100644 --- a/src/Fl_Button.cxx +++ b/src/Fl_Button.cxx @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -67,7 +68,29 @@ void Fl_Button::setonly() { // set this radio button on, turn others off void Fl_Button::draw() { if (type() == FL_HIDDEN_BUTTON) return; Fl_Color col = value() ? selection_color() : color(); - draw_box(value() ? (down_box()?down_box():fl_down(box())) : box(), col); + Fl_Boxtype bt = value() ? (down_box()?down_box():fl_down(box())) : box(); + if (compact_ && parent()) { + Fl_Widget *p = parent(); + int px, py, pw = p->w(), ph = p->h(); + if (p->as_window()) { px = 0; py = 0; } else { px = p->x(); py = p->y(); } + fl_push_clip(x(), y(), w(), h()); + draw_box(bt, px, py, pw, ph, col); + fl_pop_clip(); + const int hh = 5, ww = 5; + Fl_Color divider_color = fl_gray_ramp(FL_NUM_GRAY/3); + if (!active_r()) + divider_color = fl_inactive(divider_color); + if (x()+w() != px+pw) { + fl_color(divider_color); + fl_yxline(x()+w()-1, y()+hh, y()+h()-1-hh); + } + if (y()+h() != py+ph) { + fl_color(divider_color); + fl_xyline(x()+ww, y()+h()-1, x()+w()-1-ww); + } + } else { + draw_box(bt, col); + } draw_backdrop(); if (labeltype() == FL_NORMAL_LABEL && value()) { Fl_Color c = labelcolor(); @@ -215,11 +238,14 @@ void Fl_Button::key_release_timeout(void *d) \param[in] L widget label, default is no label */ Fl_Button::Fl_Button(int X, int Y, int W, int H, const char *L) -: Fl_Widget(X,Y,W,H,L) { +: Fl_Widget(X,Y,W,H,L), + shortcut_(0), + value_(0), + oldval(0), + down_box_(FL_NO_BOX), + compact_(0) +{ box(FL_UP_BOX); - down_box(FL_NO_BOX); - value_ = oldval = 0; - shortcut_ = 0; set_flag(SHORTCUT_LABEL); } @@ -249,3 +275,23 @@ Fl_Toggle_Button::Fl_Toggle_Button(int X,int Y,int W,int H,const char *L) { type(FL_TOGGLE_BUTTON); } + +/** + Decide if buttons should be rendered in compact mode. + + \image html compact_buttons_gtk.png "compact button keypad using GTK+ Scheme" + \image latex compact_buttons_gtk.png "compact button keypad using GTK+ Scheme" width=4cm + + \image html compact_buttons_gleam.png "compact buttons in Gleam" + \image latex compact_buttons_gleam.png "compact buttons in Gleam" width=4cm + + In compact mode, the button's surrounding border is altered to visually signal + that multiple buttons are functionally linked together. To ensure the correct + rendering of buttons in compact mode, all buttons must be part of the same + group, positioned close to each other, and aligned with the edges of the + group. Any button outlines not in contact with the parent group's outline + will be displayed as separators. + + \param[in] v switch compact mode on (1) or off (0) + */ +void Fl_Button::compact(uchar v) { compact_ = v; } diff --git a/src/fl_boxtype.cxx b/src/fl_boxtype.cxx index 96d016fe1..4f446c8ec 100644 --- a/src/fl_boxtype.cxx +++ b/src/fl_boxtype.cxx @@ -64,7 +64,7 @@ const uchar *fl_gray_ramp() {return (draw_it_active?active_ramp:inactive_ramp)-' Gets the drawing color to be used for the background of a box. This method is only useful inside box drawing code. It returns the - color to be used, either fl_inactive(c) if the widget is inactive_r() + color to be used, either fl_inactive(c) if the widget is !active_r() or \p c otherwise. */ Fl_Color Fl::box_color(Fl_Color c) { @@ -84,7 +84,7 @@ Fl_Color Fl::box_color(Fl_Color c) { This method is only useful inside box drawing code. Whenever a box is drawn with one of the standard box drawing methods, a static variable is set depending on the widget's current state - if the widget is - inactive_r() then the internal variable is false (0), otherwise it + !active_r() then the internal variable is false (0), otherwise it is true (1). This is faster than calling Fl_Widget::active_r() because the state is cached. diff --git a/test/buttons.cxx b/test/buttons.cxx index e93f4dc8e..f5a945994 100644 --- a/test/buttons.cxx +++ b/test/buttons.cxx @@ -25,7 +25,7 @@ #include int main(int argc, char **argv) { - Fl_Window *window = new Fl_Window(320, 170); + Fl_Window *window = new Fl_Window(420, 170); Fl_Button *b1 = new Fl_Button(10, 10, 130, 30, "Fl_Button"); b1->tooltip("Fl_Button"); Fl_Button *b2 = new Fl_Return_Button(150, 10, 160, 30, "Fl_Return_Button"); @@ -39,6 +39,25 @@ int main(int argc, char **argv) { Fl_Button *b6 = new Fl_Check_Button(150, 90, 160, 30, "Fl_Check_Button"); b6->tooltip("Fl_Check_Button"); + Fl_Group *keypad = new Fl_Group(320, 10, 90, 120); + Fl_Button *kp[11]; + kp[7] = new Fl_Button(320, 10, 30, 30, "7"); + kp[8] = new Fl_Button(350, 10, 30, 30, "8"); + kp[9] = new Fl_Button(380, 10, 30, 30, "9"); + kp[4] = new Fl_Button(320, 40, 30, 30, "4"); + kp[5] = new Fl_Button(350, 40, 30, 30, "5"); + kp[6] = new Fl_Button(380, 40, 30, 30, "6"); + kp[1] = new Fl_Button(320, 70, 30, 30, "1"); + kp[2] = new Fl_Button(350, 70, 30, 30, "2"); + kp[3] = new Fl_Button(380, 70, 30, 30, "3"); + kp[0] = new Fl_Button(320, 100, 60, 30, "0"); + kp[10] = new Fl_Button(380, 100, 30, 30, "."); + for (int i=0; i<11; i++) { + kp[i]->compact(1); + kp[i]->selection_color(FL_SELECTION_COLOR); + } + keypad->end(); + // Add a scheme choice widget for easier testing. Position the widget at // the right window border so the menu popup doesn't cover the check boxes etc. Fl_Scheme_Choice *scheme_choice = new Fl_Scheme_Choice(180, 130, 130, 30, "Active FLTK Scheme:"); diff --git a/test/inactive.fl b/test/inactive.fl index 0038a4fb3..c19b50a20 100644 --- a/test/inactive.fl +++ b/test/inactive.fl @@ -13,44 +13,52 @@ Function {} {open xywh {462 303 420 369} type Double resizable visible } { Fl_Group the_group { - label {activate()/deactivate() called on this Fl_Group} open selected + label {activate()/deactivate() called on this Fl_Group} open xywh {25 25 375 295} box ENGRAVED_FRAME align 17 resizable } { Fl_Button {} { label button - xywh {50 50 105 25} + xywh {50 50 105 20} } Fl_Light_Button {} { label {light button} - xywh {50 80 105 25} value 1 align 16 + xywh {50 75 105 20} value 1 align 16 + } + Fl_Group {} {open + xywh {50 100 105 20} + } { + Fl_Button {} { + label On selected + xywh {50 100 52 20} type Radio value 1 compact 1 + } + Fl_Button {} { + label Off selected + xywh {102 100 53 20} type Radio compact 1 + } } Fl_Group {} { label {Child group} open - xywh {50 130 105 125} box DOWN_FRAME + xywh {50 150 105 105} box DOWN_FRAME } { Fl_Check_Button {} { label red - xywh {54 172 97 20} type Radio down_box DIAMOND_DOWN_BOX selection_color 1 labelcolor 1 + xywh {54 192 97 20} type Radio down_box DIAMOND_DOWN_BOX selection_color 1 labelcolor 1 } Fl_Check_Button {} { label green - xywh {54 192 97 20} type Radio down_box DIAMOND_DOWN_BOX selection_color 2 labelcolor 2 + xywh {54 212 97 20} type Radio down_box DIAMOND_DOWN_BOX selection_color 2 labelcolor 2 } Fl_Check_Button {} { label blue - xywh {54 212 97 20} type Radio down_box DIAMOND_DOWN_BOX selection_color 4 labelcolor 4 - } - Fl_Check_Button {} { - label white - xywh {54 232 97 20} type Radio down_box DIAMOND_DOWN_BOX selection_color 55 labelcolor 55 + xywh {54 232 97 20} type Radio down_box DIAMOND_DOWN_BOX selection_color 4 labelcolor 4 } Fl_Check_Button {} { label check - xywh {54 132 97 20} down_box DOWN_BOX + xywh {54 152 97 20} down_box DOWN_BOX } Fl_Round_Button {} { label round - xywh {54 152 97 20} down_box ROUND_DOWN_BOX + xywh {54 172 97 20} down_box ROUND_DOWN_BOX } } Fl_Slider {} {