From 1d769c0f5caad21e3a9b0b9f2b16f1e8c5132a0e Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Mon, 2 Aug 2021 11:54:09 -0600 Subject: [PATCH 01/46] Simple button group class --- include/emp/prefab/ButtonGroup.hpp | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 include/emp/prefab/ButtonGroup.hpp diff --git a/include/emp/prefab/ButtonGroup.hpp b/include/emp/prefab/ButtonGroup.hpp new file mode 100644 index 0000000000..b72d714bac --- /dev/null +++ b/include/emp/prefab/ButtonGroup.hpp @@ -0,0 +1,29 @@ +#ifndef EMP_BUTTON_GROUP_HPP +#define EMP_BUTTON_GROUP_HPP + +namespace emp::prefab { + + class ButtonGroup : public emp::web::Div { + + public: + ButtonGroup(const std::string & in_id="") : Div(in_id) { + SetAttr("class", "btn-group"); + } + + }; + + // /** + // * Overridden stream operator causes a button group's contents to be + // * appended to existing one rather than having nested button groups. Other + // * streamed components should be nested like usual. + // * @param btn_group a button group + // */ + // template<> ButtonGroup & ButtonGroup::operator<<(ButtonGroup && btn_group) { + // static_cast
(*this) << btn_group.Children(); + // return (*this); + // } +} + + + +#endif From be39c0c34a71baa6018db259c4c7d37ce8bd549b Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Mon, 2 Aug 2021 14:43:48 -0600 Subject: [PATCH 02/46] Idea for concatenating button groups --- include/emp/prefab/ButtonGroup.hpp | 32 ++++++++++++++++-------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/include/emp/prefab/ButtonGroup.hpp b/include/emp/prefab/ButtonGroup.hpp index b72d714bac..c66db20cb6 100644 --- a/include/emp/prefab/ButtonGroup.hpp +++ b/include/emp/prefab/ButtonGroup.hpp @@ -5,25 +5,27 @@ namespace emp::prefab { class ButtonGroup : public emp::web::Div { - public: - ButtonGroup(const std::string & in_id="") : Div(in_id) { + protected: + ButtonGroup(emp::web::DivInfo * info_ref) : Div(info_ref) { SetAttr("class", "btn-group"); } + public: + ButtonGroup(const std::string & in_id="") + : ButtonGroup(new emp::web::DivInfo(in_id)) { ; } + + /** + * Plus operator joins two button groups into one. Useful for joining + * multiple default constructed groups together if it makes sense. + * LHS ButtonGroup takes RHS ButtonGroup's children. + * + * @param btn_group a button group + */ + ButtonGroup & operator+(const ButtonGroup & rhs) { + *this << rhs.Children(); + return (*this); + } }; - - // /** - // * Overridden stream operator causes a button group's contents to be - // * appended to existing one rather than having nested button groups. Other - // * streamed components should be nested like usual. - // * @param btn_group a button group - // */ - // template<> ButtonGroup & ButtonGroup::operator<<(ButtonGroup && btn_group) { - // static_cast
(*this) << btn_group.Children(); - // return (*this); - // } } - - #endif From 1a160a4b6f29e846a8c70936209d4d4ff1d4c63e Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Mon, 2 Aug 2021 14:45:33 -0600 Subject: [PATCH 03/46] Toggle button setup for play/pause button --- include/emp/prefab/ToggleButton.hpp | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 include/emp/prefab/ToggleButton.hpp diff --git a/include/emp/prefab/ToggleButton.hpp b/include/emp/prefab/ToggleButton.hpp new file mode 100644 index 0000000000..189baff6a6 --- /dev/null +++ b/include/emp/prefab/ToggleButton.hpp @@ -0,0 +1,33 @@ +#ifndef EMP_TOGGLE_BUTTON_HPP +#define EMP_TOGGLE_BUTTON_HPP + +namespace emp::prefab { + + namespace internal { + + class ToggleButtonInfo : public DivInfo { + bool active; + public: + ToggleButtonInfo(std::string & in_id) : DivInfo(in_id) {;} + } + } + + class ToggleButton : public ButtonGroup { + + protected: + ToggleButton(DivInfo * info_ref) : Div(info_ref) { + + } + + public: + ToggleButton(std::string & in_id) : Div(new DivInfo(in_id)) { ; } + + operator bool() const + + } + }; + + +} + +#endif From ce2d52e9cc2feb51b87adc43d7dc748744d03d44 Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Tue, 3 Aug 2021 10:26:09 -0600 Subject: [PATCH 04/46] Some setup for the toggle button --- include/emp/prefab/ButtonGroup.hpp | 6 +-- include/emp/prefab/ToggleButton.hpp | 84 ++++++++++++++++++++++++++--- include/emp/web/Div.hpp | 1 + 3 files changed, 81 insertions(+), 10 deletions(-) diff --git a/include/emp/prefab/ButtonGroup.hpp b/include/emp/prefab/ButtonGroup.hpp index c66db20cb6..b378a8e493 100644 --- a/include/emp/prefab/ButtonGroup.hpp +++ b/include/emp/prefab/ButtonGroup.hpp @@ -3,16 +3,16 @@ namespace emp::prefab { - class ButtonGroup : public emp::web::Div { + class ButtonGroup : public web::Div { protected: - ButtonGroup(emp::web::DivInfo * info_ref) : Div(info_ref) { + ButtonGroup(web::internal::DivInfo * info_ref) : web::Div(info_ref) { SetAttr("class", "btn-group"); } public: ButtonGroup(const std::string & in_id="") - : ButtonGroup(new emp::web::DivInfo(in_id)) { ; } + : ButtonGroup(new web::internal::DivInfo(in_id)) { ; } /** * Plus operator joins two button groups into one. Useful for joining diff --git a/include/emp/prefab/ToggleButton.hpp b/include/emp/prefab/ToggleButton.hpp index 189baff6a6..34dc412fbd 100644 --- a/include/emp/prefab/ToggleButton.hpp +++ b/include/emp/prefab/ToggleButton.hpp @@ -1,29 +1,99 @@ #ifndef EMP_TOGGLE_BUTTON_HPP #define EMP_TOGGLE_BUTTON_HPP +#include "emp/web/Element.hpp" +#include "emp/web/Input.hpp" +#include "emp/tools/string_utils.hpp" + namespace emp::prefab { namespace internal { - class ToggleButtonInfo : public DivInfo { + class ToggleButtonInfo : public web::internal::DivInfo { bool active; + public: - ToggleButtonInfo(std::string & in_id) : DivInfo(in_id) {;} + ToggleButtonInfo(const std::string & in_id) : + web::internal::DivInfo(in_id) {;} + + bool IsActive() const { + return active; + } + + void SetActive() { + active = true; + } + + void SetInactive() { + active = false; + } } } class ToggleButton : public ButtonGroup { - protected: - ToggleButton(DivInfo * info_ref) : Div(info_ref) { + /** + * Get shared info pointer, cast to ToggleButton-specific type. + * + * @return cast pointer + */ + internal::ToggleButtonInfo * Info() { + return dynamic_cast(info); + } + /** + * Get shared info pointer, cast to const ToggleButton-specific type. + * + * @return cast pointer + */ + const internal::ToggleButtonInfo * Info() const { + return dynamic_cast(info); } - public: - ToggleButton(std::string & in_id) : Div(new DivInfo(in_id)) { ; } + emp::web::Input activeRadioBtn; + emp::web::Input inactiveRadioBtn; + + emp::web::Element activeLabel; + emp::web::Element inactiveLabel; + + protected: + ToggleButton(web::internal::DivInfo * info_ref) + : ButtonGroup(info_ref), + activeRadioBtn( + [](){;}, "radio", "", emp::to_string(GetID(),"_active_radio") + ), inactiveRadioBtn( + [](){;}, "radio", "", emp::to_string(GetID(),"_inactive_radio"), false, true + ), activeLabel( + "label", emp::to_string(GetID(),"_active_label") + ), inactiveLabel( + "label", emp::to_string(GetID(),"_inactive_label") + ) { + *this << activeRadioBtn; + *this << activeLabel; + *this << inactiveRadioBtn; + *this << inactiveLabel; + activeRadioBtn.SetAttr( + "class", "btn-check", "name", emp::to_string(GetID(),"_radios"), + "autocomplete", "off" + ); + inactiveRadioBtn.SetAttr( + "class", "btn-check", "name", emp::to_string(GetID(),"_radios"), + "autocomplete", "off" + ); + activeLabel.SetAttr( + "class", "btn", "for", emp::to_string(GetID(),"_active_label") + ); + inactiveLabel.SetAttr( + "class", "btn", "for", emp::to_string(GetID(),"_inactive_label") + ); + } - operator bool() const + public: + ToggleButton(std::string & in_id="") + : ButtonGroup(new internal::ToggleButtonInfo(in_id)) { ; } + bool IsActive() const { + return Info()->IsActive(); } }; diff --git a/include/emp/web/Div.hpp b/include/emp/web/Div.hpp index eb0a410c5a..e94e547544 100644 --- a/include/emp/web/Div.hpp +++ b/include/emp/web/Div.hpp @@ -305,6 +305,7 @@ namespace web { // Get a properly cast version of info. internal::DivInfo * Info() { return (internal::DivInfo *) info; } const internal::DivInfo * Info() const { return (internal::DivInfo *) info; } + Div(internal::DivInfo * in_info) : WidgetFacet(in_info) { ; } public: Div(const std::string & in_name="") : WidgetFacet(in_name) { From 8992466348180bf6d69c9261eed39d70d04231b3 Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Tue, 3 Aug 2021 11:42:14 -0600 Subject: [PATCH 05/46] Fix switch styling in Bootstrap v5.0 --- include/emp/prefab/ToggleSwitch.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/emp/prefab/ToggleSwitch.hpp b/include/emp/prefab/ToggleSwitch.hpp index 31f964b4dc..9afede9ef5 100644 --- a/include/emp/prefab/ToggleSwitch.hpp +++ b/include/emp/prefab/ToggleSwitch.hpp @@ -38,14 +38,14 @@ namespace prefab { if (label != "") { label_element << label; } - checkbox.SetAttr("class", "custom-control-input"); - this->SetAttr("class", "custom-control custom-switch"); + checkbox.SetAttr("class", "custom-control-input form-check-input"); + this->SetAttr("class", "custom-control custom-switch form-check form-switch"); this->SetCSS( "clear", "none", "display", "inline" ); label_element.SetAttr( - "class", "custom-control-label", + "class", "custom-control-label form-check-label", "for", checkbox.GetID() ); } From 22cb37f38e5271fb6feedf5f79760497f754dd7c Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Tue, 3 Aug 2021 17:57:58 -0600 Subject: [PATCH 06/46] Get toggle button working --- include/emp/prefab/ButtonGroup.hpp | 10 +- include/emp/prefab/ToggleButton.hpp | 103 ------------------ include/emp/prefab/ToggleButtonGroup.hpp | 128 +++++++++++++++++++++++ include/emp/web/Div.hpp | 5 + 4 files changed, 137 insertions(+), 109 deletions(-) delete mode 100644 include/emp/prefab/ToggleButton.hpp create mode 100644 include/emp/prefab/ToggleButtonGroup.hpp diff --git a/include/emp/prefab/ButtonGroup.hpp b/include/emp/prefab/ButtonGroup.hpp index b378a8e493..607c62d041 100644 --- a/include/emp/prefab/ButtonGroup.hpp +++ b/include/emp/prefab/ButtonGroup.hpp @@ -15,14 +15,12 @@ namespace emp::prefab { : ButtonGroup(new web::internal::DivInfo(in_id)) { ; } /** - * Plus operator joins two button groups into one. Useful for joining - * multiple default constructed groups together if it makes sense. - * LHS ButtonGroup takes RHS ButtonGroup's children. - * + * A function useful for joining two button groups together into one unit. * @param btn_group a button group */ - ButtonGroup & operator+(const ButtonGroup & rhs) { - *this << rhs.Children(); + ButtonGroup & TakeChildren(ButtonGroup & btn_group) { + *this << btn_group.Children(); + btn_group.Clear(); return (*this); } }; diff --git a/include/emp/prefab/ToggleButton.hpp b/include/emp/prefab/ToggleButton.hpp deleted file mode 100644 index 34dc412fbd..0000000000 --- a/include/emp/prefab/ToggleButton.hpp +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef EMP_TOGGLE_BUTTON_HPP -#define EMP_TOGGLE_BUTTON_HPP - -#include "emp/web/Element.hpp" -#include "emp/web/Input.hpp" -#include "emp/tools/string_utils.hpp" - -namespace emp::prefab { - - namespace internal { - - class ToggleButtonInfo : public web::internal::DivInfo { - bool active; - - public: - ToggleButtonInfo(const std::string & in_id) : - web::internal::DivInfo(in_id) {;} - - bool IsActive() const { - return active; - } - - void SetActive() { - active = true; - } - - void SetInactive() { - active = false; - } - } - } - - class ToggleButton : public ButtonGroup { - - /** - * Get shared info pointer, cast to ToggleButton-specific type. - * - * @return cast pointer - */ - internal::ToggleButtonInfo * Info() { - return dynamic_cast(info); - } - - /** - * Get shared info pointer, cast to const ToggleButton-specific type. - * - * @return cast pointer - */ - const internal::ToggleButtonInfo * Info() const { - return dynamic_cast(info); - } - - emp::web::Input activeRadioBtn; - emp::web::Input inactiveRadioBtn; - - emp::web::Element activeLabel; - emp::web::Element inactiveLabel; - - protected: - ToggleButton(web::internal::DivInfo * info_ref) - : ButtonGroup(info_ref), - activeRadioBtn( - [](){;}, "radio", "", emp::to_string(GetID(),"_active_radio") - ), inactiveRadioBtn( - [](){;}, "radio", "", emp::to_string(GetID(),"_inactive_radio"), false, true - ), activeLabel( - "label", emp::to_string(GetID(),"_active_label") - ), inactiveLabel( - "label", emp::to_string(GetID(),"_inactive_label") - ) { - *this << activeRadioBtn; - *this << activeLabel; - *this << inactiveRadioBtn; - *this << inactiveLabel; - activeRadioBtn.SetAttr( - "class", "btn-check", "name", emp::to_string(GetID(),"_radios"), - "autocomplete", "off" - ); - inactiveRadioBtn.SetAttr( - "class", "btn-check", "name", emp::to_string(GetID(),"_radios"), - "autocomplete", "off" - ); - activeLabel.SetAttr( - "class", "btn", "for", emp::to_string(GetID(),"_active_label") - ); - inactiveLabel.SetAttr( - "class", "btn", "for", emp::to_string(GetID(),"_inactive_label") - ); - } - - public: - ToggleButton(std::string & in_id="") - : ButtonGroup(new internal::ToggleButtonInfo(in_id)) { ; } - - bool IsActive() const { - return Info()->IsActive(); - } - }; - - -} - -#endif diff --git a/include/emp/prefab/ToggleButtonGroup.hpp b/include/emp/prefab/ToggleButtonGroup.hpp new file mode 100644 index 0000000000..bc2e6435b6 --- /dev/null +++ b/include/emp/prefab/ToggleButtonGroup.hpp @@ -0,0 +1,128 @@ +#ifndef EMP_TOGGLE_BUTTON_GROUP_HPP +#define EMP_TOGGLE_BUTTON_GROUP_HPP + +#include "emp/tools/string_utils.hpp" +#include "emp/prefab/FontAwesomeIcon.hpp" +#include "emp/web/Element.hpp" +#include "emp/web/Input.hpp" + + +namespace emp::prefab { + + namespace internal { + + class ToggleButtonGroupInfo : public web::internal::DivInfo { + bool active; + + public: + ToggleButtonGroupInfo(const std::string & in_id) + : web::internal::DivInfo(in_id), active(false) { ; } + + bool IsActive() const { + return active; + } + + void SetActive(const bool & val) { + active = val; + } + }; + } + + class ToggleButtonGroup : public ButtonGroup { + + /** + * Get shared info pointer, cast to ToggleButton-specific type. + * + * @return cast pointer + */ + internal::ToggleButtonGroupInfo * Info() { + return dynamic_cast(info); + } + + /** + * Get shared info pointer, cast to const ToggleButton-specific type. + * + * @return cast pointer + */ + const internal::ToggleButtonGroupInfo * Info() const { + return dynamic_cast(info); + } + + web::Element activate_label; + web::Element deactivate_label; + + protected: + /** + * @param activate_indicator a string or FontAwesomeIcon to indicate the + * active state of this toggle + * @param activate_indicator a string or FontAwesomeIcon to indicate the + * inactive state of this toggle + */ + template + ToggleButtonGroup( + L1_TYPE & activate_indicator, + L2_TYPE & deactivate_indicator, + const std::string & activate_style, + const std::string & deactivate_style, + const bool & cassette_style, + const bool & grayout, + web::internal::DivInfo * info_ref + ) : ButtonGroup(info_ref), + activate_label("label", emp::to_string(GetID(), "_activate")), + deactivate_label("label", emp::to_string(GetID(), "_deactivate")) { + AddAttr( + "class", "btn-group-toggle", "data-toggle", "buttons" + ); + *this << activate_label; + *this << deactivate_label; + + activate_label.AddAttr( + "class", "btn", + "class", emp::to_string("btn-outline-", activate_style) + ); + web::Input activate_radio( + [&, info=this->Info()](std::string) { info->SetActive(true); }, + "radio", "", emp::to_string(GetID(), "_activate_radio"), + false, false + ); + activate_label << activate_radio; + activate_label << activate_indicator; + + deactivate_label.AddAttr( + "class", "active", + "class", "btn", + "class", emp::to_string("btn-outline-", deactivate_style) + ); + web::Input deactivate_radio( + [&, info=this->Info()](std::string) { info->SetActive(false); }, + "radio", "", emp::to_string(GetID(), "_deactivate_radio"), + false, true + ); + deactivate_label << deactivate_radio; + deactivate_label << deactivate_indicator; + } + + public: + template + ToggleButtonGroup( + L1_TYPE & activate_indicator, + L2_TYPE & deactivate_indicator, + const std::string & activate_style="success", + const std::string & deactivate_style="warning", + const bool & cassette_style=true, + const bool & grayout=false, + const std::string & in_id="" + ) : ToggleButtonGroup( + activate_indicator, deactivate_indicator, + activate_style, deactivate_style, + cassette_style, grayout, + new internal::ToggleButtonGroupInfo(in_id) + ) { ; } + + bool IsActive() const { + return Info()->IsActive(); + } + }; +} + +#endif diff --git a/include/emp/web/Div.hpp b/include/emp/web/Div.hpp index e94e547544..de49f20de4 100644 --- a/include/emp/web/Div.hpp +++ b/include/emp/web/Div.hpp @@ -35,6 +35,10 @@ #include "Widget.hpp" #include "init.hpp" +namespace emp::prefab { + class ButtonGroup; +} + namespace emp { namespace web { @@ -52,6 +56,7 @@ namespace web { class TableInfo; class DivInfo : public internal::WidgetInfo { friend Element; friend Div; friend TableInfo; + friend prefab::ButtonGroup; protected: double scroll_top; ///< Where should div scroll to? (0.0 to 1.0) emp::vector m_children; ///< Widgets contained in this one. From 033ac5956ad6ba3df0c982cff8951675ba9ea066 Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Tue, 3 Aug 2021 18:27:35 -0600 Subject: [PATCH 07/46] Some documentation --- include/emp/prefab/ToggleButtonGroup.hpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/include/emp/prefab/ToggleButtonGroup.hpp b/include/emp/prefab/ToggleButtonGroup.hpp index bc2e6435b6..f209011add 100644 --- a/include/emp/prefab/ToggleButtonGroup.hpp +++ b/include/emp/prefab/ToggleButtonGroup.hpp @@ -53,10 +53,18 @@ namespace emp::prefab { protected: /** - * @param activate_indicator a string or FontAwesomeIcon to indicate the - * active state of this toggle - * @param activate_indicator a string or FontAwesomeIcon to indicate the - * inactive state of this toggle + * @param activate_indicator a string, FontAwesomeIcon or other component + * indicating that the first button actives this toggle + * @param deactivate_indicator a string, FontAwesomeIcon or other component + * indicating that the second button deactives this toggle + * @param activate_style a bootstrap style (primary, secondary, etc) for + * the first button + * @param deactivate_style a bootstrap style (primary, secondary, etc) for + * the second button + * @param cassette_style whether the toggle should display in cassette style + * (both buttons visible) or do a swap on toggle (one button visible) + * @param grayout in cassette mode, whether buttons should be + * grayed out to further emphasize the current state */ template ToggleButtonGroup( From 5db885f8912bb5503778fd01f7c6f3cb7f7cfb0b Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Tue, 3 Aug 2021 19:11:26 -0600 Subject: [PATCH 08/46] Add styling for imbedded toggle button groups --- include/emp/prefab/DefaultPrefabStyles.less | 53 +++++++++++++++++---- include/emp/prefab/ToggleButtonGroup.hpp | 5 ++ 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/include/emp/prefab/DefaultPrefabStyles.less b/include/emp/prefab/DefaultPrefabStyles.less index 1f0a521a53..ced322d64c 100644 --- a/include/emp/prefab/DefaultPrefabStyles.less +++ b/include/emp/prefab/DefaultPrefabStyles.less @@ -23,7 +23,8 @@ @BootstrapDeviceLarge: 992px; @BootstrapDeviceExtraLarge: 1200px; -/* Glyphicon Toggle */ +// ---------------- Glyphicon Toggle ---------------- + .collapse_toggle[aria-expanded=true] .fa-angle-double-down { display: none; } @@ -49,7 +50,7 @@ float: right; } -/* Card */ +// ------------------ Card ------------------ .card{ margin-bottom: 10px; } @@ -69,7 +70,7 @@ text-align: left; } -/* Comment Box */ +// ---------------- Comment Box ---------------- .commentbox_triangle { width: 0; height: 0; @@ -90,7 +91,7 @@ display: none; } -/* Loading modal */ +// ---------------- Loading Modal ----------------- .bd-example-modal-lg .modal-dialog { display: table; position: relative; @@ -103,7 +104,8 @@ border: none; } -/* Config Panel */ +// ---------------- Config Panel ---------------- + .config_main { display: flex; flex-flow: column wrap; @@ -125,7 +127,7 @@ justify-content: flex-end; } -/* Mobile adjustments */ +// ---------------- Mobile Adjustments ---------------- // TODO: it would be much better to avoid media break points, // but need to solve the auto-flow dense/min-width issue (see .value_view). @@ -138,7 +140,7 @@ } } -/* Value Box, Control and Display */ +// ----------- Value Box, Control and Display ----------- .value_box { display: grid; grid-template-columns: auto 1fr; @@ -180,7 +182,7 @@ flex: 1 1 auto; } &[type=number] { - width: 100px; + flex: 0 1 100px; } &[type=text] { flex: 1 1 auto; @@ -188,8 +190,7 @@ } } -// A value description should span from the first column -// to the last (entire row) +// A value description should span from the first column to the last (whole row) .value_description { grid-column: 1 / -1; } @@ -198,3 +199,35 @@ .excluded { display: none; } + +// -------------- ToggleButtonGroup -------------- + +// Overrides nested toggel button group styles when using "hide_active" to turn +// cassette style buttons into play/pause style and "grayout" to add additional +// visual cues + +div:not(.btn-group) > div.btn-group.btn-group-toggle.hide_inactive > .btn { + border-radius: .25rem; +} + +.btn-group > .hide_inactive.btn-group-toggle:first-child > .btn { + border-top-left-radius: .25rem; + border-bottom-left-radius: .25rem; +} + +.btn-group > .hide_inactive.btn-group-toggle:last-child > .btn { + border-top-right-radius: .25rem; + border-bottom-right-radius: .25rem; +} + +.btn-group > .hide_inactive.btn-group-toggle > .btn { + margin-left: unset; +} + +.btn-group-toggle.hide_inactive > label.active { + display: none; +} + +.btn-group-toggle.grayout > label:not(.active):not(:hover) { + filter: grayscale(1); +} diff --git a/include/emp/prefab/ToggleButtonGroup.hpp b/include/emp/prefab/ToggleButtonGroup.hpp index f209011add..e61406cf4f 100644 --- a/include/emp/prefab/ToggleButtonGroup.hpp +++ b/include/emp/prefab/ToggleButtonGroup.hpp @@ -83,6 +83,11 @@ namespace emp::prefab { ); *this << activate_label; *this << deactivate_label; + if (!cassette_style) { + AddAttr("class", "hide_inactive"); + } else if (grayout) { + AddAttr("class", "grayout"); + } activate_label.AddAttr( "class", "btn", From 8f649f3818cc92d4469582c91d1923bf2b4770b9 Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Tue, 3 Aug 2021 19:24:16 -0600 Subject: [PATCH 09/46] Input works on radio buttons now --- include/emp/web/Input.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/emp/web/Input.hpp b/include/emp/web/Input.hpp index cc36369e28..766cd4cf42 100644 --- a/include/emp/web/Input.hpp +++ b/include/emp/web/Input.hpp @@ -261,7 +261,7 @@ namespace web { Info()->callback_id = JSWrap( std::function( [b_info](std::string new_val){b_info->DoChange(new_val);} ) ); Info()->onchange_info = emp::to_string("emp.Callback(", Info()->callback_id, ", ['checkbox', 'radio'].includes(this.type) ? this.checked.toString() : this.value);"); // Allows user to set the checkbox to start out on/checked - if (in_type.compare("checkbox") == 0 && is_checked){ + if ((in_type.compare("checkbox") == 0 || in_type.compare("radio") == 0) && is_checked) { this->SetAttr("checked", "true"); } } From 6ebd4968455e6f13edcb7241cb9777adc5bfd64c Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Mon, 9 Aug 2021 10:38:53 -0700 Subject: [PATCH 10/46] No need for components as members --- include/emp/prefab/ToggleButtonGroup.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/emp/prefab/ToggleButtonGroup.hpp b/include/emp/prefab/ToggleButtonGroup.hpp index e61406cf4f..8afa72e9b3 100644 --- a/include/emp/prefab/ToggleButtonGroup.hpp +++ b/include/emp/prefab/ToggleButtonGroup.hpp @@ -48,9 +48,6 @@ namespace emp::prefab { return dynamic_cast(info); } - web::Element activate_label; - web::Element deactivate_label; - protected: /** * @param activate_indicator a string, FontAwesomeIcon or other component @@ -75,14 +72,17 @@ namespace emp::prefab { const bool & cassette_style, const bool & grayout, web::internal::DivInfo * info_ref - ) : ButtonGroup(info_ref), - activate_label("label", emp::to_string(GetID(), "_activate")), - deactivate_label("label", emp::to_string(GetID(), "_deactivate")) { + ) : ButtonGroup(info_ref) + { AddAttr( "class", "btn-group-toggle", "data-toggle", "buttons" ); + + web::Element activate_label("label", emp::to_string(GetID(), "_activate")); + web::Element deactivate_label("label", emp::to_string(GetID(), "_deactivate")); *this << activate_label; *this << deactivate_label; + if (!cassette_style) { AddAttr("class", "hide_inactive"); } else if (grayout) { From 9f5a3fdd1315260d0a11f29af6e4471739a46c18 Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Mon, 9 Aug 2021 16:03:28 -0700 Subject: [PATCH 11/46] Control panel basics --- include/emp/prefab/ControlPanel.hpp | 63 +++++++++++++++++++++ include/emp/prefab/DefaultPrefabStyles.less | 8 ++- include/emp/prefab/ToggleButtonGroup.hpp | 18 +++--- include/emp/web/Div.hpp | 2 + 4 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 include/emp/prefab/ControlPanel.hpp diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp new file mode 100644 index 0000000000..fb2c680248 --- /dev/null +++ b/include/emp/prefab/ControlPanel.hpp @@ -0,0 +1,63 @@ +#ifndef EMP_CONTROL_PANEL_HPP +#define EMP_CONTROL_PANEL_HPP + +#include "emp/base/optional.hpp" +#include "emp/prefab/ButtonGroup.hpp" +#include "emp/prefab/ToggleButtonGroup.hpp" +#include "emp/prefab/FontAwesomeIcon.hpp" +#include "emp/tools/string_utils.hpp" +#include "emp/web/Div.hpp" + +namespace emp::prefab { + + class EndGroup {}; + + class ControlPanel : public web::Div { + + ToggleButtonGroup play_pause_toggle; + optional active_group; + + protected: + ControlPanel(web::internal::DivInfo * in_info) : web::Div(in_info), + play_pause_toggle(ToggleButtonGroup{ + FontAwesomeIcon{"fa-play"}, FontAwesomeIcon{"fa-pause"}, + "success", "warning", + true, false, emp::to_string(GetID(), "_play_pause") + }) + { + AddAttr( + "class", "btn-toolbar", + "class", "space_groups", + "role", "toolbar", + "aria-label", "Toolbar with simulation controls" + ); + active_group = ButtonGroup{}; + *active_group << play_pause_toggle; + static_cast
(*this) << *active_group; + } + + public: + ControlPanel(const std::string & in_id="") + : ControlPanel(new web::internal::DivInfo(in_id)) { ; } + + template + emp::prefab::ControlPanel & operator<<(IN_TYPE && in_val) { + if constexpr(std::is_same::value) { + std::cout << "BG in!" << std::endl; + active_group = in_val; + static_cast
(*this) << *active_group; + } else if constexpr(std::is_same::value) { + active_group = {}; + } else { + if(!active_group.has_value()) { + active_group = ButtonGroup{}; + static_cast
(*this) << *active_group; + } + *active_group << std::forward(in_val); + } + return (*this); + } + }; +} + +#endif diff --git a/include/emp/prefab/DefaultPrefabStyles.less b/include/emp/prefab/DefaultPrefabStyles.less index ced322d64c..da95ae0ad5 100644 --- a/include/emp/prefab/DefaultPrefabStyles.less +++ b/include/emp/prefab/DefaultPrefabStyles.less @@ -200,9 +200,15 @@ display: none; } +// -------------- ToolBar -------------- + +.btn-toolbar.space_groups > :not(:last-child) { + margin-right: .5rem; +} + // -------------- ToggleButtonGroup -------------- -// Overrides nested toggel button group styles when using "hide_active" to turn +// Overrides nested toggle button group styles when using "hide_active" to turn // cassette style buttons into play/pause style and "grayout" to add additional // visual cues diff --git a/include/emp/prefab/ToggleButtonGroup.hpp b/include/emp/prefab/ToggleButtonGroup.hpp index 8afa72e9b3..a5b47aeeb9 100644 --- a/include/emp/prefab/ToggleButtonGroup.hpp +++ b/include/emp/prefab/ToggleButtonGroup.hpp @@ -2,7 +2,6 @@ #define EMP_TOGGLE_BUTTON_GROUP_HPP #include "emp/tools/string_utils.hpp" -#include "emp/prefab/FontAwesomeIcon.hpp" #include "emp/web/Element.hpp" #include "emp/web/Input.hpp" @@ -53,7 +52,7 @@ namespace emp::prefab { * @param activate_indicator a string, FontAwesomeIcon or other component * indicating that the first button actives this toggle * @param deactivate_indicator a string, FontAwesomeIcon or other component - * indicating that the second button deactives this toggle + * indicating that the second button deactivates this toggle * @param activate_style a bootstrap style (primary, secondary, etc) for * the first button * @param deactivate_style a bootstrap style (primary, secondary, etc) for @@ -65,8 +64,8 @@ namespace emp::prefab { */ template ToggleButtonGroup( - L1_TYPE & activate_indicator, - L2_TYPE & deactivate_indicator, + L1_TYPE && activate_indicator, + L2_TYPE && deactivate_indicator, const std::string & activate_style, const std::string & deactivate_style, const bool & cassette_style, @@ -99,7 +98,7 @@ namespace emp::prefab { false, false ); activate_label << activate_radio; - activate_label << activate_indicator; + activate_label << std::forward(activate_indicator); deactivate_label.AddAttr( "class", "active", @@ -112,21 +111,22 @@ namespace emp::prefab { false, true ); deactivate_label << deactivate_radio; - deactivate_label << deactivate_indicator; + deactivate_label << std::forward(deactivate_indicator); } public: template ToggleButtonGroup( - L1_TYPE & activate_indicator, - L2_TYPE & deactivate_indicator, + L1_TYPE && activate_indicator, + L2_TYPE && deactivate_indicator, const std::string & activate_style="success", const std::string & deactivate_style="warning", const bool & cassette_style=true, const bool & grayout=false, const std::string & in_id="" ) : ToggleButtonGroup( - activate_indicator, deactivate_indicator, + std::forward(activate_indicator), + std::forward(deactivate_indicator), activate_style, deactivate_style, cassette_style, grayout, new internal::ToggleButtonGroupInfo(in_id) diff --git a/include/emp/web/Div.hpp b/include/emp/web/Div.hpp index de49f20de4..a7a66d899e 100644 --- a/include/emp/web/Div.hpp +++ b/include/emp/web/Div.hpp @@ -37,6 +37,7 @@ namespace emp::prefab { class ButtonGroup; + class ControlPanel; } namespace emp { @@ -57,6 +58,7 @@ namespace web { class DivInfo : public internal::WidgetInfo { friend Element; friend Div; friend TableInfo; friend prefab::ButtonGroup; + friend prefab::ControlPanel; protected: double scroll_top; ///< Where should div scroll to? (0.0 to 1.0) emp::vector m_children; ///< Widgets contained in this one. From 875640b59ba3bb92c1a43d608d1d07a3e1b4f457 Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Tue, 10 Aug 2021 12:30:10 -0700 Subject: [PATCH 12/46] Allow grouping buttons in ControlPanel --- include/emp/prefab/ControlPanel.hpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index fb2c680248..e5f75dca2d 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -10,8 +10,6 @@ namespace emp::prefab { - class EndGroup {}; - class ControlPanel : public web::Div { ToggleButtonGroup play_pause_toggle; @@ -41,13 +39,13 @@ namespace emp::prefab { : ControlPanel(new web::internal::DivInfo(in_id)) { ; } template - emp::prefab::ControlPanel & operator<<(IN_TYPE && in_val) { - if constexpr(std::is_same::value) { - std::cout << "BG in!" << std::endl; - active_group = in_val; + ControlPanel & operator<<(IN_TYPE && in_val) { + // Took soooo long to figure out but if in_val is a r-value ref + // IN_TYPE is just the TYPE. If it's l-value then it's TYPE &. + // std::decay and forward help handle both. + if constexpr(std::is_same::type, ButtonGroup>::value) { + active_group.emplace(std::forward(in_val)); static_cast
(*this) << *active_group; - } else if constexpr(std::is_same::value) { - active_group = {}; } else { if(!active_group.has_value()) { active_group = ButtonGroup{}; From 7408f8fce418dfc461051ecbf2cbe6beda5c8301 Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Wed, 18 Aug 2021 13:18:50 -0700 Subject: [PATCH 13/46] No more control panel for now --- include/emp/prefab/ControlPanel.hpp | 61 ----------------------------- 1 file changed, 61 deletions(-) delete mode 100644 include/emp/prefab/ControlPanel.hpp diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp deleted file mode 100644 index e5f75dca2d..0000000000 --- a/include/emp/prefab/ControlPanel.hpp +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef EMP_CONTROL_PANEL_HPP -#define EMP_CONTROL_PANEL_HPP - -#include "emp/base/optional.hpp" -#include "emp/prefab/ButtonGroup.hpp" -#include "emp/prefab/ToggleButtonGroup.hpp" -#include "emp/prefab/FontAwesomeIcon.hpp" -#include "emp/tools/string_utils.hpp" -#include "emp/web/Div.hpp" - -namespace emp::prefab { - - class ControlPanel : public web::Div { - - ToggleButtonGroup play_pause_toggle; - optional active_group; - - protected: - ControlPanel(web::internal::DivInfo * in_info) : web::Div(in_info), - play_pause_toggle(ToggleButtonGroup{ - FontAwesomeIcon{"fa-play"}, FontAwesomeIcon{"fa-pause"}, - "success", "warning", - true, false, emp::to_string(GetID(), "_play_pause") - }) - { - AddAttr( - "class", "btn-toolbar", - "class", "space_groups", - "role", "toolbar", - "aria-label", "Toolbar with simulation controls" - ); - active_group = ButtonGroup{}; - *active_group << play_pause_toggle; - static_cast
(*this) << *active_group; - } - - public: - ControlPanel(const std::string & in_id="") - : ControlPanel(new web::internal::DivInfo(in_id)) { ; } - - template - ControlPanel & operator<<(IN_TYPE && in_val) { - // Took soooo long to figure out but if in_val is a r-value ref - // IN_TYPE is just the TYPE. If it's l-value then it's TYPE &. - // std::decay and forward help handle both. - if constexpr(std::is_same::type, ButtonGroup>::value) { - active_group.emplace(std::forward(in_val)); - static_cast
(*this) << *active_group; - } else { - if(!active_group.has_value()) { - active_group = ButtonGroup{}; - static_cast
(*this) << *active_group; - } - *active_group << std::forward(in_val); - } - return (*this); - } - }; -} - -#endif From 2149befd563f8d3e132c0a72034e332912c7990c Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Thu, 19 Aug 2021 14:23:59 -0700 Subject: [PATCH 14/46] Ironing out --- include/emp/prefab/ButtonGroup.hpp | 2 + include/emp/prefab/ControlPanel.hpp | 215 +++++++++++++++++++++++ include/emp/prefab/ToggleButtonGroup.hpp | 32 +++- 3 files changed, 245 insertions(+), 4 deletions(-) create mode 100644 include/emp/prefab/ControlPanel.hpp diff --git a/include/emp/prefab/ButtonGroup.hpp b/include/emp/prefab/ButtonGroup.hpp index 607c62d041..477a47e218 100644 --- a/include/emp/prefab/ButtonGroup.hpp +++ b/include/emp/prefab/ButtonGroup.hpp @@ -1,6 +1,8 @@ #ifndef EMP_BUTTON_GROUP_HPP #define EMP_BUTTON_GROUP_HPP +#include "emp/web/Div.hpp" + namespace emp::prefab { class ButtonGroup : public web::Div { diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp new file mode 100644 index 0000000000..166d105b55 --- /dev/null +++ b/include/emp/prefab/ControlPanel.hpp @@ -0,0 +1,215 @@ +#ifndef EMP_CONTROL_PANEL_HPP +#define EMP_CONTROL_PANEL_HPP + +#include "emp/base/optional.hpp" +#include "emp/base/vector.hpp" + +#include "emp/prefab/ButtonGroup.hpp" +#include "emp/prefab/FontAwesomeIcon.hpp" +#include "emp/prefab/ToggleButtonGroup.hpp" + +#include "emp/tools/string_utils.hpp" + +#include "emp/web/Animate.hpp" +#include "emp/web/Element.hpp" +#include "emp/web/Div.hpp" + +namespace emp::prefab { + + namespace internal { + + class ControlPanelInfo : public web::internal::DivInfo { + + std::string refresh_unit = "MILLISECONDS"; + std::map refresh_rates{ + {"MILLISECONDS", 100}, {"FRAMES", 5} + }; + + emp::vector refresh_list; + std::function simulation; + + public: + ControlPanelInfo(const std::string & in_id="") + : DivInfo(in_id), simulation([](){ ; }) { ; } + + void SetSimulation(std::function & sim) { + simulation = sim; + } + + const std::function & GetSimulation() const { + return simulation; + } + + void SetUnit(const std::string & unit) { + refresh_unit = unit; + } + + const std::string & GetUnit() const { + return refresh_unit; + } + + void SetRate(const int & rate) { + refresh_rates[refresh_unit] = rate; + } + + const int & GetRate() { + return refresh_rates[refresh_unit]; + } + + emp::vector & GetRefreshList() { + return refresh_list; + } + }; + } + + class ControlPanel : public web::Div { + + /** + * Get shared info pointer, cast to ControlPanel-specific type. + * @return cast pointer + */ + internal::ControlPanelInfo * Info() { + return dynamic_cast(info); + } + + /** + * Get shared info pointer, cast to const ControlPanel-specific type. + * @return cast pointer + */ + const internal::ControlPanelInfo * Info() const { + return dynamic_cast(info); + } + + ToggleButtonGroup toggle_run; + Div button_line; + web::Button step; + + protected: + ControlPanel( + const std::string & refresh_mode, + const int & refresh_rate, + web::internal::DivInfo * in_info + ) : web::Div(in_info), + toggle_run{ + FontAwesomeIcon{"fa-play"}, FontAwesomeIcon{"fa-pause"}, + "success", "warning", + true, false, + emp::to_string(GetID(), "_run_toggle") + }, + button_line(ButtonGroup{emp::to_string(GetID(), "_core")}), + step{[](){ ; }, "", emp::to_string(GetID(), "_step")} + { + AddAttr( + "class", "btn-toolbar", + "class", "space_groups", + "role", "toolbar", + "aria-label", "Toolbar with simulation controls" + ); + SetUnit(refresh_mode); + SetRate(refresh_rate); + + step.SetAttr("class", "success"); + step << FontAwesomeIcon{"fa-step-forward"}; + button_line << toggle_run; + static_cast
(*this) << button_line; + + AddAnimation(GetID(), + [elapsed_milliseconds = 0, + &run_sim=GetSimulation(), + &refresh_list=Info()->GetRefreshList(), + &unit=GetUnit(), + &rate=GetRate()] + (const web::Animate & anim) mutable { + + // Run the simulation function every frame + run_sim(); + + if (unit == "FRAMES") { + // Units of frames means redraw every # of frames + if (anim.GetFrameCount() % rate) { + for (emp::web::Widget & wid : refresh_list) { + wid.Redraw(); + } + } + } else { + // Units of milliseconds means redraw every # of milliseconds + elapsed_milliseconds += anim.GetStepTime(); + if (elapsed_milliseconds > rate) { + elapsed_milliseconds -= rate; + for (emp::web::Widget & wid : refresh_list) { + wid.Redraw(); + } + } + // see ReadoutPanel for explanation of this pattern + if (elapsed_milliseconds > rate) elapsed_milliseconds = 0; + } + } + ); + + toggle_run.SetCallback( + [&anim=Animate(GetID()), step=web::Button(step)] + (bool set_active) mutable { + if (set_active) { + anim.Start(); + step.SetAttr("disabled", true); + } else { + anim.Stop(); + step.SetAttr("disabled", true); + } + } + ); + + step.SetCallback([&anim=Animate(GetID())]() { + anim.Step(); + }); + } + + public: + ControlPanel( + const std::string & refresh_mode, + const int & refresh_rate, + const std::string & in_id="") + : ControlPanel( + refresh_mode, + refresh_rate, + new internal::ControlPanelInfo(in_id) + ) { ; } + + void SetSimulation(std::function & sim) { + Info()->SetSimulation(sim); + } + + const std::function & GetSimulation() const { return Info()->GetSimulation(); } + + void SetUnit(const std::string & units) { Info()->SetUnit(units); } + + const std::string & GetUnit() const { return Info()->GetUnit(); } + + void SetRate(const int & val) { Info()->SetRate(val); } + + const int & GetRate() { return Info()->GetRate(); } + + void AddToRefreshList(Widget & area) { + Info()->GetRefreshList().push_back(area); + } + + template + ControlPanel & operator<<(IN_TYPE && in_val) { + // Took soooo long to figure out but if in_val is a r-value ref + // IN_TYPE is just the TYPE. If it's l-value then it's TYPE &. + // std::decay and forward help handle both. + if constexpr(std::is_same::type, web::Button>::value || + std::is_same::type, ToggleButtonGroup>::value) { + button_line << std::forward(in_val); + } else if constexpr(std::is_same::type, ButtonGroup>::value) { + button_line = std::forward(in_val); + static_cast
(*this) << button_line; + } else { + static_cast
(*this) << std::forward(in_val); + } + return (*this); + } + }; +} + +#endif diff --git a/include/emp/prefab/ToggleButtonGroup.hpp b/include/emp/prefab/ToggleButtonGroup.hpp index a5b47aeeb9..3699e7e2d6 100644 --- a/include/emp/prefab/ToggleButtonGroup.hpp +++ b/include/emp/prefab/ToggleButtonGroup.hpp @@ -1,21 +1,33 @@ #ifndef EMP_TOGGLE_BUTTON_GROUP_HPP #define EMP_TOGGLE_BUTTON_GROUP_HPP + #include "emp/tools/string_utils.hpp" +#include "emp/web/Div.hpp" #include "emp/web/Element.hpp" #include "emp/web/Input.hpp" - +#include "emp/prefab/ButtonGroup.hpp" namespace emp::prefab { namespace internal { + using on_toggle_t = std::function; class ToggleButtonGroupInfo : public web::internal::DivInfo { + on_toggle_t callback; bool active; public: ToggleButtonGroupInfo(const std::string & in_id) - : web::internal::DivInfo(in_id), active(false) { ; } + : web::internal::DivInfo(in_id), callback([](bool){ ; }), active(false) { ; } + + void UpdateCallback(const on_toggle_t & cb) { + callback = cb; + } + + const on_toggle_t & GetCallback() const { + return callback; + } bool IsActive() const { return active; @@ -28,6 +40,7 @@ namespace emp::prefab { } class ToggleButtonGroup : public ButtonGroup { + using on_toggle_t = internal::on_toggle_t; /** * Get shared info pointer, cast to ToggleButton-specific type. @@ -93,7 +106,10 @@ namespace emp::prefab { "class", emp::to_string("btn-outline-", activate_style) ); web::Input activate_radio( - [&, info=this->Info()](std::string) { info->SetActive(true); }, + [&, info=this->Info()](std::string) { + info->SetActive(true); + info->GetCallback()(true); + }, "radio", "", emp::to_string(GetID(), "_activate_radio"), false, false ); @@ -106,7 +122,10 @@ namespace emp::prefab { "class", emp::to_string("btn-outline-", deactivate_style) ); web::Input deactivate_radio( - [&, info=this->Info()](std::string) { info->SetActive(false); }, + [&, info=this->Info()](std::string) { + info->SetActive(false); + info->GetCallback()(false); + }, "radio", "", emp::to_string(GetID(), "_deactivate_radio"), false, true ); @@ -135,6 +154,11 @@ namespace emp::prefab { bool IsActive() const { return Info()->IsActive(); } + + ToggleButtonGroup & SetCallback(const on_toggle_t & cb) { + Info()->UpdateCallback(cb); + return (*this); + } }; } From b9fcedca6b8f3e9e73f6d633b63f5a8c991959eb Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Thu, 19 Aug 2021 16:02:48 -0700 Subject: [PATCH 15/46] Use checker function --- include/emp/prefab/ControlPanel.hpp | 110 +++++++++++++++------------- 1 file changed, 60 insertions(+), 50 deletions(-) diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index 166d105b55..eef2767ad6 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -17,13 +17,15 @@ namespace emp::prefab { namespace internal { + using checker_func_t = std::function; class ControlPanelInfo : public web::internal::DivInfo { - std::string refresh_unit = "MILLISECONDS"; + std::string refresh_unit; std::map refresh_rates{ {"MILLISECONDS", 100}, {"FRAMES", 5} }; + checker_func_t redraw_checker; emp::vector refresh_list; std::function simulation; @@ -32,6 +34,10 @@ namespace emp::prefab { ControlPanelInfo(const std::string & in_id="") : DivInfo(in_id), simulation([](){ ; }) { ; } + const checker_func_t & GetRedrawChecker() const { + return redraw_checker; + } + void SetSimulation(std::function & sim) { simulation = sim; } @@ -42,20 +48,30 @@ namespace emp::prefab { void SetUnit(const std::string & unit) { refresh_unit = unit; - } - const std::string & GetUnit() const { - return refresh_unit; + const int & rate = refresh_rates[refresh_unit]; + if (unit == "MILLISECONDS") { + redraw_checker = [elapsed_milliseconds = 0, rate] + (const web::Animate & anim) mutable { + elapsed_milliseconds += anim.GetStepTime(); + if (elapsed_milliseconds > rate) { + elapsed_milliseconds -= rate; + if (elapsed_milliseconds > rate) elapsed_milliseconds = 0; + return true; + } + return false; + }; + } else if (unit == "FRAMES") { + redraw_checker = [rate](const web::Animate & anim) { + return anim.GetFrameCount() % rate; + }; + } } void SetRate(const int & rate) { refresh_rates[refresh_unit] = rate; } - const int & GetRate() { - return refresh_rates[refresh_unit]; - } - emp::vector & GetRefreshList() { return refresh_list; } @@ -97,7 +113,11 @@ namespace emp::prefab { emp::to_string(GetID(), "_run_toggle") }, button_line(ButtonGroup{emp::to_string(GetID(), "_core")}), - step{[](){ ; }, "", emp::to_string(GetID(), "_step")} + step{ + [](){ ; }, + "", + emp::to_string(GetID(), "_step") + } { AddAttr( "class", "btn-toolbar", @@ -105,56 +125,42 @@ namespace emp::prefab { "role", "toolbar", "aria-label", "Toolbar with simulation controls" ); - SetUnit(refresh_mode); - SetRate(refresh_rate); + SetRefreshUnit(refresh_mode); + SetRefreshRate(refresh_rate); - step.SetAttr("class", "success"); - step << FontAwesomeIcon{"fa-step-forward"}; - button_line << toggle_run; static_cast
(*this) << button_line; + button_line << toggle_run; + button_line << step; + + step.AddAttr( + "class", "btn", + "class", "btn-success", + "disabled", true + ); AddAnimation(GetID(), - [elapsed_milliseconds = 0, - &run_sim=GetSimulation(), - &refresh_list=Info()->GetRefreshList(), - &unit=GetUnit(), - &rate=GetRate()] + [&, run_sim=GetSimulation(), + refresh_list=Info()->GetRefreshList(), + check_redraw=Info()->GetRedrawChecker()] (const web::Animate & anim) mutable { - // Run the simulation function every frame run_sim(); - - if (unit == "FRAMES") { - // Units of frames means redraw every # of frames - if (anim.GetFrameCount() % rate) { - for (emp::web::Widget & wid : refresh_list) { - wid.Redraw(); - } + // Redraw widgets according to a rule + if(check_redraw(anim)) { + for (emp::web::Widget & wid : refresh_list) { + wid.Redraw(); } - } else { - // Units of milliseconds means redraw every # of milliseconds - elapsed_milliseconds += anim.GetStepTime(); - if (elapsed_milliseconds > rate) { - elapsed_milliseconds -= rate; - for (emp::web::Widget & wid : refresh_list) { - wid.Redraw(); - } - } - // see ReadoutPanel for explanation of this pattern - if (elapsed_milliseconds > rate) elapsed_milliseconds = 0; } } ); toggle_run.SetCallback( [&anim=Animate(GetID()), step=web::Button(step)] - (bool set_active) mutable { - if (set_active) { + (bool is_active) mutable { + if (is_active) { anim.Start(); - step.SetAttr("disabled", true); } else { anim.Stop(); - step.SetAttr("disabled", true); } } ); @@ -175,19 +181,23 @@ namespace emp::prefab { new internal::ControlPanelInfo(in_id) ) { ; } - void SetSimulation(std::function & sim) { + ControlPanel & SetSimulation(std::function & sim) { Info()->SetSimulation(sim); + return *this; } - const std::function & GetSimulation() const { return Info()->GetSimulation(); } - - void SetUnit(const std::string & units) { Info()->SetUnit(units); } - - const std::string & GetUnit() const { return Info()->GetUnit(); } + const std::function & GetSimulation() const { + return Info()->GetSimulation(); + } - void SetRate(const int & val) { Info()->SetRate(val); } + ControlPanel & SetRefreshUnit(const std::string & units) { + Info()->SetUnit(units); + return *this; + } - const int & GetRate() { return Info()->GetRate(); } + void SetRefreshRate(const int & val) { + Info()->SetRate(val); + } void AddToRefreshList(Widget & area) { Info()->GetRefreshList().push_back(area); From c2e1aa0bd82bec4368dd1f71f08d35374675a878 Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Fri, 20 Aug 2021 14:50:32 -0700 Subject: [PATCH 16/46] Fix bug with toggle clicks --- include/emp/prefab/ToggleButtonGroup.hpp | 30 +++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/include/emp/prefab/ToggleButtonGroup.hpp b/include/emp/prefab/ToggleButtonGroup.hpp index 3699e7e2d6..c86c4c4ade 100644 --- a/include/emp/prefab/ToggleButtonGroup.hpp +++ b/include/emp/prefab/ToggleButtonGroup.hpp @@ -101,15 +101,19 @@ namespace emp::prefab { AddAttr("class", "grayout"); } + auto & on_toggle = GetCallback(); + activate_label.AddAttr( "class", "btn", "class", emp::to_string("btn-outline-", activate_style) ); + activate_label.OnClick([tog=*this, &handle_toggle=on_toggle]() mutable { + tog.SetActive(true); + handle_toggle(true); + }); + web::Input activate_radio( - [&, info=this->Info()](std::string) { - info->SetActive(true); - info->GetCallback()(true); - }, + [](std::string){ ; }, "radio", "", emp::to_string(GetID(), "_activate_radio"), false, false ); @@ -121,11 +125,13 @@ namespace emp::prefab { "class", "btn", "class", emp::to_string("btn-outline-", deactivate_style) ); + deactivate_label.OnClick([tog=*this, &handle_toggle=on_toggle]() mutable { + tog.SetActive(false); + handle_toggle(false); + }); + web::Input deactivate_radio( - [&, info=this->Info()](std::string) { - info->SetActive(false); - info->GetCallback()(false); - }, + [](std::string){ ; }, "radio", "", emp::to_string(GetID(), "_deactivate_radio"), false, true ); @@ -155,6 +161,14 @@ namespace emp::prefab { return Info()->IsActive(); } + void SetActive(const bool & val) { + Info()->SetActive(val); + } + + const on_toggle_t & GetCallback() const { + return Info()->GetCallback(); + } + ToggleButtonGroup & SetCallback(const on_toggle_t & cb) { Info()->UpdateCallback(cb); return (*this); From 3ae32156fc848ce5f35942e08068976ec432ed88 Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Fri, 20 Aug 2021 14:50:50 -0700 Subject: [PATCH 17/46] Optimize checker and fix reference issues --- include/emp/prefab/ControlPanel.hpp | 67 +++++++++++++++++------------ 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index eef2767ad6..108a37ee5f 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -25,20 +25,44 @@ namespace emp::prefab { std::map refresh_rates{ {"MILLISECONDS", 100}, {"FRAMES", 5} }; - checker_func_t redraw_checker; + + const std::map refresh_checkers{ + { "MILLISECONDS", + [elapsed_milliseconds = 0, this] + (const web::Animate & anim) mutable { + int rate = this->refresh_rates[this->refresh_unit]; + elapsed_milliseconds += anim.GetStepTime(); + if (elapsed_milliseconds > rate) { + elapsed_milliseconds -= rate; + if (elapsed_milliseconds > rate) elapsed_milliseconds = 0; + return true; + } + return false; + }}, + { "FRAMES", + [this](const web::Animate & anim) { + return anim.GetFrameCount() % this->refresh_rates[this->refresh_unit]; + } + } + }; + + checker_func_t do_redraw; emp::vector refresh_list; std::function simulation; public: ControlPanelInfo(const std::string & in_id="") - : DivInfo(in_id), simulation([](){ ; }) { ; } + : DivInfo(in_id), + refresh_unit("MILLISECONDS"), + do_redraw(refresh_checkers.at(refresh_unit)), + simulation([](){ ; }) { ; } const checker_func_t & GetRedrawChecker() const { - return redraw_checker; + return do_redraw; } - void SetSimulation(std::function & sim) { + void SetSimulation(const std::function & sim) { simulation = sim; } @@ -48,24 +72,7 @@ namespace emp::prefab { void SetUnit(const std::string & unit) { refresh_unit = unit; - - const int & rate = refresh_rates[refresh_unit]; - if (unit == "MILLISECONDS") { - redraw_checker = [elapsed_milliseconds = 0, rate] - (const web::Animate & anim) mutable { - elapsed_milliseconds += anim.GetStepTime(); - if (elapsed_milliseconds > rate) { - elapsed_milliseconds -= rate; - if (elapsed_milliseconds > rate) elapsed_milliseconds = 0; - return true; - } - return false; - }; - } else if (unit == "FRAMES") { - redraw_checker = [rate](const web::Animate & anim) { - return anim.GetFrameCount() % rate; - }; - } + do_redraw = refresh_checkers.at(refresh_unit); } void SetRate(const int & rate) { @@ -139,15 +146,17 @@ namespace emp::prefab { ); AddAnimation(GetID(), - [&, run_sim=GetSimulation(), - refresh_list=Info()->GetRefreshList(), - check_redraw=Info()->GetRedrawChecker()] + [&run_sim=GetSimulation(), + &refresh_list=Info()->GetRefreshList(), + &do_redraw=Info()->GetRedrawChecker()] (const web::Animate & anim) mutable { // Run the simulation function every frame run_sim(); // Redraw widgets according to a rule - if(check_redraw(anim)) { - for (emp::web::Widget & wid : refresh_list) { + if(do_redraw(anim)) { + std::cout << "List size " << refresh_list.size() << std::endl; + for (auto & wid : refresh_list) { + std::cout << "Redrawing " << wid.GetID() << std::endl; wid.Redraw(); } } @@ -158,6 +167,7 @@ namespace emp::prefab { [&anim=Animate(GetID()), step=web::Button(step)] (bool is_active) mutable { if (is_active) { + anim.Start(); } else { anim.Stop(); @@ -181,7 +191,7 @@ namespace emp::prefab { new internal::ControlPanelInfo(in_id) ) { ; } - ControlPanel & SetSimulation(std::function & sim) { + ControlPanel & SetSimulation(const std::function & sim) { Info()->SetSimulation(sim); return *this; } @@ -201,6 +211,7 @@ namespace emp::prefab { void AddToRefreshList(Widget & area) { Info()->GetRefreshList().push_back(area); + std::cout << Info()->GetRefreshList().size() << std::endl; } template From 50ba0640f24f5e6218190b0ac8ba58ade6274f2b Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Fri, 20 Aug 2021 16:00:38 -0700 Subject: [PATCH 18/46] Document control panel --- include/emp/prefab/ControlPanel.hpp | 116 +++++++++++++++++++++++++--- 1 file changed, 107 insertions(+), 9 deletions(-) diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index 108a37ee5f..17de64231a 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -18,14 +18,23 @@ namespace emp::prefab { namespace internal { using checker_func_t = std::function; - + /** + * Shared pointer held by instances of ControlPanel class representing + * the same conceptual ControlPanel DOM object. + * Contains state that should persist while ControlPanel DOM object + * persists. + */ class ControlPanelInfo : public web::internal::DivInfo { + // The unit for rate of refresh std::string refresh_unit; + + // A list of refresh rates with current value associated with current unit std::map refresh_rates{ {"MILLISECONDS", 100}, {"FRAMES", 5} }; + // Map units to cool refresh checkers const std::map refresh_checkers{ { "MILLISECONDS", [elapsed_milliseconds = 0, this] @@ -36,6 +45,7 @@ namespace emp::prefab { elapsed_milliseconds -= rate; if (elapsed_milliseconds > rate) elapsed_milliseconds = 0; return true; + // If this is weird check out explanation of pattern in ReadoutPanel } return false; }}, @@ -46,45 +56,83 @@ namespace emp::prefab { } }; + // The current redraw checker function checker_func_t do_redraw; + // A list of widget that should be redraw when do_redraw return true emp::vector refresh_list; + + // A function to run every frame (as fast as possible) std::function simulation; public: + /** + * Construct a shared pointer to manage ControlPanel state. + * + * @param in_id HTML ID of ConfigPanel div + */ ControlPanelInfo(const std::string & in_id="") : DivInfo(in_id), refresh_unit("MILLISECONDS"), do_redraw(refresh_checkers.at(refresh_unit)), simulation([](){ ; }) { ; } + /** + * Get a reference to the redraw checker function + * @return a redraw checker (void(const Animate &) function) + */ const checker_func_t & GetRedrawChecker() const { return do_redraw; } + /** + * Set the simulation for this control panel + * @param sim the function to be run every frame (as fast as possible) + */ void SetSimulation(const std::function & sim) { simulation = sim; } + /** + * Get the simulation for this control panel + * @return the function to be run every frame (as fast as possible) + */ const std::function & GetSimulation() const { return simulation; } + /** + * Set the refresh rate units for this control panel + * @param unit either "MILLISECONDS" or "FRAMES" + */ void SetUnit(const std::string & unit) { refresh_unit = unit; do_redraw = refresh_checkers.at(refresh_unit); } + /** + * Set the refresh rate for this control panel + * @param rate the number of milliseconds or frames between refreshes + */ void SetRate(const int & rate) { refresh_rates[refresh_unit] = rate; } + /** + * Get the refresh list for this control panel + * @return a list of Widgets that will be refreshed every update period + */ emp::vector & GetRefreshList() { return refresh_list; } }; } - + /** + * Use the ConfigPanel class to add a play/pause toggle button and a step + * button to your application. You can add a simulation to be run, web + * components to be redrawn, and more Buttons or ButtonGroups to add more + * functionality. + */ class ControlPanel : public web::Div { /** @@ -108,6 +156,13 @@ namespace emp::prefab { web::Button step; protected: + /** + * The protected contructor for a Control panel that sets up the state + * and event handlers + * @param refresh_mode units of "MILLISECONDS" or "FRAMES" + * @param refresh_rate the number of milliseconds or frames between refreshes + * @param in_info info object associated with this component + */ ControlPanel( const std::string & refresh_mode, const int & refresh_rate, @@ -132,8 +187,7 @@ namespace emp::prefab { "role", "toolbar", "aria-label", "Toolbar with simulation controls" ); - SetRefreshUnit(refresh_mode); - SetRefreshRate(refresh_rate); + SetRefreshRate(refresh_rate, refresh_mode); static_cast
(*this) << button_line; button_line << toggle_run; @@ -154,9 +208,7 @@ namespace emp::prefab { run_sim(); // Redraw widgets according to a rule if(do_redraw(anim)) { - std::cout << "List size " << refresh_list.size() << std::endl; for (auto & wid : refresh_list) { - std::cout << "Redrawing " << wid.GetID() << std::endl; wid.Redraw(); } } @@ -181,6 +233,12 @@ namespace emp::prefab { } public: + /** + * Contructor for a Control panel. + * @param refresh_mode units of "MILLISECONDS" or "FRAMES" + * @param refresh_rate the number of milliseconds or frames between refreshes + * @param in_id HTML ID of control panel div + */ ControlPanel( const std::string & refresh_mode, const int & refresh_rate, @@ -191,29 +249,69 @@ namespace emp::prefab { new internal::ControlPanelInfo(in_id) ) { ; } + /** + * Set the simulation for this control panel + * @param sim the function to be run every frame (as fast as possible) + */ ControlPanel & SetSimulation(const std::function & sim) { Info()->SetSimulation(sim); return *this; } + /** + * Get the simulation for this control panel + * @return the function to be run every frame (as fast as possible) + */ const std::function & GetSimulation() const { return Info()->GetSimulation(); } + /** + * Set the refresh rate units for this control panel. + * @param unit either "MILLISECONDS" or "FRAMES" + * @note rates are independent for "MILLISECONDS" and "FRAMES" + */ ControlPanel & SetRefreshUnit(const std::string & units) { Info()->SetUnit(units); return *this; } - void SetRefreshRate(const int & val) { - Info()->SetRate(val); + /** + * Set the refresh rate for this control panel for the current unit. + * @param rate period in frames or milliseconds + * @note rates are independent for "MILLISECONDS" and "FRAMES". + */ + void SetRefreshRate(const int & rate) { + Info()->SetRate(rate); } + /** + * Set the refresh rate for this control panel. + * @param rate the number of milliseconds or frames between refreshes + * @param unit either "MILLISECONDS" or "FRAMES" + * @note rates are independent for "MILLISECONDS" and "FRAMES" + */ + void SetRefreshRate( const int & rate, const std::string & units) { + Info()->SetUnit(units); + Info()->SetRate(rate); + } + + /** + * Adds a Widget to a list of widgets redrawn at the specified refresh rate. + * @param area a widget + */ void AddToRefreshList(Widget & area) { Info()->GetRefreshList().push_back(area); - std::cout << Info()->GetRefreshList().size() << std::endl; } + /** + * Stream operator to add a component to the control panel. + * + * Some special behavior: Buttons and ToggleButtonGroups will be added + * to the last ButtonGroup added to keep related components together. + * If you want to start a new group, just stream in a new ButtonGroup. + * @param in_val a component to be added to the control panel + */ template ControlPanel & operator<<(IN_TYPE && in_val) { // Took soooo long to figure out but if in_val is a r-value ref From b099a3beb84405ffb3e3592caa01cfec56a9b8af Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Fri, 20 Aug 2021 16:25:16 -0700 Subject: [PATCH 19/46] Remove step button disabling --- include/emp/prefab/ControlPanel.hpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index 17de64231a..70bba05410 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -193,12 +193,6 @@ namespace emp::prefab { button_line << toggle_run; button_line << step; - step.AddAttr( - "class", "btn", - "class", "btn-success", - "disabled", true - ); - AddAnimation(GetID(), [&run_sim=GetSimulation(), &refresh_list=Info()->GetRefreshList(), @@ -219,7 +213,6 @@ namespace emp::prefab { [&anim=Animate(GetID()), step=web::Button(step)] (bool is_active) mutable { if (is_active) { - anim.Start(); } else { anim.Stop(); From 0d3256e749a90079f71c71c7b83bc5f72d729950 Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Fri, 20 Aug 2021 16:58:43 -0700 Subject: [PATCH 20/46] Add documentation --- include/emp/prefab/ButtonGroup.hpp | 15 ++++- include/emp/prefab/ToggleButtonGroup.hpp | 70 +++++++++++++++++++++++- 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/include/emp/prefab/ButtonGroup.hpp b/include/emp/prefab/ButtonGroup.hpp index 477a47e218..ded9c7823b 100644 --- a/include/emp/prefab/ButtonGroup.hpp +++ b/include/emp/prefab/ButtonGroup.hpp @@ -4,20 +4,33 @@ #include "emp/web/Div.hpp" namespace emp::prefab { - + /** + * Use a ButtonGroup to place buttons of a similar role into the same + * container or to save space by placing buttons without gaps between them. + */ class ButtonGroup : public web::Div { protected: + /** + * The protected contructor for a ButtonGroup. + * @param info_ref shared pointer containing presistent state + */ ButtonGroup(web::internal::DivInfo * info_ref) : web::Div(info_ref) { SetAttr("class", "btn-group"); } public: + /** + * Constructor for a ButtonGroup. + * @param in_id HTML ID of ButtonGroup div + */ ButtonGroup(const std::string & in_id="") : ButtonGroup(new web::internal::DivInfo(in_id)) { ; } /** * A function useful for joining two button groups together into one unit. + * Removes buttons from the ButtonGroup passed in and adds them to this + * button group group. * @param btn_group a button group */ ButtonGroup & TakeChildren(ButtonGroup & btn_group) { diff --git a/include/emp/prefab/ToggleButtonGroup.hpp b/include/emp/prefab/ToggleButtonGroup.hpp index c86c4c4ade..5091e81e2c 100644 --- a/include/emp/prefab/ToggleButtonGroup.hpp +++ b/include/emp/prefab/ToggleButtonGroup.hpp @@ -12,7 +12,12 @@ namespace emp::prefab { namespace internal { using on_toggle_t = std::function; - + /** + * Shared pointer held by instances of ToggleButtonGroup class representing + * the same conceptual ToggleButtonGroup DOM object. + * Contains state that should persist while ToggleButtonGroup DOM object + * persists. + */ class ToggleButtonGroupInfo : public web::internal::DivInfo { on_toggle_t callback; bool active; @@ -21,24 +26,46 @@ namespace emp::prefab { ToggleButtonGroupInfo(const std::string & in_id) : web::internal::DivInfo(in_id), callback([](bool){ ; }), active(false) { ; } + /** + * Set the function to be called when the component toggles + * @param cb a callback function that accepts a boolean indicating + * whether the toggle is active or inactive + */ void UpdateCallback(const on_toggle_t & cb) { callback = cb; } + /** + * Get the function to be called when the component toggles + * @return a callback function + */ const on_toggle_t & GetCallback() const { return callback; } + /** + * Determines whether the toggle is in the active state + * @return boolean + */ bool IsActive() const { return active; } - void SetActive(const bool & val) { - active = val; + /** + * Set the active state of the toggle + * @param is_active a boolean + */ + void SetActive(const bool & is_active) { + active = is_active; } }; } + /** + * Use a ToggleButtonGroup to create a control with two labeled, visually + * distinct states. Choose whether the button should display cassette-style + * with two separate buttons or as a single button that flip-flops state. + */ class ToggleButtonGroup : public ButtonGroup { using on_toggle_t = internal::on_toggle_t; @@ -74,6 +101,7 @@ namespace emp::prefab { * (both buttons visible) or do a swap on toggle (one button visible) * @param grayout in cassette mode, whether buttons should be * grayed out to further emphasize the current state + * @param info_ref shared pointer containing presistent state */ template ToggleButtonGroup( @@ -111,6 +139,10 @@ namespace emp::prefab { tog.SetActive(true); handle_toggle(true); }); + // OnClick used due to a strange bug(?) in which the input radios only + // fire their onchange function once + // Probably due to Bootstrap/jQuery weirdness and this is a last minute + // work around ¯\_(ツ)_/¯ web::Input activate_radio( [](std::string){ ; }, @@ -140,6 +172,21 @@ namespace emp::prefab { } public: + /** + * @param activate_indicator a string, FontAwesomeIcon or other component + * indicating that the first button actives this toggle + * @param deactivate_indicator a string, FontAwesomeIcon or other component + * indicating that the second button deactivates this toggle + * @param activate_style a bootstrap style (primary, secondary, etc) for + * the first button + * @param deactivate_style a bootstrap style (primary, secondary, etc) for + * the second button + * @param cassette_style whether the toggle should display in cassette style + * (both buttons visible) or do a swap on toggle (one button visible) + * @param grayout in cassette mode, whether buttons should be + * grayed out to further emphasize the current state + * @param in_id HTML ID of ToggleButtonGroup div + */ template ToggleButtonGroup( L1_TYPE && activate_indicator, @@ -157,18 +204,35 @@ namespace emp::prefab { new internal::ToggleButtonGroupInfo(in_id) ) { ; } + /** + * Determines whether the toggle is in the active state + * @return boolean + */ bool IsActive() const { return Info()->IsActive(); } + /** + * Set the active state of the toggle + * @param is_active a boolean + */ void SetActive(const bool & val) { Info()->SetActive(val); } + /** + * Get the function to be called when the component toggles + * @return a callback function + */ const on_toggle_t & GetCallback() const { return Info()->GetCallback(); } + /** + * Set the function to be called when the component toggles + * @param cb a callback function that accepts a boolean indicating + * whether the toggle is active or inactive + */ ToggleButtonGroup & SetCallback(const on_toggle_t & cb) { Info()->UpdateCallback(cb); return (*this); From b0be1779237bf00e7c6fb59f7b7edc853de46431 Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Wed, 25 Aug 2021 14:48:33 -0700 Subject: [PATCH 21/46] Swap rate and unit parameters in constructor --- include/emp/prefab/ControlPanel.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index 70bba05410..c181d3ced3 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -59,7 +59,7 @@ namespace emp::prefab { // The current redraw checker function checker_func_t do_redraw; - // A list of widget that should be redraw when do_redraw return true + // A list of widget that should be redrawn when do_redraw return true emp::vector refresh_list; // A function to run every frame (as fast as possible) @@ -131,7 +131,7 @@ namespace emp::prefab { * Use the ConfigPanel class to add a play/pause toggle button and a step * button to your application. You can add a simulation to be run, web * components to be redrawn, and more Buttons or ButtonGroups to add more - * functionality. + * functionality to the control panel. */ class ControlPanel : public web::Div { @@ -164,8 +164,8 @@ namespace emp::prefab { * @param in_info info object associated with this component */ ControlPanel( - const std::string & refresh_mode, const int & refresh_rate, + const std::string & refresh_unit, web::internal::DivInfo * in_info ) : web::Div(in_info), toggle_run{ @@ -187,7 +187,7 @@ namespace emp::prefab { "role", "toolbar", "aria-label", "Toolbar with simulation controls" ); - SetRefreshRate(refresh_rate, refresh_mode); + SetRefreshRate(refresh_rate, refresh_unit); static_cast
(*this) << button_line; button_line << toggle_run; @@ -228,17 +228,17 @@ namespace emp::prefab { public: /** * Contructor for a Control panel. - * @param refresh_mode units of "MILLISECONDS" or "FRAMES" * @param refresh_rate the number of milliseconds or frames between refreshes + * @param refresh_mode units of "MILLISECONDS" or "FRAMES" * @param in_id HTML ID of control panel div */ ControlPanel( - const std::string & refresh_mode, const int & refresh_rate, + const std::string & refresh_unit, const std::string & in_id="") : ControlPanel( - refresh_mode, refresh_rate, + refresh_unit, new internal::ControlPanelInfo(in_id) ) { ; } From 5a4cc26efdd6ccd94c72c3726d8869895f63f31c Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Wed, 25 Aug 2021 15:54:30 -0700 Subject: [PATCH 22/46] Add unit test for control panel --- include/emp/prefab/ControlPanel.hpp | 10 +- tests/web/ControlPanel.cpp | 156 ++++++++++++++++++++++++++++ tests/web/Makefile | 2 +- 3 files changed, 164 insertions(+), 4 deletions(-) create mode 100644 tests/web/ControlPanel.cpp diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index c181d3ced3..c98c42696e 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -172,13 +172,13 @@ namespace emp::prefab { FontAwesomeIcon{"fa-play"}, FontAwesomeIcon{"fa-pause"}, "success", "warning", true, false, - emp::to_string(GetID(), "_run_toggle") + emp::to_string(GetID(), "_main_toggle") }, - button_line(ButtonGroup{emp::to_string(GetID(), "_core")}), + button_line(ButtonGroup{emp::to_string(GetID(), "_main")}), step{ [](){ ; }, "", - emp::to_string(GetID(), "_step") + emp::to_string(GetID(), "_main_step") } { AddAttr( @@ -188,6 +188,10 @@ namespace emp::prefab { "aria-label", "Toolbar with simulation controls" ); SetRefreshRate(refresh_rate, refresh_unit); + step.AddAttr( + "class", "btn", + "class", "btn-success" + ); static_cast
(*this) << button_line; button_line << toggle_run; diff --git a/tests/web/ControlPanel.cpp b/tests/web/ControlPanel.cpp new file mode 100644 index 0000000000..9ac5f05fd0 --- /dev/null +++ b/tests/web/ControlPanel.cpp @@ -0,0 +1,156 @@ +// This file is part of Empirical, https://github.com/devosoft/Empirical +// Copyright (C) Michigan State University, 2021. +// Released under the MIT Software license; see doc/LICENSE + +#include "emp/web/_MochaTestRunner.hpp" +#include "emp/web/Document.hpp" +#include "emp/web/Div.hpp" +#include "emp/prefab/ButtonGroup.hpp" +#include "emp/prefab/ToggleButtonGroup.hpp" +#include "emp/prefab/ControlPanel.hpp" + +// Tests the integration between the Control Panel, Buttons, Button Groups, +// and ToggleButtonGroups +struct Test_Control_Panel : emp::web::BaseTest { +/* + * Creates extra div + the following control panel structure + * +------------------+------+-----+-------------------+ +---+---+---+ +--+ + * | +------+-------+ | | | +------+--------+ | | | | | | | + * | | Play | Pause | | Step | C | | Auto | Manual | | | D | A | B | | | + * | +------+-------+ | | | +------+--------+ | | | | | | | + * +------------------+------+-----+-------------------+ +---+---+---+ +--+ + * ToggleButtonGroup (default) ToggleButtonGroup (added) + */ + Test_Control_Panel() + : BaseTest({"emp_test_container"}) { + + emp::prefab::ControlPanel cp{5, "FRAMES", "ctrl"}; + emp::web::Div sim_area{"sim_area"}; + cp.AddToRefreshList(sim_area); + cp.SetRefreshRate(500, "MILLISECONDS"); + + emp::prefab::ButtonGroup husk{"husk"}; + husk << emp::web::Button{[](){;}, "A", "a_button"}; + husk << emp::web::Button{[](){;}, "B", "b_button"}; + + cp << emp::web::Button([](){;}, "C", "c_button"); + emp::prefab::ToggleButtonGroup toggle{ + "Auto", "Manual", + "primary", "secondary", + true, false, + "mode_toggle" + }; + cp << toggle; + toggle.SetCallback([](bool active) { + if (active) { + std::cout << "Auto!" << std::endl; + } else { + std::cout << "Manual!" << std::endl; + } + }); + emp::prefab::ButtonGroup real{"real"}; + real << emp::web::Button([](){;}, "D", "d_button"); + real.TakeChildren(husk); + cp << real; + cp << husk; + + Doc("emp_test_container") << sim_area; + Doc("emp_test_container") << cp; + } + + void Describe() override { + EM_ASM({ + describe("Control Panel HTML layout", function() { + const cp = document.getElementById('ctrl'); + it("should have three children (3 main button groups)", function() { + chai.assert.equal(cp.childElementCount, 3); + }); + + describe("first button group (#ctrl_main)", function() { + const bg1 = document.getElementById('ctrl_main'); + it("should exist", function() { + chai.assert.isNotNull(bg1); + }); + it("should have control panel (#ctrl) as parent", function() { + chai.assert.equal(bg1.parentElement.getAttribute("id"), "ctrl"); + }); + describe("group's children", function() { + // has 2 toggle button group bookending two buttons + it("has children elements: div, 2 buttons, div", function() { + const nodeNames = Array.from(bg1.children).map(child => child.nodeName); + chai.assert.deepEqual(nodeNames, ["DIV", "BUTTON", "BUTTON", "DIV"]); + }); + it("has main toggle button group (#ctrl_main_toggle)", function() { + const main_toggle = document.getElementById('ctrl_main_toggle'); + chai.assert.equal(main_toggle.parentElement.getAttribute("id"), "ctrl_main"); + }); + it("has step button (#ctrl_main_step)", function() { + const step = document.getElementById('ctrl_main_step'); + chai.assert.equal(step.parentElement.getAttribute("id"), "ctrl_main"); + }); + it("has C button (#c_button)", function() { + const c_button = bg1.children[2]; + chai.assert.equal(c_button.getAttribute("id"), "c_button"); + }); + it("has auto/manual toggle", function() { + const mode_toggle = bg1.children[3]; + chai.assert.equal(mode_toggle.getAttribute("id"), "mode_toggle"); + }); + }); + }); + + describe("second button group (#real)", function() { + const bg2 = document.getElementById('real'); + it("should exist", function() { + chai.assert.isNotNull(bg2); + }); + it("should have control panel (#ctrl) as parent", function() { + chai.assert.equal(bg2.parentElement.getAttribute("id"), "ctrl"); + }); + it("should have 3 children (due to TakeChildren)", function() { + chai.assert.equal(bg2.childElementCount, 3); + }); + describe("group's children", function() { + it("has only buttons", function() { + const nodeNames = Array.from(bg2.children).map(child => child.nodeName); + chai.assert.deepEqual(nodeNames, ["BUTTON", "BUTTON","BUTTON"]); + }); + it("has first child D (#d_button)", function() { + const d_button = bg2.children[0]; + chai.assert.equal(d_button.getAttribute("id"), "d_button"); + }); + it("has second child A (#a_button)", function() { + const a_button = bg2.children[1]; + chai.assert.equal(a_button.getAttribute("id"), "a_button"); + }); + it("has third child B (#b_button)", function() { + const b_button = bg2.children[2]; + chai.assert.equal(b_button.getAttribute("id"), "b_button"); + }); + }); + }); + + describe("third button group (#husk)", function() { + const bg3 = document.getElementById('husk'); + it("should exist", function() { + chai.assert.isNotNull(bg3); + }); + it("should have control panel (#ctrl) as parent", function() { + chai.assert.equal(bg3.parentElement.getAttribute("id"), "ctrl"); + }); + it("should have no children (due to TakeChildren)", function() { + chai.assert.equal(bg3.childElementCount, 0); + }); + }); + }); + }); + } +}; + +emp::web::MochaTestRunner test_runner; +int main() { + test_runner.Initialize({"emp_test_container"}); + test_runner.AddTest("Test emp::prefab::ControlPanel HTML Layout"); + test_runner.Run(); +} + diff --git a/tests/web/Makefile b/tests/web/Makefile index 05e9fc7869..8d914a2550 100644 --- a/tests/web/Makefile +++ b/tests/web/Makefile @@ -1,6 +1,6 @@ SHELL := /bin/bash -TEST_NAMES = ConfigPanel Collapse LoadingModal Card CommentBox Modal ToggleSwitch CodeBlock LoadingIcon FontAwesomeIcon ClickCounterDemo ClickCollapseDemo Element TextFeed js_utils JSWrap Widget visualizations ValueBox +TEST_NAMES = ConfigPanel Collapse LoadingModal Card CommentBox Modal ToggleSwitch CodeBlock LoadingIcon FontAwesomeIcon ClickCounterDemo ClickCollapseDemo Element TextFeed js_utils JSWrap Widget visualizations ValueBox ControlPanel # Currently a couple of the tests won't compile to native so this is a separate list for now. Eventually we should fix # that and just have one list From 21f8b50cb4fcbe3faaa4190af500ed0ae523884d Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Wed, 25 Aug 2021 16:14:52 -0700 Subject: [PATCH 23/46] Test for ToggleButtonGroup --- tests/web/Makefile | 2 +- tests/web/ToggleButtonGroup.cpp | 78 +++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 tests/web/ToggleButtonGroup.cpp diff --git a/tests/web/Makefile b/tests/web/Makefile index 8d914a2550..9c450f4ecb 100644 --- a/tests/web/Makefile +++ b/tests/web/Makefile @@ -1,6 +1,6 @@ SHELL := /bin/bash -TEST_NAMES = ConfigPanel Collapse LoadingModal Card CommentBox Modal ToggleSwitch CodeBlock LoadingIcon FontAwesomeIcon ClickCounterDemo ClickCollapseDemo Element TextFeed js_utils JSWrap Widget visualizations ValueBox ControlPanel +TEST_NAMES = ConfigPanel Collapse LoadingModal Card CommentBox Modal ToggleSwitch CodeBlock LoadingIcon FontAwesomeIcon ClickCounterDemo ClickCollapseDemo Element TextFeed js_utils JSWrap Widget visualizations ValueBox ControlPanel ToggleButtonGroup # Currently a couple of the tests won't compile to native so this is a separate list for now. Eventually we should fix # that and just have one list diff --git a/tests/web/ToggleButtonGroup.cpp b/tests/web/ToggleButtonGroup.cpp new file mode 100644 index 0000000000..5a186db166 --- /dev/null +++ b/tests/web/ToggleButtonGroup.cpp @@ -0,0 +1,78 @@ +// This file is part of Empirical, https://github.com/devosoft/Empirical +// Copyright (C) Michigan State University, 2021. +// Released under the MIT Software license; see doc/LICENSE + +#include "emp/web/_MochaTestRunner.hpp" +#include "emp/web/Document.hpp" +#include "emp/prefab/ToggleButtonGroup.hpp" +#include "emp/prefab/FontAwesomeIcon.hpp" + +struct Test_Toggle_Button_Group : emp::web::BaseTest { + Test_Toggle_Button_Group() + : BaseTest({"emp_test_container"}) { + emp::prefab::ToggleButtonGroup icon_and_string( + emp::prefab::FontAwesomeIcon{"fa-play"}, "Pause", + "primary", "secondary", + true, false, "icon_and_string" + ); + Doc("emp_test_container") << icon_and_string; + } + + void Describe() override { + EM_ASM({ + describe("ToggleButtonGroup HTML Layout", function() { + const toggle = document.getElementById("icon_and_string"); + it("should exists", function() { + chai.assert.isNotNull(toggle); + }); + it("should have parent #emp_test_container", function() { + chai.assert.equal(toggle.parentElement.getAttribute("id"), "emp_test_container"); + }); + it("should have 2 children", function() { + chai.assert.equal(toggle.childElementCount, 2); + }); + describe("first label (activate)", function() { + const label1 = toggle.children[0]; + it("should have ID #icon_and_string_activate", function() { + chai.assert.equal(label1.getAttribute("id"), "icon_and_string_activate"); + }); + it("should have two children", function() { + chai.assert.equal(label1.childElementCount, 2); + }); + const activate_radio = label1.children[0]; + it('should have first child be a radio input', function() { + chai.assert.equal(activate_radio.nodeName, "INPUT"); + }); + const activate_indicator = label1.children[1]; + it('should have second child be a span', function() { + chai.assert.equal(activate_indicator.nodeName, "SPAN"); + }); + }); + describe("second label (deactivate)", function() { + const label2 = toggle.children[1]; + it("should have ID #icon_and_string_deactivate", function() { + chai.assert.equal(label2.getAttribute("id"), "icon_and_string_deactivate"); + }); + it("should have two children", function() { + chai.assert.equal(label2.childElementCount, 2); + }); + const deactivate_radio = label2.children[0]; + it('should have first child be a radio input', function() { + chai.assert.equal(deactivate_radio.nodeName, "INPUT"); + }); + const deactivate_indicator = label2.children[1]; + it('should have second child be a span', function() { + chai.assert.equal(deactivate_indicator.nodeName, "SPAN"); + }); + }); + }); + }); + } +}; + +emp::web::MochaTestRunner test_runner; +int main() { + test_runner.Initialize({"emp_test_container"}); + test_runner.AddTest("Test emp::prefab::ToggleButtonGroup HTML Layout"); + test_runner.Run(); +} From d0faa5c79e96f21be3198df9bf9d1df7f765e743 Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Thu, 2 Sep 2021 17:13:07 -0700 Subject: [PATCH 24/46] Address feedback --- include/emp/prefab/ButtonGroup.hpp | 29 +- include/emp/prefab/ControlPanel.hpp | 326 ++++++++++++++--------- include/emp/prefab/ToggleButtonGroup.hpp | 171 ++++++------ tests/web/ControlPanel.cpp | 4 +- 4 files changed, 312 insertions(+), 218 deletions(-) diff --git a/include/emp/prefab/ButtonGroup.hpp b/include/emp/prefab/ButtonGroup.hpp index ded9c7823b..cae4f751b4 100644 --- a/include/emp/prefab/ButtonGroup.hpp +++ b/include/emp/prefab/ButtonGroup.hpp @@ -1,3 +1,13 @@ +/** + * @note This file is part of Empirical, https://github.com/devosoft/Empirical + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021 + * + * @file ButtonGroup.hpp + * @brief ButtonGroups add styling to compactly display a group of buttons and + * provides methods useful for moving buttons between groups. + */ + #ifndef EMP_BUTTON_GROUP_HPP #define EMP_BUTTON_GROUP_HPP @@ -5,6 +15,10 @@ namespace emp::prefab { /** + * A ButtonGroup is a container with styling specifically to display buttons. + * It also provides methods for moving buttons from one group into another + * allowing the user to combine groups. + * * Use a ButtonGroup to place buttons of a similar role into the same * container or to save space by placing buttons without gaps between them. */ @@ -12,7 +26,9 @@ namespace emp::prefab { protected: /** - * The protected contructor for a ButtonGroup. + * A protected contructor for a ButtonGroup for internal use only. See the + * prefab/README.md for more information on this design pattern. + * * @param info_ref shared pointer containing presistent state */ ButtonGroup(web::internal::DivInfo * info_ref) : web::Div(info_ref) { @@ -29,16 +45,17 @@ namespace emp::prefab { /** * A function useful for joining two button groups together into one unit. - * Removes buttons from the ButtonGroup passed in and adds them to this - * button group group. + * Removes buttons from the ButtonGroup passed in and appends them in order + * to this button group group. + * * @param btn_group a button group */ - ButtonGroup & TakeChildren(ButtonGroup & btn_group) { + ButtonGroup & TakeChildren(ButtonGroup && btn_group) { *this << btn_group.Children(); btn_group.Clear(); return (*this); } }; -} +} // namespace emp::prefab -#endif +#endif // #ifndef EMP_BUTTON_GROUP_HPP diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index c98c42696e..be5a30ba77 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -1,6 +1,19 @@ +/** + * @note This file is part of Empirical, https://github.com/devosoft/Empirical + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021 + * + * @file ControlPanel.hpp + * @brief A ControlPanel manages ... + */ + #ifndef EMP_CONTROL_PANEL_HPP #define EMP_CONTROL_PANEL_HPP +#include +#include +#include + #include "emp/base/optional.hpp" #include "emp/base/vector.hpp" @@ -16,117 +29,146 @@ namespace emp::prefab { - namespace internal { - using checker_func_t = std::function; +class ControlPanel; + +namespace internal { + + /** + * Shared pointer held by instances of ControlPanel class representing + * the same conceptual ControlPanel DOM object. + * Contains state that should persist while ControlPanel DOM object + * persists. + */ + class ControlPanelInfo : public web::internal::DivInfo { + friend ControlPanel; /** - * Shared pointer held by instances of ControlPanel class representing - * the same conceptual ControlPanel DOM object. - * Contains state that should persist while ControlPanel DOM object - * persists. + * A RefreshChecker is a functor that accepts an Animate reference + * and returns a boolean indicating whether components should be redrawn + * based on an internal "rate" for redrawing. */ - class ControlPanelInfo : public web::internal::DivInfo { - - // The unit for rate of refresh - std::string refresh_unit; - - // A list of refresh rates with current value associated with current unit - std::map refresh_rates{ - {"MILLISECONDS", 100}, {"FRAMES", 5} - }; - - // Map units to cool refresh checkers - const std::map refresh_checkers{ - { "MILLISECONDS", - [elapsed_milliseconds = 0, this] - (const web::Animate & anim) mutable { - int rate = this->refresh_rates[this->refresh_unit]; - elapsed_milliseconds += anim.GetStepTime(); - if (elapsed_milliseconds > rate) { - elapsed_milliseconds -= rate; - if (elapsed_milliseconds > rate) elapsed_milliseconds = 0; - return true; - // If this is weird check out explanation of pattern in ReadoutPanel - } - return false; - }}, - { "FRAMES", - [this](const web::Animate & anim) { - return anim.GetFrameCount() % this->refresh_rates[this->refresh_unit]; + class RefreshChecker { + protected: + int rate; + public: + RefreshChecker(int r) { rate = r; } + virtual bool operator()(const web::Animate &) { + return true; + } + void SetRate(int r) { + rate = r; + } + int GetRate() const { return rate; } + }; + + // ControlPanelInfo holds contains two specific instances of a + // RefreshChecker for milliseconds and frames to keep rates independent + // for each. + class : public RefreshChecker { + using RefreshChecker::RefreshChecker; + int elapsed_milliseconds = 0; + bool operator()(const web::Animate & anim) { + elapsed_milliseconds += anim.GetStepTime(); + if (elapsed_milliseconds > rate) { + elapsed_milliseconds -= rate; + if (elapsed_milliseconds > rate) elapsed_milliseconds = 0; + return true; } - } - }; + return false; + } + } millisecond_refresher{100}; + class : public RefreshChecker { + using RefreshChecker::RefreshChecker; + bool operator()(const web::Animate & anim) { + return anim.GetFrameCount() % rate; + } + } frame_refresher{5}; - // The current redraw checker function - checker_func_t do_redraw; + // The current redraw checker function + RefreshChecker should_redraw; - // A list of widget that should be redrawn when do_redraw return true - emp::vector refresh_list; + // A list of widget that should be redrawn when do_redraw return true + emp::vector refresh_list; - // A function to run every frame (as fast as possible) - std::function simulation; + // A function to run every frame (as fast as possible) + std::function step_callback; - public: - /** - * Construct a shared pointer to manage ControlPanel state. - * - * @param in_id HTML ID of ConfigPanel div - */ - ControlPanelInfo(const std::string & in_id="") - : DivInfo(in_id), - refresh_unit("MILLISECONDS"), - do_redraw(refresh_checkers.at(refresh_unit)), - simulation([](){ ; }) { ; } - - /** - * Get a reference to the redraw checker function - * @return a redraw checker (void(const Animate &) function) - */ - const checker_func_t & GetRedrawChecker() const { - return do_redraw; - } + /** + * Construct a shared pointer to manage ControlPanel state. + * + * @param in_id HTML ID of ConfigPanel div + */ + ControlPanelInfo(const std::string & in_id="") + : DivInfo(in_id), + should_redraw(millisecond_refresher), + step_callback([](){ ; }) { ; } - /** - * Set the simulation for this control panel - * @param sim the function to be run every frame (as fast as possible) - */ - void SetSimulation(const std::function & sim) { - simulation = sim; - } + /** + * Get a constant reference to the redraw checker function + * @return a redraw checker + */ + const RefreshChecker & GetRefreshChecker() const { + return should_redraw; + } - /** - * Get the simulation for this control panel - * @return the function to be run every frame (as fast as possible) - */ - const std::function & GetSimulation() const { - return simulation; - } + /** + * Get a reference to the redraw checker function + * @return a redraw checker + */ + RefreshChecker & GetRefreshChecker() { + return should_redraw; + } - /** - * Set the refresh rate units for this control panel - * @param unit either "MILLISECONDS" or "FRAMES" - */ - void SetUnit(const std::string & unit) { - refresh_unit = unit; - do_redraw = refresh_checkers.at(refresh_unit); - } + /** + * Gets a void callback function that will be called every frame (as often as possible). + * @return the void callback function to advance the state of some simulation or process + * by one update every call. + */ + const std::function & GetStepCallback() const { + return step_callback; + } - /** - * Set the refresh rate for this control panel - * @param rate the number of milliseconds or frames between refreshes - */ - void SetRate(const int & rate) { - refresh_rates[refresh_unit] = rate; - } + /** + * Get the refresh list for this control panel + * @return a list of Widgets that will be refreshed every update period + */ + emp::vector & GetRefreshList() { + return refresh_list; + } + + public: + /** + * Set the redraw checker function + * @param check a redraw checker + */ + void SetRefreshChecker(const RefreshChecker & check) { + should_redraw = check; + } + + /** + * Set the redraw checker function to milliseconds instance + */ + void SetRefreshCheckerMilliseconds() { + should_redraw = millisecond_refresher; + } + + /** + * Set the redraw checker function to frames instance + */ + void SetRefreshCheckerFrames() { + should_redraw = frame_refresher; + } + + /** + * Sets a void callback function that will be called every frame (as often as possible). + * @param step the void callback function to advance the state of some simulation or process + * by one update every call. + */ + void SetStepCallback(const std::function & step) { + step_callback = step; + } + }; +} // namspace internal - /** - * Get the refresh list for this control panel - * @return a list of Widgets that will be refreshed every update period - */ - emp::vector & GetRefreshList() { - return refresh_list; - } - }; - } /** * Use the ConfigPanel class to add a play/pause toggle button and a step * button to your application. You can add a simulation to be run, web @@ -134,7 +176,10 @@ namespace emp::prefab { * functionality to the control panel. */ class ControlPanel : public web::Div { + public: + using RefreshChecker = internal::ControlPanelInfo::RefreshChecker; + private: /** * Get shared info pointer, cast to ControlPanel-specific type. * @return cast pointer @@ -151,14 +196,16 @@ namespace emp::prefab { return dynamic_cast(info); } - ToggleButtonGroup toggle_run; - Div button_line; - web::Button step; + ToggleButtonGroup toggle_run; // The main toggle to stop/start animation + ButtonGroup button_line; // Current button group to which buttons are streamed + web::Button step; // Button to step forward animation protected: /** * The protected contructor for a Control panel that sets up the state - * and event handlers + * and event handlers. For internal use only. See the prefab/README.md for + * more information on this design pattern. + * * @param refresh_mode units of "MILLISECONDS" or "FRAMES" * @param refresh_rate the number of milliseconds or frames between refreshes * @param in_info info object associated with this component @@ -198,16 +245,16 @@ namespace emp::prefab { button_line << step; AddAnimation(GetID(), - [&run_sim=GetSimulation(), + [&step_simulation=Info()->GetStepCallback(), &refresh_list=Info()->GetRefreshList(), - &do_redraw=Info()->GetRedrawChecker()] + &should_redraw=Info()->GetRefreshChecker()] (const web::Animate & anim) mutable { // Run the simulation function every frame - run_sim(); + step_simulation(); // Redraw widgets according to a rule - if(do_redraw(anim)) { - for (auto & wid : refresh_list) { - wid.Redraw(); + if (should_redraw(anim)) { + for (auto & widget : refresh_list) { + widget.Redraw(); } } } @@ -215,7 +262,7 @@ namespace emp::prefab { toggle_run.SetCallback( [&anim=Animate(GetID()), step=web::Button(step)] - (bool is_active) mutable { + (const bool & is_active) mutable { if (is_active) { anim.Start(); } else { @@ -247,29 +294,27 @@ namespace emp::prefab { ) { ; } /** - * Set the simulation for this control panel - * @param sim the function to be run every frame (as fast as possible) + * Sets a void callback function that will be called every frame (as often as possible). + * @param step the void callback function to advance the state of some simulation or process + * by one update every call. */ - ControlPanel & SetSimulation(const std::function & sim) { - Info()->SetSimulation(sim); + ControlPanel & SetStepCallback(const std::function & step) { + Info()->SetStepCallback(step); return *this; } - /** - * Get the simulation for this control panel - * @return the function to be run every frame (as fast as possible) - */ - const std::function & GetSimulation() const { - return Info()->GetSimulation(); - } - /** * Set the refresh rate units for this control panel. * @param unit either "MILLISECONDS" or "FRAMES" - * @note rates are independent for "MILLISECONDS" and "FRAMES" + * @note rates are independent for "MILLISECONDS" and "FRAMES" so changing + * units may also change the rate. */ ControlPanel & SetRefreshUnit(const std::string & units) { - Info()->SetUnit(units); + if (units == "MILLISECONDS") { + Info()->SetRefreshCheckerMilliseconds(); + } else if (units == "FRAMES") { + Info()->SetRefreshCheckerFrames(); + } return *this; } @@ -279,18 +324,35 @@ namespace emp::prefab { * @note rates are independent for "MILLISECONDS" and "FRAMES". */ void SetRefreshRate(const int & rate) { - Info()->SetRate(rate); + Info()->GetRefreshChecker().SetRate(rate); } /** * Set the refresh rate for this control panel. * @param rate the number of milliseconds or frames between refreshes * @param unit either "MILLISECONDS" or "FRAMES" - * @note rates are independent for "MILLISECONDS" and "FRAMES" + * @note rates are independent for "MILLISECONDS" and "FRAMES". + */ + void SetRefreshRate(const int & rate, const std::string & units) { + SetRefreshUnit(units); + SetRefreshRate(rate); + } + /** + * Give the control panel a custom method to determine what frames to + * refresh on. + * @param check an instance of a RefreshChecker + */ + ControlPanel & SetRefreshChecker(const RefreshChecker & check) { + Info()->SetRefreshChecker(check); + return *this; + } + + /** + * Gets the current refresh rate for the control panel. + * @return the current refresh rate */ - void SetRefreshRate( const int & rate, const std::string & units) { - Info()->SetUnit(units); - Info()->SetRate(rate); + int GetRate() const { + return Info()->GetRefreshChecker().GetRate(); } /** @@ -318,7 +380,7 @@ namespace emp::prefab { std::is_same::type, ToggleButtonGroup>::value) { button_line << std::forward(in_val); } else if constexpr(std::is_same::type, ButtonGroup>::value) { - button_line = std::forward(in_val); + button_line = std::forward(in_val); static_cast
(*this) << button_line; } else { static_cast
(*this) << std::forward(in_val); @@ -326,6 +388,6 @@ namespace emp::prefab { return (*this); } }; -} +} // namespace emp::prefab -#endif +#endif // #ifndef EMP_CONTROL_PANEL_HPP diff --git a/include/emp/prefab/ToggleButtonGroup.hpp b/include/emp/prefab/ToggleButtonGroup.hpp index 5091e81e2c..5083b4db1c 100644 --- a/include/emp/prefab/ToggleButtonGroup.hpp +++ b/include/emp/prefab/ToggleButtonGroup.hpp @@ -1,6 +1,18 @@ +/** + * @note This file is part of Empirical, https://github.com/devosoft/Empirical + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021 + * + * @file ToggleButtonGroup.hpp + * @brief ToggleButtonGroups maintain two button elements intended to represent two + * mutually exclusive states. + */ + #ifndef EMP_TOGGLE_BUTTON_GROUP_HPP #define EMP_TOGGLE_BUTTON_GROUP_HPP +#include +#include #include "emp/tools/string_utils.hpp" #include "emp/web/Div.hpp" @@ -9,62 +21,71 @@ #include "emp/prefab/ButtonGroup.hpp" namespace emp::prefab { +namespace internal { + using on_toggle_t = std::function; + /** + * Shared pointer held by instances of ToggleButtonGroup class representing + * the same conceptual ToggleButtonGroup DOM object. + * Contains state that should persist while ToggleButtonGroup DOM object + * persists. + */ + class ToggleButtonGroupInfo : public web::internal::DivInfo { + on_toggle_t callback; // A callback to be called when the component changes states + bool is_active; // Whether the toggle is in the activated or deactivated state + + public: + ToggleButtonGroupInfo(const std::string & in_id) + : web::internal::DivInfo(in_id), callback([](bool){ ; }), is_active(false) { ; } - namespace internal { - using on_toggle_t = std::function; /** - * Shared pointer held by instances of ToggleButtonGroup class representing - * the same conceptual ToggleButtonGroup DOM object. - * Contains state that should persist while ToggleButtonGroup DOM object - * persists. + * Set the function to be called when the component toggles + * @param cb a callback function that accepts a boolean indicating + * whether the toggle is active or inactive */ - class ToggleButtonGroupInfo : public web::internal::DivInfo { - on_toggle_t callback; - bool active; - - public: - ToggleButtonGroupInfo(const std::string & in_id) - : web::internal::DivInfo(in_id), callback([](bool){ ; }), active(false) { ; } - - /** - * Set the function to be called when the component toggles - * @param cb a callback function that accepts a boolean indicating - * whether the toggle is active or inactive - */ - void UpdateCallback(const on_toggle_t & cb) { - callback = cb; - } + void UpdateCallback(const on_toggle_t & cb) { + callback = cb; + } - /** - * Get the function to be called when the component toggles - * @return a callback function - */ - const on_toggle_t & GetCallback() const { - return callback; - } + /** + * Get the function to be called when the component toggles from activated + * to deactivated. + * @return a callback function + */ + const on_toggle_t & GetCallback() const { + return callback; + } - /** - * Determines whether the toggle is in the active state - * @return boolean - */ - bool IsActive() const { - return active; - } + /** + * Determines whether the toggle is in the active state + * @return boolean + */ + bool IsActive() const { + return is_active; + } - /** - * Set the active state of the toggle - * @param is_active a boolean - */ - void SetActive(const bool & is_active) { - active = is_active; - } - }; - } + /** + * Sets this toggle to activated. + */ + void SetActive() { + is_active = true; + } + + /** + * Sets this toggle to deactivated. + */ + void SetInactive() { + is_active = false; + } + }; +} // namespace internal /** * Use a ToggleButtonGroup to create a control with two labeled, visually * distinct states. Choose whether the button should display cassette-style * with two separate buttons or as a single button that flip-flops state. + * + * State can be accessed procedurally via IsActive() or in an event driven + * manner by setting a callback via SetCallback(). */ class ToggleButtonGroup : public ButtonGroup { using on_toggle_t = internal::on_toggle_t; @@ -89,6 +110,9 @@ namespace emp::prefab { protected: /** + * Protected constructor for a ToggleButton group. For internal use only. + * See the prefab/README.md for more information on this design pattern. + * * @param activate_indicator a string, FontAwesomeIcon or other component * indicating that the first button actives this toggle * @param deactivate_indicator a string, FontAwesomeIcon or other component @@ -109,8 +133,8 @@ namespace emp::prefab { L2_TYPE && deactivate_indicator, const std::string & activate_style, const std::string & deactivate_style, - const bool & cassette_style, - const bool & grayout, + bool cassette_style, + bool grayout, web::internal::DivInfo * info_ref ) : ButtonGroup(info_ref) { @@ -129,20 +153,19 @@ namespace emp::prefab { AddAttr("class", "grayout"); } - auto & on_toggle = GetCallback(); + auto & on_toggle = Info()->GetCallback(); activate_label.AddAttr( "class", "btn", "class", emp::to_string("btn-outline-", activate_style) ); activate_label.OnClick([tog=*this, &handle_toggle=on_toggle]() mutable { - tog.SetActive(true); + tog.SetActive(); handle_toggle(true); }); - // OnClick used due to a strange bug(?) in which the input radios only - // fire their onchange function once - // Probably due to Bootstrap/jQuery weirdness and this is a last minute - // work around ¯\_(ツ)_/¯ + // OnClick used due to a strange bug(?) in Javascript in which the input + // radios only fire their onchange function once. Probably due to some + // Bootstrap/jQuery weirdness. This is a last minute work around ¯\_(ツ)_/¯. web::Input activate_radio( [](std::string){ ; }, @@ -158,7 +181,7 @@ namespace emp::prefab { "class", emp::to_string("btn-outline-", deactivate_style) ); deactivate_label.OnClick([tog=*this, &handle_toggle=on_toggle]() mutable { - tog.SetActive(false); + tog.SetInactive(); handle_toggle(false); }); @@ -174,7 +197,7 @@ namespace emp::prefab { public: /** * @param activate_indicator a string, FontAwesomeIcon or other component - * indicating that the first button actives this toggle + * indicating that the first button activates this toggle * @param deactivate_indicator a string, FontAwesomeIcon or other component * indicating that the second button deactivates this toggle * @param activate_style a bootstrap style (primary, secondary, etc) for @@ -183,54 +206,46 @@ namespace emp::prefab { * the second button * @param cassette_style whether the toggle should display in cassette style * (both buttons visible) or do a swap on toggle (one button visible) - * @param grayout in cassette mode, whether buttons should be - * grayed out to further emphasize the current state + * @param grayout whether a grayscale filter should be used to further + * emphasize which button has been pressed * @param in_id HTML ID of ToggleButtonGroup div */ - template + template ToggleButtonGroup( - L1_TYPE && activate_indicator, - L2_TYPE && deactivate_indicator, + LABEL1_TYPE && activate_indicator, + LABEL2_TYPE && deactivate_indicator, const std::string & activate_style="success", const std::string & deactivate_style="warning", const bool & cassette_style=true, const bool & grayout=false, const std::string & in_id="" ) : ToggleButtonGroup( - std::forward(activate_indicator), - std::forward(deactivate_indicator), + std::forward(activate_indicator), + std::forward(deactivate_indicator), activate_style, deactivate_style, cassette_style, grayout, new internal::ToggleButtonGroupInfo(in_id) ) { ; } /** - * Determines whether the toggle is in the active state + * Determines whether the toggle is activated or deactivated. * @return boolean */ bool IsActive() const { return Info()->IsActive(); } - /** - * Set the active state of the toggle - * @param is_active a boolean - */ - void SetActive(const bool & val) { - Info()->SetActive(val); + void SetActive() { + Info()->SetActive(); } - /** - * Get the function to be called when the component toggles - * @return a callback function - */ - const on_toggle_t & GetCallback() const { - return Info()->GetCallback(); + void SetInactive() { + Info()->SetInactive(); } /** * Set the function to be called when the component toggles - * @param cb a callback function that accepts a boolean indicating + * @param cb a void callback function that accepts a boolean indicating * whether the toggle is active or inactive */ ToggleButtonGroup & SetCallback(const on_toggle_t & cb) { @@ -238,6 +253,6 @@ namespace emp::prefab { return (*this); } }; -} +} // namespace emp::prefab -#endif +#endif // #ifndef EMP_TOGGLE_BUTTON_GROUP_HPP diff --git a/tests/web/ControlPanel.cpp b/tests/web/ControlPanel.cpp index 9ac5f05fd0..1bb1f8de74 100644 --- a/tests/web/ControlPanel.cpp +++ b/tests/web/ControlPanel.cpp @@ -13,7 +13,7 @@ // and ToggleButtonGroups struct Test_Control_Panel : emp::web::BaseTest { /* - * Creates extra div + the following control panel structure + * Creates the following control panel structure * +------------------+------+-----+-------------------+ +---+---+---+ +--+ * | +------+-------+ | | | +------+--------+ | | | | | | | * | | Play | Pause | | Step | C | | Auto | Manual | | | D | A | B | | | @@ -50,7 +50,7 @@ struct Test_Control_Panel : emp::web::BaseTest { }); emp::prefab::ButtonGroup real{"real"}; real << emp::web::Button([](){;}, "D", "d_button"); - real.TakeChildren(husk); + real.TakeChildren(std::forward(husk)); cp << real; cp << husk; From e4f3f2a379c09871fb2bad7a8f758d2662a77036 Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Fri, 3 Sep 2021 14:52:51 -0700 Subject: [PATCH 25/46] More documentation --- include/emp/prefab/ControlPanel.hpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index be5a30ba77..4c6b468ee8 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -4,14 +4,20 @@ * @date 2021 * * @file ControlPanel.hpp - * @brief A ControlPanel manages ... + * @brief A ControlPanel manages a container for buttons and an Animate instance + * that can run a callback function every frame and redraw a list of Widgets + * based on frames or milliseconds elapsed and a specified rate. + * + * @todo The default control panel should eventually contain a select element + * to choose the rate unit and a numeric input for the rate. The two rates + * should be independent so that changing the unit to e.g. FRAMES will change + * the input to the FRAMES rate. */ #ifndef EMP_CONTROL_PANEL_HPP #define EMP_CONTROL_PANEL_HPP #include -#include #include #include "emp/base/optional.hpp" From ca489f393b8d2bddb8cf606fce91f6c742500130 Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Fri, 3 Sep 2021 16:07:23 -0700 Subject: [PATCH 26/46] Simplify some stuff --- include/emp/prefab/ControlPanel.hpp | 45 +++++++----------------- include/emp/prefab/ToggleButtonGroup.hpp | 42 ++++++++++++---------- tests/web/ToggleButtonGroup.cpp | 3 +- 3 files changed, 37 insertions(+), 53 deletions(-) diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index 4c6b468ee8..f8a90a64eb 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -54,12 +54,12 @@ namespace internal { */ class RefreshChecker { protected: - int rate; + int rate = 0; public: - RefreshChecker(int r) { rate = r; } virtual bool operator()(const web::Animate &) { + std::cout << "Base" << std::endl; return true; - } + }; void SetRate(int r) { rate = r; } @@ -67,12 +67,12 @@ namespace internal { }; // ControlPanelInfo holds contains two specific instances of a - // RefreshChecker for milliseconds and frames to keep rates independent - // for each. + // RefreshChecker for milliseconds and frames to keep independent rates + // for both. class : public RefreshChecker { - using RefreshChecker::RefreshChecker; + public: int elapsed_milliseconds = 0; - bool operator()(const web::Animate & anim) { + bool operator()(const web::Animate & anim) override { elapsed_milliseconds += anim.GetStepTime(); if (elapsed_milliseconds > rate) { elapsed_milliseconds -= rate; @@ -81,21 +81,21 @@ namespace internal { } return false; } - } millisecond_refresher{100}; + } millisecond_refresher; class : public RefreshChecker { - using RefreshChecker::RefreshChecker; - bool operator()(const web::Animate & anim) { + public: + bool operator()(const web::Animate & anim) override { return anim.GetFrameCount() % rate; } - } frame_refresher{5}; + } frame_refresher; // The current redraw checker function - RefreshChecker should_redraw; + RefreshChecker & should_redraw; // A list of widget that should be redrawn when do_redraw return true emp::vector refresh_list; - // A function to run every frame (as fast as possible) + // A void callback function to run every frame (as fast as possible) std::function step_callback; /** @@ -141,15 +141,6 @@ namespace internal { return refresh_list; } - public: - /** - * Set the redraw checker function - * @param check a redraw checker - */ - void SetRefreshChecker(const RefreshChecker & check) { - should_redraw = check; - } - /** * Set the redraw checker function to milliseconds instance */ @@ -224,7 +215,6 @@ namespace internal { toggle_run{ FontAwesomeIcon{"fa-play"}, FontAwesomeIcon{"fa-pause"}, "success", "warning", - true, false, emp::to_string(GetID(), "_main_toggle") }, button_line(ButtonGroup{emp::to_string(GetID(), "_main")}), @@ -343,15 +333,6 @@ namespace internal { SetRefreshUnit(units); SetRefreshRate(rate); } - /** - * Give the control panel a custom method to determine what frames to - * refresh on. - * @param check an instance of a RefreshChecker - */ - ControlPanel & SetRefreshChecker(const RefreshChecker & check) { - Info()->SetRefreshChecker(check); - return *this; - } /** * Gets the current refresh rate for the control panel. diff --git a/include/emp/prefab/ToggleButtonGroup.hpp b/include/emp/prefab/ToggleButtonGroup.hpp index 5083b4db1c..c28a81363c 100644 --- a/include/emp/prefab/ToggleButtonGroup.hpp +++ b/include/emp/prefab/ToggleButtonGroup.hpp @@ -121,10 +121,6 @@ namespace internal { * the first button * @param deactivate_style a bootstrap style (primary, secondary, etc) for * the second button - * @param cassette_style whether the toggle should display in cassette style - * (both buttons visible) or do a swap on toggle (one button visible) - * @param grayout in cassette mode, whether buttons should be - * grayed out to further emphasize the current state * @param info_ref shared pointer containing presistent state */ template @@ -133,8 +129,6 @@ namespace internal { L2_TYPE && deactivate_indicator, const std::string & activate_style, const std::string & deactivate_style, - bool cassette_style, - bool grayout, web::internal::DivInfo * info_ref ) : ButtonGroup(info_ref) { @@ -147,12 +141,6 @@ namespace internal { *this << activate_label; *this << deactivate_label; - if (!cassette_style) { - AddAttr("class", "hide_inactive"); - } else if (grayout) { - AddAttr("class", "grayout"); - } - auto & on_toggle = Info()->GetCallback(); activate_label.AddAttr( @@ -204,10 +192,6 @@ namespace internal { * the first button * @param deactivate_style a bootstrap style (primary, secondary, etc) for * the second button - * @param cassette_style whether the toggle should display in cassette style - * (both buttons visible) or do a swap on toggle (one button visible) - * @param grayout whether a grayscale filter should be used to further - * emphasize which button has been pressed * @param in_id HTML ID of ToggleButtonGroup div */ template @@ -216,14 +200,11 @@ namespace internal { LABEL2_TYPE && deactivate_indicator, const std::string & activate_style="success", const std::string & deactivate_style="warning", - const bool & cassette_style=true, - const bool & grayout=false, const std::string & in_id="" ) : ToggleButtonGroup( std::forward(activate_indicator), std::forward(deactivate_indicator), activate_style, deactivate_style, - cassette_style, grayout, new internal::ToggleButtonGroupInfo(in_id) ) { ; } @@ -235,10 +216,16 @@ namespace internal { return Info()->IsActive(); } + /** + * Sets the state of the toggle to active. + */ void SetActive() { Info()->SetActive(); } + /** + * Sets the state of the toggle to inactive. + */ void SetInactive() { Info()->SetInactive(); } @@ -252,6 +239,23 @@ namespace internal { Info()->UpdateCallback(cb); return (*this); } + + /** + * Change styling from cassette style (buttons side-by-side) to single + * button style so that button will swap between the two indicators. + */ + ToggleButtonGroup & Compress() { + AddAttr("class", "hide_inactive"); + return (*this); + } + + /** + * Add a grayscale filter to further emphasize the current state of the toggle. + */ + ToggleButtonGroup & Grayout() { + AddAttr("class", "grayout"); + return (*this); + } }; } // namespace emp::prefab diff --git a/tests/web/ToggleButtonGroup.cpp b/tests/web/ToggleButtonGroup.cpp index 5a186db166..261b32af1c 100644 --- a/tests/web/ToggleButtonGroup.cpp +++ b/tests/web/ToggleButtonGroup.cpp @@ -12,8 +12,7 @@ struct Test_Toggle_Button_Group : emp::web::BaseTest { : BaseTest({"emp_test_container"}) { emp::prefab::ToggleButtonGroup icon_and_string( emp::prefab::FontAwesomeIcon{"fa-play"}, "Pause", - "primary", "secondary", - true, false, "icon_and_string" + "primary", "secondary", "icon_and_string" ); Doc("emp_test_container") << icon_and_string; } From 2aac8acb9a3322cad54c4f12db87a7a3f0324535 Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Fri, 3 Sep 2021 16:09:31 -0700 Subject: [PATCH 27/46] Remove cout --- include/emp/prefab/ControlPanel.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index f8a90a64eb..9deb9a8ac7 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -57,7 +57,6 @@ namespace internal { int rate = 0; public: virtual bool operator()(const web::Animate &) { - std::cout << "Base" << std::endl; return true; }; void SetRate(int r) { From c9abe28527dd25464670395f94b3e39bfca15786 Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Thu, 9 Sep 2021 10:30:57 -0700 Subject: [PATCH 28/46] More descriptive template types --- include/emp/prefab/ToggleButtonGroup.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/emp/prefab/ToggleButtonGroup.hpp b/include/emp/prefab/ToggleButtonGroup.hpp index c28a81363c..4d0a61bf86 100644 --- a/include/emp/prefab/ToggleButtonGroup.hpp +++ b/include/emp/prefab/ToggleButtonGroup.hpp @@ -123,10 +123,10 @@ namespace internal { * the second button * @param info_ref shared pointer containing presistent state */ - template + template ToggleButtonGroup( - L1_TYPE && activate_indicator, - L2_TYPE && deactivate_indicator, + LABEL1_TYPE && activate_indicator, + LABEL2_TYPE && deactivate_indicator, const std::string & activate_style, const std::string & deactivate_style, web::internal::DivInfo * info_ref From 8ba64558330e94874b84f3cd3ab80ac4fabf39ec Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Thu, 9 Sep 2021 10:39:51 -0700 Subject: [PATCH 29/46] Explain DivInfo constructor for Div --- include/emp/web/Div.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/emp/web/Div.hpp b/include/emp/web/Div.hpp index a7a66d899e..93ac56574c 100644 --- a/include/emp/web/Div.hpp +++ b/include/emp/web/Div.hpp @@ -312,6 +312,10 @@ namespace web { // Get a properly cast version of info. internal::DivInfo * Info() { return (internal::DivInfo *) info; } const internal::DivInfo * Info() const { return (internal::DivInfo *) info; } + + // A constructor using a DivInfo pointer allows us to pass down derived + // class pointers when we need to extend the functionality of a component + // see the prefab/README.md for more about this design pattern Div(internal::DivInfo * in_info) : WidgetFacet(in_info) { ; } public: From e3db915627408782b67f0b170c1e65aa3e44594e Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Wed, 22 Sep 2021 10:26:41 -0700 Subject: [PATCH 30/46] Use map to hold checker instances --- include/emp/prefab/ControlPanel.hpp | 83 +++++++++++++++--------- include/emp/prefab/ToggleButtonGroup.hpp | 4 +- 2 files changed, 56 insertions(+), 31 deletions(-) diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index 9deb9a8ac7..7afe108f16 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -19,6 +19,7 @@ #include #include +#include #include "emp/base/optional.hpp" #include "emp/base/vector.hpp" @@ -65,10 +66,7 @@ namespace internal { int GetRate() const { return rate; } }; - // ControlPanelInfo holds contains two specific instances of a - // RefreshChecker for milliseconds and frames to keep independent rates - // for both. - class : public RefreshChecker { + class MillisecondRefreshChecker: public RefreshChecker { public: int elapsed_milliseconds = 0; bool operator()(const web::Animate & anim) override { @@ -80,16 +78,25 @@ namespace internal { } return false; } - } millisecond_refresher; - class : public RefreshChecker { + }; + + class FrameRefreshChecker: public RefreshChecker { public: bool operator()(const web::Animate & anim) override { return anim.GetFrameCount() % rate; } - } frame_refresher; + }; + + // ControlPanelInfo holds contains two specific instances of a + // RefreshChecker for milliseconds and frames to keep independent rates + // for both. + std::unordered_map refresh_checkers{ + { "MILLISECONDS", new MillisecondRefreshChecker{} }, + { "FRAMES", new FrameRefreshChecker{} } + }; // The current redraw checker function - RefreshChecker & should_redraw; + RefreshChecker * should_redraw; // A list of widget that should be redrawn when do_redraw return true emp::vector refresh_list; @@ -104,14 +111,22 @@ namespace internal { */ ControlPanelInfo(const std::string & in_id="") : DivInfo(in_id), - should_redraw(millisecond_refresher), + should_redraw(refresh_checkers.at("MILLISECONDS")), step_callback([](){ ; }) { ; } + /** + * Desctructor for the shared pointer managing the ControlPanel's state. + * Cleans up any pointers to new RefreshCheckers. + */ + ~ControlPanelInfo() { + for (auto & p : anim_map) delete p.second; + } + /** * Get a constant reference to the redraw checker function * @return a redraw checker */ - const RefreshChecker & GetRefreshChecker() const { + const RefreshChecker * GetRefreshChecker() const { return should_redraw; } @@ -119,10 +134,20 @@ namespace internal { * Get a reference to the redraw checker function * @return a redraw checker */ - RefreshChecker & GetRefreshChecker() { + RefreshChecker * GetRefreshChecker() { return should_redraw; } + /** + * Adds a new refresh checker to the list of those available. + * @param name a name for the checker + * @param checker an instance of a functor inheriting from emp::prefab::internal::RefreshChecker + */ + template + void AddRefreshChecker(const std::string & name, SPECIALIZED_REF_CHECK && checker) { + refresh_checkers[name] = new SPECIALIZED_REF_CHECK{std::move(checker)}; + } + /** * Gets a void callback function that will be called every frame (as often as possible). * @return the void callback function to advance the state of some simulation or process @@ -141,17 +166,10 @@ namespace internal { } /** - * Set the redraw checker function to milliseconds instance - */ - void SetRefreshCheckerMilliseconds() { - should_redraw = millisecond_refresher; - } - - /** - * Set the redraw checker function to frames instance + * Set the redraw checker function to the one specified. */ - void SetRefreshCheckerFrames() { - should_redraw = frame_refresher; + void SetRefreshChecker(const std::string & checker_name) { + should_redraw = refresh_checkers.at(checker_name); } /** @@ -242,7 +260,7 @@ namespace internal { AddAnimation(GetID(), [&step_simulation=Info()->GetStepCallback(), &refresh_list=Info()->GetRefreshList(), - &should_redraw=Info()->GetRefreshChecker()] + &should_redraw=*(Info()->GetRefreshChecker())] (const web::Animate & anim) mutable { // Run the simulation function every frame step_simulation(); @@ -298,6 +316,17 @@ namespace internal { return *this; } + /** + * Adds a new refresh checker to the list of those available. + * @param name a name for the checker + * @param checker an instance of a functor inheriting from + * emp::prefab::ControlPanel::RefreshChecker + */ + template + void AddRefreshMode(const std::string & name, SPECIALIZED_REF_CHECK && checker) { + Info()->AddRefreshChecker(name, std::forward(checker)); + } + /** * Set the refresh rate units for this control panel. * @param unit either "MILLISECONDS" or "FRAMES" @@ -305,11 +334,7 @@ namespace internal { * units may also change the rate. */ ControlPanel & SetRefreshUnit(const std::string & units) { - if (units == "MILLISECONDS") { - Info()->SetRefreshCheckerMilliseconds(); - } else if (units == "FRAMES") { - Info()->SetRefreshCheckerFrames(); - } + Info()->SetRefreshChecker(units); return *this; } @@ -319,7 +344,7 @@ namespace internal { * @note rates are independent for "MILLISECONDS" and "FRAMES". */ void SetRefreshRate(const int & rate) { - Info()->GetRefreshChecker().SetRate(rate); + Info()->GetRefreshChecker()->SetRate(rate); } /** @@ -338,7 +363,7 @@ namespace internal { * @return the current refresh rate */ int GetRate() const { - return Info()->GetRefreshChecker().GetRate(); + return Info()->GetRefreshChecker()->GetRate(); } /** diff --git a/include/emp/prefab/ToggleButtonGroup.hpp b/include/emp/prefab/ToggleButtonGroup.hpp index 4d0a61bf86..40c16296ce 100644 --- a/include/emp/prefab/ToggleButtonGroup.hpp +++ b/include/emp/prefab/ToggleButtonGroup.hpp @@ -161,7 +161,7 @@ namespace internal { false, false ); activate_label << activate_radio; - activate_label << std::forward(activate_indicator); + activate_label << std::forward(activate_indicator); deactivate_label.AddAttr( "class", "active", @@ -179,7 +179,7 @@ namespace internal { false, true ); deactivate_label << deactivate_radio; - deactivate_label << std::forward(deactivate_indicator); + deactivate_label << std::forward(deactivate_indicator); } public: From 6be2bb2b5971bc4a343f18ec210012ae6f8c2e8e Mon Sep 17 00:00:00 2001 From: Tait Weicht Date: Mon, 27 Sep 2021 10:54:14 -0700 Subject: [PATCH 31/46] Name change for clarity --- include/emp/prefab/ControlPanel.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index 7afe108f16..2b59b4f91d 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -143,9 +143,9 @@ namespace internal { * @param name a name for the checker * @param checker an instance of a functor inheriting from emp::prefab::internal::RefreshChecker */ - template - void AddRefreshChecker(const std::string & name, SPECIALIZED_REF_CHECK && checker) { - refresh_checkers[name] = new SPECIALIZED_REF_CHECK{std::move(checker)}; + template + void AddRefreshChecker(const std::string & name, SPECIALIZED_REFRESH_CHECKER && checker) { + refresh_checkers[name] = new SPECIALIZED_REFRESH_CHECKER{std::move(checker)}; } /** @@ -322,9 +322,9 @@ namespace internal { * @param checker an instance of a functor inheriting from * emp::prefab::ControlPanel::RefreshChecker */ - template - void AddRefreshMode(const std::string & name, SPECIALIZED_REF_CHECK && checker) { - Info()->AddRefreshChecker(name, std::forward(checker)); + template + void AddRefreshMode(const std::string & name, SPECIALIZED_REFRESH_CHECKER && checker) { + Info()->AddRefreshChecker(name, std::forward(checker)); } /** From 0ef6bebe3ca7a4286cc879121037cd5c66819e47 Mon Sep 17 00:00:00 2001 From: Matthew Andres Moreno Date: Tue, 28 Sep 2021 21:25:17 -0400 Subject: [PATCH 32/46] Include string header --- include/emp/prefab/ToggleButtonGroup.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/emp/prefab/ToggleButtonGroup.hpp b/include/emp/prefab/ToggleButtonGroup.hpp index 40c16296ce..58ccb9e173 100644 --- a/include/emp/prefab/ToggleButtonGroup.hpp +++ b/include/emp/prefab/ToggleButtonGroup.hpp @@ -12,6 +12,7 @@ #define EMP_TOGGLE_BUTTON_GROUP_HPP #include +#include #include #include "emp/tools/string_utils.hpp" From 7bab656f55197f7ec79ae75bcc6a16a0f1f627b7 Mon Sep 17 00:00:00 2001 From: Matthew Andres Moreno Date: Tue, 28 Sep 2021 21:30:21 -0400 Subject: [PATCH 33/46] Include string header --- include/emp/prefab/ControlPanel.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index 2b59b4f91d..29c81a8775 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -18,6 +18,7 @@ #define EMP_CONTROL_PANEL_HPP #include +#include #include #include From 9179f2e830c3995555e3b932728339c86f634fcb Mon Sep 17 00:00:00 2001 From: "mmore500.login+git@gmail.com" Date: Tue, 28 Sep 2021 23:13:36 -0400 Subject: [PATCH 34/46] Implement and test DisjointVariant --- include/emp/datastructs/DisjointVariant.hpp | 65 +++++++++++++++++++++ include/emp/polyfill/type_identity.hpp | 23 ++++++++ tests/datastructs/DisjointVariant.cpp | 26 +++++++++ tests/datastructs/Makefile | 2 +- 4 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 include/emp/datastructs/DisjointVariant.hpp create mode 100644 include/emp/polyfill/type_identity.hpp create mode 100644 tests/datastructs/DisjointVariant.cpp diff --git a/include/emp/datastructs/DisjointVariant.hpp b/include/emp/datastructs/DisjointVariant.hpp new file mode 100644 index 0000000000..09776bc505 --- /dev/null +++ b/include/emp/datastructs/DisjointVariant.hpp @@ -0,0 +1,65 @@ +/** + * @note This file is part of Empirical, https://github.com/devosoft/Empirical + * @copyright Copyright (C) Michigan State University, MIT Software license; see doc/LICENSE.md + * @date 2021 + * + * @file DisjointVariant.hpp + * @brief A container similar to std::variant, where only one of a set of + * types can be active, but state is maintained for inactive types (they are + * not destructed or overwritten). + */ + + +#ifndef EMP_DISJOINT_VARIANT_HPP + +#include +#include +#include + +#include "../polyfill/type_identity.hpp" + +namespace emp { + +template +class DisjointVariant { + + // Holds state for each member. + std::tuple disjoint_data; + + /// Tracks which type is active. + std::variant...> active_typeid; + + public: + + /// Forwarding constructor. + template + DisjointVariant(Args&&... args) + : disjoint_data(std::forward(args)...) + {} + + /// Switch which data member is active. + template + void Activate() { + using wrapped_active_type_t = std::type_identity; + active_typeid.template emplace(); + } + + /// Wraps std::visit to execute visitor on active data member. + template + decltype(auto) Visit(Visitor&& visitor) { + return std::visit( + [this, &visitor]( const auto& typeid_ ){ + using wrapped_active_type_t = std::decay_t; + using active_type_t = typename wrapped_active_type_t::type; + auto& active_data = std::get( disjoint_data ); + return std::forward(visitor)(active_data); + }, + active_typeid + ); + } + +}; + +} // namespace emp + +#endif diff --git a/include/emp/polyfill/type_identity.hpp b/include/emp/polyfill/type_identity.hpp new file mode 100644 index 0000000000..221cc0b08b --- /dev/null +++ b/include/emp/polyfill/type_identity.hpp @@ -0,0 +1,23 @@ +#ifndef POLYFILL_TYPE_IDENTITY_H +#define POLYFILL_TYPE_IDENTITY_H + +#if __cplusplus <= 201703L + +// TODO: C++20 || cpp20 +namespace std { + + // adapted from https://en.cppreference.com/w/cpp/types/type_identity + template< class T > + struct type_identity { + using type = T; + }; + +} + +#else // #if __cplusplus <= 201703L + +#include + +#endif // #if __cplusplus <= 201703L + +#endif // #ifndef POLYFILL_TYPE_IDENTITY_H diff --git a/tests/datastructs/DisjointVariant.cpp b/tests/datastructs/DisjointVariant.cpp new file mode 100644 index 0000000000..8aed95332f --- /dev/null +++ b/tests/datastructs/DisjointVariant.cpp @@ -0,0 +1,26 @@ +#define CATCH_CONFIG_MAIN + +#include "third-party/Catch/single_include/catch2/catch.hpp" + +#include "emp/datastructs/DisjointVariant.hpp" + +#include +#include + +TEST_CASE("Test DisjointVariant", "[datastructs]") { + + emp::DisjointVariant disjoint_variant{10, 8.2}; + + disjoint_variant.Visit( [](auto&& v){ REQUIRE( v == 10 ); } ); + + // write a new value into disjoint_variant's int slot + disjoint_variant.Visit( [](auto&& v){ v = 42; } ); + disjoint_variant.Visit( [](auto&& v){ REQUIRE( v == 42 ); } ); + + disjoint_variant.Activate(); + disjoint_variant.Visit( [](auto&& v){ REQUIRE( v == 8.2 ); } ); + + disjoint_variant.Activate(); + disjoint_variant.Visit( [](auto&& v){ REQUIRE( v == 42 ); } ); + +} diff --git a/tests/datastructs/Makefile b/tests/datastructs/Makefile index 8ade0e9885..859bfd9f2e 100644 --- a/tests/datastructs/Makefile +++ b/tests/datastructs/Makefile @@ -1,4 +1,4 @@ -TEST_NAMES = BloomFilter Bool Cache DynamicString Graph graph_utils hash_utils IndexMap map_utils QueueCache ra_set reference_vector set_utils SmallFifoMap SmallVector StringMap TimeQueue tuple_struct tuple_utils TypeMap UnorderedIndexMap valsort_map vector_utils +TEST_NAMES = BloomFilter Bool Cache DisjointVariant DynamicString Graph graph_utils hash_utils IndexMap map_utils QueueCache ra_set reference_vector set_utils SmallFifoMap SmallVector StringMap TimeQueue tuple_struct tuple_utils TypeMap UnorderedIndexMap valsort_map vector_utils # -O3 -Wl,--stack,8388608 -ftrack-macro-expansion=0 FLAGS = -std=c++17 -g -pthread -Wall -Wno-unused-function -Wno-unused-private-field -I../../include/ -I../../ -I../../third-party/cereal/include/ From 30e0ed15173119fc46434045b29a9a1b41f9f853 Mon Sep 17 00:00:00 2001 From: "mmore500.login+git@gmail.com" Date: Tue, 28 Sep 2021 23:14:46 -0400 Subject: [PATCH 35/46] Use relative path for in-library includes --- include/emp/prefab/ControlPanel.hpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index 29c81a8775..bda982d7fe 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -22,18 +22,15 @@ #include #include -#include "emp/base/optional.hpp" -#include "emp/base/vector.hpp" - -#include "emp/prefab/ButtonGroup.hpp" -#include "emp/prefab/FontAwesomeIcon.hpp" -#include "emp/prefab/ToggleButtonGroup.hpp" - -#include "emp/tools/string_utils.hpp" - -#include "emp/web/Animate.hpp" -#include "emp/web/Element.hpp" -#include "emp/web/Div.hpp" +#include "../base/optional.hpp" +#include "../base/vector.hpp" +#include "../prefab/ButtonGroup.hpp" +#include "../prefab/FontAwesomeIcon.hpp" +#include "../prefab/ToggleButtonGroup.hpp" +#include "../tools/string_utils.hpp" +#include "../web/Animate.hpp" +#include "../web/Element.hpp" +#include "../web/Div.hpp" namespace emp::prefab { From 99279765e4c5b743a1c2037f48c0d95d46175f6b Mon Sep 17 00:00:00 2001 From: "mmore500.login+git@gmail.com" Date: Tue, 28 Sep 2021 23:56:05 -0400 Subject: [PATCH 36/46] Fix duplicated constructor --- include/emp/web/Div.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/emp/web/Div.hpp b/include/emp/web/Div.hpp index e209c706c1..ea0f87541b 100644 --- a/include/emp/web/Div.hpp +++ b/include/emp/web/Div.hpp @@ -312,7 +312,6 @@ namespace web { // Get a properly cast version of info. internal::DivInfo * Info() { return (internal::DivInfo *) info; } const internal::DivInfo * Info() const { return (internal::DivInfo *) info; } - Div(internal::DivInfo * in_info) : WidgetFacet(in_info) { ; } // A constructor using a DivInfo pointer allows us to pass down derived // class pointers when we need to extend the functionality of a component From 30ae1ca185c5f60d03f27cecfec83989785e8daf Mon Sep 17 00:00:00 2001 From: "mmore500.login+git@gmail.com" Date: Wed, 29 Sep 2021 00:20:33 -0400 Subject: [PATCH 37/46] Add missing header --- include/emp/datastructs/DisjointVariant.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/emp/datastructs/DisjointVariant.hpp b/include/emp/datastructs/DisjointVariant.hpp index 09776bc505..576fae494b 100644 --- a/include/emp/datastructs/DisjointVariant.hpp +++ b/include/emp/datastructs/DisjointVariant.hpp @@ -14,6 +14,7 @@ #include #include +#include #include #include "../polyfill/type_identity.hpp" From e65067c899a7b6fc53dc81c2b640f592141d496f Mon Sep 17 00:00:00 2001 From: "mmore500.login+git@gmail.com" Date: Wed, 29 Sep 2021 00:20:54 -0400 Subject: [PATCH 38/46] Add Assign and Activate methods --- include/emp/datastructs/DisjointVariant.hpp | 23 ++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/include/emp/datastructs/DisjointVariant.hpp b/include/emp/datastructs/DisjointVariant.hpp index 576fae494b..6730aeb740 100644 --- a/include/emp/datastructs/DisjointVariant.hpp +++ b/include/emp/datastructs/DisjointVariant.hpp @@ -24,7 +24,7 @@ namespace emp { template class DisjointVariant { - // Holds state for each member. + // Holds state for each element. std::tuple disjoint_data; /// Tracks which type is active. @@ -38,14 +38,27 @@ class DisjointVariant { : disjoint_data(std::forward(args)...) {} - /// Switch which data member is active. + /// Switch which data element is active. template void Activate() { - using wrapped_active_type_t = std::type_identity; - active_typeid.template emplace(); + using wrapped_active_type_t = std::type_identity; + active_typeid.template emplace(); } - /// Wraps std::visit to execute visitor on active data member. + /// Assign to data element. + template + void AssignToElement(T&& val) { + std::get(disjoint_data) = std::forward(val); + } + + /// Assign data element and set that element as active. + template + void AssignAndActivate(T&& val) { + AssignToElement( std::forward(val) ); + Activate(); + } + + /// Wraps std::visit to execute visitor on active data element. template decltype(auto) Visit(Visitor&& visitor) { return std::visit( From b1509009158bc9496b660e60f3c163f03659716a Mon Sep 17 00:00:00 2001 From: "mmore500.login+git@gmail.com" Date: Wed, 29 Sep 2021 00:21:33 -0400 Subject: [PATCH 39/46] Refactor ControlPanel to use DisjointVariant --- include/emp/prefab/ControlPanel.hpp | 244 ++++++++++++---------------- tests/web/ControlPanel.cpp | 8 +- 2 files changed, 105 insertions(+), 147 deletions(-) diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index bda982d7fe..6bd1aba7da 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -24,6 +24,7 @@ #include "../base/optional.hpp" #include "../base/vector.hpp" +#include "../datastructs/DisjointVariant.hpp" #include "../prefab/ButtonGroup.hpp" #include "../prefab/FontAwesomeIcon.hpp" #include "../prefab/ToggleButtonGroup.hpp" @@ -36,6 +37,55 @@ namespace emp::prefab { class ControlPanel; +/** + * A RefreshChecker is a functor that accepts an Animate reference + * and returns a boolean indicating whether components should be redrawn + * based on an internal "rate" for redrawing. + */ +class MillisecondRefreshChecker { + + int refresh_rate{}; + int elapsed_milliseconds{}; + + public: + + explicit MillisecondRefreshChecker(const int refresh_rate_=0) + : refresh_rate(refresh_rate_) + {} + + bool ShouldRefresh(const web::Animate & anim) { + elapsed_milliseconds += anim.GetStepTime(); + if (elapsed_milliseconds > refresh_rate) { + elapsed_milliseconds -= refresh_rate; + if (elapsed_milliseconds > refresh_rate) elapsed_milliseconds = 0; + return true; + } + return false; + } + +}; + +/** + * A RefreshChecker is a functor that accepts an Animate reference + * and returns a boolean indicating whether components should be redrawn + * based on an internal "rate" for redrawing. + */ +class FrameRefreshChecker { + + int refresh_rate{}; + + public: + + explicit FrameRefreshChecker(const int refresh_rate_=0) + : refresh_rate(refresh_rate_) + {} + + bool ShouldRefresh(const web::Animate & anim) { + return anim.GetFrameCount() % refresh_rate; + } + +}; + namespace internal { /** @@ -46,55 +96,14 @@ namespace internal { */ class ControlPanelInfo : public web::internal::DivInfo { friend ControlPanel; - /** - * A RefreshChecker is a functor that accepts an Animate reference - * and returns a boolean indicating whether components should be redrawn - * based on an internal "rate" for redrawing. - */ - class RefreshChecker { - protected: - int rate = 0; - public: - virtual bool operator()(const web::Animate &) { - return true; - }; - void SetRate(int r) { - rate = r; - } - int GetRate() const { return rate; } - }; - - class MillisecondRefreshChecker: public RefreshChecker { - public: - int elapsed_milliseconds = 0; - bool operator()(const web::Animate & anim) override { - elapsed_milliseconds += anim.GetStepTime(); - if (elapsed_milliseconds > rate) { - elapsed_milliseconds -= rate; - if (elapsed_milliseconds > rate) elapsed_milliseconds = 0; - return true; - } - return false; - } - }; - - class FrameRefreshChecker: public RefreshChecker { - public: - bool operator()(const web::Animate & anim) override { - return anim.GetFrameCount() % rate; - } - }; // ControlPanelInfo holds contains two specific instances of a // RefreshChecker for milliseconds and frames to keep independent rates // for both. - std::unordered_map refresh_checkers{ - { "MILLISECONDS", new MillisecondRefreshChecker{} }, - { "FRAMES", new FrameRefreshChecker{} } - }; - - // The current redraw checker function - RefreshChecker * should_redraw; + emp::DisjointVariant< + emp::prefab::MillisecondRefreshChecker, + emp::prefab::FrameRefreshChecker + > checkers; // A list of widget that should be redrawn when do_redraw return true emp::vector refresh_list; @@ -109,51 +118,37 @@ namespace internal { */ ControlPanelInfo(const std::string & in_id="") : DivInfo(in_id), - should_redraw(refresh_checkers.at("MILLISECONDS")), step_callback([](){ ; }) { ; } - /** - * Desctructor for the shared pointer managing the ControlPanel's state. - * Cleans up any pointers to new RefreshCheckers. - */ - ~ControlPanelInfo() { - for (auto & p : anim_map) delete p.second; - } - /** * Get a constant reference to the redraw checker function * @return a redraw checker */ - const RefreshChecker * GetRefreshChecker() const { - return should_redraw; + bool ShouldRefresh(const emp::web::Animate& anim) { + return checkers.Visit([&anim](auto&& active_checker){ + return active_checker.ShouldRefresh( anim ); + }); } /** - * Get a reference to the redraw checker function - * @return a redraw checker + * Configure a checker but do not activate it. */ - RefreshChecker * GetRefreshChecker() { - return should_redraw; + template + void ConfigureRefreshChecker(Checker&& checker) { + checkers.AssignToElement( std::forward(checker) ); } /** - * Adds a new refresh checker to the list of those available. - * @param name a name for the checker - * @param checker an instance of a functor inheriting from emp::prefab::internal::RefreshChecker + * Specify which refresh checker to use. */ - template - void AddRefreshChecker(const std::string & name, SPECIALIZED_REFRESH_CHECKER && checker) { - refresh_checkers[name] = new SPECIALIZED_REFRESH_CHECKER{std::move(checker)}; - } + template + void ActivateRefreshChecker() { checkers.Activate(); } + /** - * Gets a void callback function that will be called every frame (as often as possible). - * @return the void callback function to advance the state of some simulation or process - * by one update every call. + * Calls the void callback function that will be called every frame (as often as possible). */ - const std::function & GetStepCallback() const { - return step_callback; - } + void CallStepCallback() const { step_callback(); } /** * Get the refresh list for this control panel @@ -163,13 +158,6 @@ namespace internal { return refresh_list; } - /** - * Set the redraw checker function to the one specified. - */ - void SetRefreshChecker(const std::string & checker_name) { - should_redraw = refresh_checkers.at(checker_name); - } - /** * Sets a void callback function that will be called every frame (as often as possible). * @param step the void callback function to advance the state of some simulation or process @@ -178,6 +166,7 @@ namespace internal { void SetStepCallback(const std::function & step) { step_callback = step; } + }; } // namspace internal @@ -188,9 +177,6 @@ namespace internal { * functionality to the control panel. */ class ControlPanel : public web::Div { - public: - using RefreshChecker = internal::ControlPanelInfo::RefreshChecker; - private: /** * Get shared info pointer, cast to ControlPanel-specific type. @@ -218,13 +204,13 @@ namespace internal { * and event handlers. For internal use only. See the prefab/README.md for * more information on this design pattern. * - * @param refresh_mode units of "MILLISECONDS" or "FRAMES" - * @param refresh_rate the number of milliseconds or frames between refreshes + * @param checker refresh checker to use + * (i.e., emp::MillisecondRefreshChecker or emp::FrameRefreshChecker) * @param in_info info object associated with this component */ + template< typename RefreshChecker > ControlPanel( - const int & refresh_rate, - const std::string & refresh_unit, + RefreshChecker&& checker, web::internal::DivInfo * in_info ) : web::Div(in_info), toggle_run{ @@ -245,7 +231,7 @@ namespace internal { "role", "toolbar", "aria-label", "Toolbar with simulation controls" ); - SetRefreshRate(refresh_rate, refresh_unit); + SetRefreshChecker(std::forward(checker) ); step.AddAttr( "class", "btn", "class", "btn-success" @@ -256,15 +242,12 @@ namespace internal { button_line << step; AddAnimation(GetID(), - [&step_simulation=Info()->GetStepCallback(), - &refresh_list=Info()->GetRefreshList(), - &should_redraw=*(Info()->GetRefreshChecker())] - (const web::Animate & anim) mutable { + [info=Info()](const web::Animate & anim) mutable { // Run the simulation function every frame - step_simulation(); + info->CallStepCallback(); // Redraw widgets according to a rule - if (should_redraw(anim)) { - for (auto & widget : refresh_list) { + if (info->ShouldRefresh(anim)) { + for (auto & widget : info->GetRefreshList()) { widget.Redraw(); } } @@ -290,17 +273,15 @@ namespace internal { public: /** * Contructor for a Control panel. - * @param refresh_rate the number of milliseconds or frames between refreshes - * @param refresh_mode units of "MILLISECONDS" or "FRAMES" + * @param refresh_checker object to control refresh rate * @param in_id HTML ID of control panel div */ + template ControlPanel( - const int & refresh_rate, - const std::string & refresh_unit, - const std::string & in_id="") - : ControlPanel( - refresh_rate, - refresh_unit, + RefreshChecker&& refresh_checker, + const std::string & in_id="" + ) : ControlPanel( + std::forward( refresh_checker ), new internal::ControlPanelInfo(in_id) ) { ; } @@ -315,53 +296,32 @@ namespace internal { } /** - * Adds a new refresh checker to the list of those available. - * @param name a name for the checker - * @param checker an instance of a functor inheriting from - * emp::prefab::ControlPanel::RefreshChecker + * Set which refresh checker this panel will use. */ - template - void AddRefreshMode(const std::string & name, SPECIALIZED_REFRESH_CHECKER && checker) { - Info()->AddRefreshChecker(name, std::forward(checker)); - } + template + void ActivateRefreshChecker() { Info()->ActivateRefreshChecker(); } /** - * Set the refresh rate units for this control panel. - * @param unit either "MILLISECONDS" or "FRAMES" - * @note rates are independent for "MILLISECONDS" and "FRAMES" so changing - * units may also change the rate. + * Configure a particular refresh checker associated with this panel + * but do not activate it. + * @param checker configured checker to copy into panel. */ - ControlPanel & SetRefreshUnit(const std::string & units) { - Info()->SetRefreshChecker(units); - return *this; - } - - /** - * Set the refresh rate for this control panel for the current unit. - * @param rate period in frames or milliseconds - * @note rates are independent for "MILLISECONDS" and "FRAMES". - */ - void SetRefreshRate(const int & rate) { - Info()->GetRefreshChecker()->SetRate(rate); - } - - /** - * Set the refresh rate for this control panel. - * @param rate the number of milliseconds or frames between refreshes - * @param unit either "MILLISECONDS" or "FRAMES" - * @note rates are independent for "MILLISECONDS" and "FRAMES". - */ - void SetRefreshRate(const int & rate, const std::string & units) { - SetRefreshUnit(units); - SetRefreshRate(rate); + template + void ConfigureRefreshChecker( Checker&& checker ) { + Info()->ConfigureRefreshChecker( + std::forward(checker) + ); } /** - * Gets the current refresh rate for the control panel. - * @return the current refresh rate + * Configure a particular refresh checker associated with this panel + * and activate it. + * @param checker configured checker to copy into panel. */ - int GetRate() const { - return Info()->GetRefreshChecker()->GetRate(); + template + void SetRefreshChecker( Checker&& checker ) { + ConfigureRefreshChecker( std::forward(checker) ); + ActivateRefreshChecker(); } /** diff --git a/tests/web/ControlPanel.cpp b/tests/web/ControlPanel.cpp index 1bb1f8de74..b6dcb87fd0 100644 --- a/tests/web/ControlPanel.cpp +++ b/tests/web/ControlPanel.cpp @@ -24,10 +24,10 @@ struct Test_Control_Panel : emp::web::BaseTest { Test_Control_Panel() : BaseTest({"emp_test_container"}) { - emp::prefab::ControlPanel cp{5, "FRAMES", "ctrl"}; + emp::prefab::ControlPanel cp{ emp::prefab::FrameRefreshChecker(5), "ctrl"}; emp::web::Div sim_area{"sim_area"}; cp.AddToRefreshList(sim_area); - cp.SetRefreshRate(500, "MILLISECONDS"); + cp.SetRefreshChecker( emp::prefab::MillisecondRefreshChecker(500) ); emp::prefab::ButtonGroup husk{"husk"}; husk << emp::web::Button{[](){;}, "A", "a_button"}; @@ -37,7 +37,6 @@ struct Test_Control_Panel : emp::web::BaseTest { emp::prefab::ToggleButtonGroup toggle{ "Auto", "Manual", "primary", "secondary", - true, false, "mode_toggle" }; cp << toggle; @@ -50,7 +49,7 @@ struct Test_Control_Panel : emp::web::BaseTest { }); emp::prefab::ButtonGroup real{"real"}; real << emp::web::Button([](){;}, "D", "d_button"); - real.TakeChildren(std::forward(husk)); + real.TakeChildren(std::forward(husk)); cp << real; cp << husk; @@ -153,4 +152,3 @@ int main() { test_runner.AddTest("Test emp::prefab::ControlPanel HTML Layout"); test_runner.Run(); } - From 359be34417508fdb505287e99f7fc437b2aecf72 Mon Sep 17 00:00:00 2001 From: "mmore500.login+git@gmail.com" Date: Wed, 29 Sep 2021 00:28:39 -0400 Subject: [PATCH 40/46] Add DyanmicRefreshChecker --- include/emp/prefab/ControlPanel.hpp | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index 6bd1aba7da..56b66a3157 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -86,6 +86,29 @@ class FrameRefreshChecker { }; +/** + * A RefreshChecker is a functor that accepts an Animate reference + * and returns a boolean indicating whether components should be redrawn + * based on an internal "rate" for redrawing. + */ +class DynamicRefreshChecker { + + std::function checker; + + public: + + explicit DynamicRefreshChecker( + const std::function checker_ + =[](const web::Animate &){ return true; } + ) : checker(checker_) + {} + + bool ShouldRefresh(const web::Animate & anim) { + return checker(anim); + } + +}; + namespace internal { /** @@ -102,7 +125,8 @@ namespace internal { // for both. emp::DisjointVariant< emp::prefab::MillisecondRefreshChecker, - emp::prefab::FrameRefreshChecker + emp::prefab::FrameRefreshChecker, + emp::prefab::DynamicRefreshChecker > checkers; // A list of widget that should be redrawn when do_redraw return true @@ -205,7 +229,7 @@ namespace internal { * more information on this design pattern. * * @param checker refresh checker to use - * (i.e., emp::MillisecondRefreshChecker or emp::FrameRefreshChecker) + * (i.e., emp::MillisecondRefreshChecker, emp::FrameRefreshChecker, or emp::DynamicRefreshChecker) * @param in_info info object associated with this component */ template< typename RefreshChecker > From 167ce5bd037cc73954648c36f294af26a38e5e4e Mon Sep 17 00:00:00 2001 From: "mmore500.login+git@gmail.com" Date: Wed, 29 Sep 2021 08:24:10 -0400 Subject: [PATCH 41/46] Add zero-overhead static test --- tests/datastructs/DisjointVariant.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/datastructs/DisjointVariant.cpp b/tests/datastructs/DisjointVariant.cpp index 8aed95332f..1022a5ddec 100644 --- a/tests/datastructs/DisjointVariant.cpp +++ b/tests/datastructs/DisjointVariant.cpp @@ -5,6 +5,7 @@ #include "emp/datastructs/DisjointVariant.hpp" #include +#include #include TEST_CASE("Test DisjointVariant", "[datastructs]") { @@ -23,4 +24,11 @@ TEST_CASE("Test DisjointVariant", "[datastructs]") { disjoint_variant.Activate(); disjoint_variant.Visit( [](auto&& v){ REQUIRE( v == 42 ); } ); + // ensure that disjoint variant is zero overhead, + // that there's only one copy of a big type in there + static_assert( + sizeof(emp::DisjointVariant>) + < 2*sizeof(std::tuple>) + ); + } From 4deacf46a15d657811f00eeba41c2fee645e753d Mon Sep 17 00:00:00 2001 From: "mmore500.login+git@gmail.com" Date: Wed, 29 Sep 2021 15:33:58 -0400 Subject: [PATCH 42/46] Buff DisjointVariant static asserts --- tests/datastructs/DisjointVariant.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/datastructs/DisjointVariant.cpp b/tests/datastructs/DisjointVariant.cpp index 1022a5ddec..b6f13b523e 100644 --- a/tests/datastructs/DisjointVariant.cpp +++ b/tests/datastructs/DisjointVariant.cpp @@ -28,7 +28,18 @@ TEST_CASE("Test DisjointVariant", "[datastructs]") { // that there's only one copy of a big type in there static_assert( sizeof(emp::DisjointVariant>) - < 2*sizeof(std::tuple>) + < 2 * sizeof(std::array) + ); + // that overhead scales 1:1 with data + static_assert( + sizeof(emp::DisjointVariant) + - sizeof(emp::DisjointVariant) + == sizeof(std::tuple) + - sizeof(std::tuple) + ); + // that overhead is <= uint64_t + static_assert( + sizeof(emp::DisjointVariant) <= sizeof(uint64_t) + 1 ); } From 60830375236f75fd974764bfee62121adcf45bbd Mon Sep 17 00:00:00 2001 From: "mmore500.login+git@gmail.com" Date: Fri, 1 Oct 2021 00:30:30 -0400 Subject: [PATCH 43/46] Implement and test ApplyToAll --- include/emp/datastructs/DisjointVariant.hpp | 14 ++++++++++++++ tests/datastructs/DisjointVariant.cpp | 5 +++++ 2 files changed, 19 insertions(+) diff --git a/include/emp/datastructs/DisjointVariant.hpp b/include/emp/datastructs/DisjointVariant.hpp index 6730aeb740..2fd3fb908c 100644 --- a/include/emp/datastructs/DisjointVariant.hpp +++ b/include/emp/datastructs/DisjointVariant.hpp @@ -72,6 +72,20 @@ class DisjointVariant { ); } + + /// Wraps std::apply to execute function on each data element. + template + void ApplyToAll(UnaryFunction&& f) { + // adapted from https://stackoverflow.com/a/54053084 + std::apply( + [&f](auto&&... args){ + (( f(args) ), ...); + }, + disjoint_data + ); + } + + }; } // namespace emp diff --git a/tests/datastructs/DisjointVariant.cpp b/tests/datastructs/DisjointVariant.cpp index b6f13b523e..35d2ecb7ae 100644 --- a/tests/datastructs/DisjointVariant.cpp +++ b/tests/datastructs/DisjointVariant.cpp @@ -5,6 +5,7 @@ #include "emp/datastructs/DisjointVariant.hpp" #include +#include #include #include @@ -12,6 +13,10 @@ TEST_CASE("Test DisjointVariant", "[datastructs]") { emp::DisjointVariant disjoint_variant{10, 8.2}; + std::stringstream ss; + disjoint_variant.ApplyToAll( [&ss](auto&& v){ ss << v << ' '; } ); + REQUIRE( ss.str() == "10 8.2 " ); + disjoint_variant.Visit( [](auto&& v){ REQUIRE( v == 10 ); } ); // write a new value into disjoint_variant's int slot From eae6c720b560bc2f8eb5f2d4740f60a9e7536fa5 Mon Sep 17 00:00:00 2001 From: "mmore500.login+git@gmail.com" Date: Fri, 1 Oct 2021 00:30:49 -0400 Subject: [PATCH 44/46] Mock up describe functions --- include/emp/prefab/ControlPanel.hpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index 56b66a3157..d709c5730b 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -63,6 +63,10 @@ class MillisecondRefreshChecker { return false; } + std::string Describe() const { + return emp::to_string("Every ", refresh_rate, " Milliseconds"); + } + }; /** @@ -84,6 +88,10 @@ class FrameRefreshChecker { return anim.GetFrameCount() % refresh_rate; } + std::string Describe() const { + return emp::to_string("Every ", refresh_rate, " Updates"); + } + }; /** @@ -94,19 +102,24 @@ class FrameRefreshChecker { class DynamicRefreshChecker { std::function checker; + std::string description; public: explicit DynamicRefreshChecker( const std::function checker_ - =[](const web::Animate &){ return true; } + =[](const web::Animate &){ return true; }, + const std::string& description_="Custom Refresh" ) : checker(checker_) + , description(description_) {} bool ShouldRefresh(const web::Animate & anim) { return checker(anim); } + std::string Describe() const { return description; } + }; namespace internal { From a6563779d3845be1096719ae5ca6306f303d5099 Mon Sep 17 00:00:00 2001 From: "mmore500.login+git@gmail.com" Date: Fri, 1 Oct 2021 09:45:26 -0400 Subject: [PATCH 45/46] Revert "Refactor ControlPanel to use DisjointVariant" This reverts commit b1509009158bc9496b660e60f3c163f03659716a. --- include/emp/prefab/ControlPanel.hpp | 281 ++++++++++++++-------------- tests/web/ControlPanel.cpp | 8 +- 2 files changed, 147 insertions(+), 142 deletions(-) diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index d709c5730b..bda982d7fe 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -24,7 +24,6 @@ #include "../base/optional.hpp" #include "../base/vector.hpp" -#include "../datastructs/DisjointVariant.hpp" #include "../prefab/ButtonGroup.hpp" #include "../prefab/FontAwesomeIcon.hpp" #include "../prefab/ToggleButtonGroup.hpp" @@ -37,91 +36,6 @@ namespace emp::prefab { class ControlPanel; -/** - * A RefreshChecker is a functor that accepts an Animate reference - * and returns a boolean indicating whether components should be redrawn - * based on an internal "rate" for redrawing. - */ -class MillisecondRefreshChecker { - - int refresh_rate{}; - int elapsed_milliseconds{}; - - public: - - explicit MillisecondRefreshChecker(const int refresh_rate_=0) - : refresh_rate(refresh_rate_) - {} - - bool ShouldRefresh(const web::Animate & anim) { - elapsed_milliseconds += anim.GetStepTime(); - if (elapsed_milliseconds > refresh_rate) { - elapsed_milliseconds -= refresh_rate; - if (elapsed_milliseconds > refresh_rate) elapsed_milliseconds = 0; - return true; - } - return false; - } - - std::string Describe() const { - return emp::to_string("Every ", refresh_rate, " Milliseconds"); - } - -}; - -/** - * A RefreshChecker is a functor that accepts an Animate reference - * and returns a boolean indicating whether components should be redrawn - * based on an internal "rate" for redrawing. - */ -class FrameRefreshChecker { - - int refresh_rate{}; - - public: - - explicit FrameRefreshChecker(const int refresh_rate_=0) - : refresh_rate(refresh_rate_) - {} - - bool ShouldRefresh(const web::Animate & anim) { - return anim.GetFrameCount() % refresh_rate; - } - - std::string Describe() const { - return emp::to_string("Every ", refresh_rate, " Updates"); - } - -}; - -/** - * A RefreshChecker is a functor that accepts an Animate reference - * and returns a boolean indicating whether components should be redrawn - * based on an internal "rate" for redrawing. - */ -class DynamicRefreshChecker { - - std::function checker; - std::string description; - - public: - - explicit DynamicRefreshChecker( - const std::function checker_ - =[](const web::Animate &){ return true; }, - const std::string& description_="Custom Refresh" - ) : checker(checker_) - , description(description_) - {} - - bool ShouldRefresh(const web::Animate & anim) { - return checker(anim); - } - - std::string Describe() const { return description; } - -}; - namespace internal { /** @@ -132,15 +46,55 @@ namespace internal { */ class ControlPanelInfo : public web::internal::DivInfo { friend ControlPanel; + /** + * A RefreshChecker is a functor that accepts an Animate reference + * and returns a boolean indicating whether components should be redrawn + * based on an internal "rate" for redrawing. + */ + class RefreshChecker { + protected: + int rate = 0; + public: + virtual bool operator()(const web::Animate &) { + return true; + }; + void SetRate(int r) { + rate = r; + } + int GetRate() const { return rate; } + }; + + class MillisecondRefreshChecker: public RefreshChecker { + public: + int elapsed_milliseconds = 0; + bool operator()(const web::Animate & anim) override { + elapsed_milliseconds += anim.GetStepTime(); + if (elapsed_milliseconds > rate) { + elapsed_milliseconds -= rate; + if (elapsed_milliseconds > rate) elapsed_milliseconds = 0; + return true; + } + return false; + } + }; + + class FrameRefreshChecker: public RefreshChecker { + public: + bool operator()(const web::Animate & anim) override { + return anim.GetFrameCount() % rate; + } + }; // ControlPanelInfo holds contains two specific instances of a // RefreshChecker for milliseconds and frames to keep independent rates // for both. - emp::DisjointVariant< - emp::prefab::MillisecondRefreshChecker, - emp::prefab::FrameRefreshChecker, - emp::prefab::DynamicRefreshChecker - > checkers; + std::unordered_map refresh_checkers{ + { "MILLISECONDS", new MillisecondRefreshChecker{} }, + { "FRAMES", new FrameRefreshChecker{} } + }; + + // The current redraw checker function + RefreshChecker * should_redraw; // A list of widget that should be redrawn when do_redraw return true emp::vector refresh_list; @@ -155,37 +109,51 @@ namespace internal { */ ControlPanelInfo(const std::string & in_id="") : DivInfo(in_id), + should_redraw(refresh_checkers.at("MILLISECONDS")), step_callback([](){ ; }) { ; } + /** + * Desctructor for the shared pointer managing the ControlPanel's state. + * Cleans up any pointers to new RefreshCheckers. + */ + ~ControlPanelInfo() { + for (auto & p : anim_map) delete p.second; + } + /** * Get a constant reference to the redraw checker function * @return a redraw checker */ - bool ShouldRefresh(const emp::web::Animate& anim) { - return checkers.Visit([&anim](auto&& active_checker){ - return active_checker.ShouldRefresh( anim ); - }); + const RefreshChecker * GetRefreshChecker() const { + return should_redraw; } /** - * Configure a checker but do not activate it. + * Get a reference to the redraw checker function + * @return a redraw checker */ - template - void ConfigureRefreshChecker(Checker&& checker) { - checkers.AssignToElement( std::forward(checker) ); + RefreshChecker * GetRefreshChecker() { + return should_redraw; } /** - * Specify which refresh checker to use. + * Adds a new refresh checker to the list of those available. + * @param name a name for the checker + * @param checker an instance of a functor inheriting from emp::prefab::internal::RefreshChecker */ - template - void ActivateRefreshChecker() { checkers.Activate(); } - + template + void AddRefreshChecker(const std::string & name, SPECIALIZED_REFRESH_CHECKER && checker) { + refresh_checkers[name] = new SPECIALIZED_REFRESH_CHECKER{std::move(checker)}; + } /** - * Calls the void callback function that will be called every frame (as often as possible). + * Gets a void callback function that will be called every frame (as often as possible). + * @return the void callback function to advance the state of some simulation or process + * by one update every call. */ - void CallStepCallback() const { step_callback(); } + const std::function & GetStepCallback() const { + return step_callback; + } /** * Get the refresh list for this control panel @@ -195,6 +163,13 @@ namespace internal { return refresh_list; } + /** + * Set the redraw checker function to the one specified. + */ + void SetRefreshChecker(const std::string & checker_name) { + should_redraw = refresh_checkers.at(checker_name); + } + /** * Sets a void callback function that will be called every frame (as often as possible). * @param step the void callback function to advance the state of some simulation or process @@ -203,7 +178,6 @@ namespace internal { void SetStepCallback(const std::function & step) { step_callback = step; } - }; } // namspace internal @@ -214,6 +188,9 @@ namespace internal { * functionality to the control panel. */ class ControlPanel : public web::Div { + public: + using RefreshChecker = internal::ControlPanelInfo::RefreshChecker; + private: /** * Get shared info pointer, cast to ControlPanel-specific type. @@ -241,13 +218,13 @@ namespace internal { * and event handlers. For internal use only. See the prefab/README.md for * more information on this design pattern. * - * @param checker refresh checker to use - * (i.e., emp::MillisecondRefreshChecker, emp::FrameRefreshChecker, or emp::DynamicRefreshChecker) + * @param refresh_mode units of "MILLISECONDS" or "FRAMES" + * @param refresh_rate the number of milliseconds or frames between refreshes * @param in_info info object associated with this component */ - template< typename RefreshChecker > ControlPanel( - RefreshChecker&& checker, + const int & refresh_rate, + const std::string & refresh_unit, web::internal::DivInfo * in_info ) : web::Div(in_info), toggle_run{ @@ -268,7 +245,7 @@ namespace internal { "role", "toolbar", "aria-label", "Toolbar with simulation controls" ); - SetRefreshChecker(std::forward(checker) ); + SetRefreshRate(refresh_rate, refresh_unit); step.AddAttr( "class", "btn", "class", "btn-success" @@ -279,12 +256,15 @@ namespace internal { button_line << step; AddAnimation(GetID(), - [info=Info()](const web::Animate & anim) mutable { + [&step_simulation=Info()->GetStepCallback(), + &refresh_list=Info()->GetRefreshList(), + &should_redraw=*(Info()->GetRefreshChecker())] + (const web::Animate & anim) mutable { // Run the simulation function every frame - info->CallStepCallback(); + step_simulation(); // Redraw widgets according to a rule - if (info->ShouldRefresh(anim)) { - for (auto & widget : info->GetRefreshList()) { + if (should_redraw(anim)) { + for (auto & widget : refresh_list) { widget.Redraw(); } } @@ -310,15 +290,17 @@ namespace internal { public: /** * Contructor for a Control panel. - * @param refresh_checker object to control refresh rate + * @param refresh_rate the number of milliseconds or frames between refreshes + * @param refresh_mode units of "MILLISECONDS" or "FRAMES" * @param in_id HTML ID of control panel div */ - template ControlPanel( - RefreshChecker&& refresh_checker, - const std::string & in_id="" - ) : ControlPanel( - std::forward( refresh_checker ), + const int & refresh_rate, + const std::string & refresh_unit, + const std::string & in_id="") + : ControlPanel( + refresh_rate, + refresh_unit, new internal::ControlPanelInfo(in_id) ) { ; } @@ -333,32 +315,53 @@ namespace internal { } /** - * Set which refresh checker this panel will use. + * Adds a new refresh checker to the list of those available. + * @param name a name for the checker + * @param checker an instance of a functor inheriting from + * emp::prefab::ControlPanel::RefreshChecker */ - template - void ActivateRefreshChecker() { Info()->ActivateRefreshChecker(); } + template + void AddRefreshMode(const std::string & name, SPECIALIZED_REFRESH_CHECKER && checker) { + Info()->AddRefreshChecker(name, std::forward(checker)); + } /** - * Configure a particular refresh checker associated with this panel - * but do not activate it. - * @param checker configured checker to copy into panel. + * Set the refresh rate units for this control panel. + * @param unit either "MILLISECONDS" or "FRAMES" + * @note rates are independent for "MILLISECONDS" and "FRAMES" so changing + * units may also change the rate. */ - template - void ConfigureRefreshChecker( Checker&& checker ) { - Info()->ConfigureRefreshChecker( - std::forward(checker) - ); + ControlPanel & SetRefreshUnit(const std::string & units) { + Info()->SetRefreshChecker(units); + return *this; + } + + /** + * Set the refresh rate for this control panel for the current unit. + * @param rate period in frames or milliseconds + * @note rates are independent for "MILLISECONDS" and "FRAMES". + */ + void SetRefreshRate(const int & rate) { + Info()->GetRefreshChecker()->SetRate(rate); + } + + /** + * Set the refresh rate for this control panel. + * @param rate the number of milliseconds or frames between refreshes + * @param unit either "MILLISECONDS" or "FRAMES" + * @note rates are independent for "MILLISECONDS" and "FRAMES". + */ + void SetRefreshRate(const int & rate, const std::string & units) { + SetRefreshUnit(units); + SetRefreshRate(rate); } /** - * Configure a particular refresh checker associated with this panel - * and activate it. - * @param checker configured checker to copy into panel. + * Gets the current refresh rate for the control panel. + * @return the current refresh rate */ - template - void SetRefreshChecker( Checker&& checker ) { - ConfigureRefreshChecker( std::forward(checker) ); - ActivateRefreshChecker(); + int GetRate() const { + return Info()->GetRefreshChecker()->GetRate(); } /** diff --git a/tests/web/ControlPanel.cpp b/tests/web/ControlPanel.cpp index b6dcb87fd0..1bb1f8de74 100644 --- a/tests/web/ControlPanel.cpp +++ b/tests/web/ControlPanel.cpp @@ -24,10 +24,10 @@ struct Test_Control_Panel : emp::web::BaseTest { Test_Control_Panel() : BaseTest({"emp_test_container"}) { - emp::prefab::ControlPanel cp{ emp::prefab::FrameRefreshChecker(5), "ctrl"}; + emp::prefab::ControlPanel cp{5, "FRAMES", "ctrl"}; emp::web::Div sim_area{"sim_area"}; cp.AddToRefreshList(sim_area); - cp.SetRefreshChecker( emp::prefab::MillisecondRefreshChecker(500) ); + cp.SetRefreshRate(500, "MILLISECONDS"); emp::prefab::ButtonGroup husk{"husk"}; husk << emp::web::Button{[](){;}, "A", "a_button"}; @@ -37,6 +37,7 @@ struct Test_Control_Panel : emp::web::BaseTest { emp::prefab::ToggleButtonGroup toggle{ "Auto", "Manual", "primary", "secondary", + true, false, "mode_toggle" }; cp << toggle; @@ -49,7 +50,7 @@ struct Test_Control_Panel : emp::web::BaseTest { }); emp::prefab::ButtonGroup real{"real"}; real << emp::web::Button([](){;}, "D", "d_button"); - real.TakeChildren(std::forward(husk)); + real.TakeChildren(std::forward(husk)); cp << real; cp << husk; @@ -152,3 +153,4 @@ int main() { test_runner.AddTest("Test emp::prefab::ControlPanel HTML Layout"); test_runner.Run(); } + From 414e349d93a47dab07b22cdf71c39e8559e7a970 Mon Sep 17 00:00:00 2001 From: "mmore500.login+git@gmail.com" Date: Fri, 1 Oct 2021 10:27:44 -0400 Subject: [PATCH 46/46] Minor refactoring on @lemniscate8's approach :x :wq --- include/emp/prefab/ControlPanel.hpp | 109 ++++++++++++---------------- tests/web/ControlPanel.cpp | 4 +- 2 files changed, 49 insertions(+), 64 deletions(-) diff --git a/include/emp/prefab/ControlPanel.hpp b/include/emp/prefab/ControlPanel.hpp index bda982d7fe..3a9663f79c 100644 --- a/include/emp/prefab/ControlPanel.hpp +++ b/include/emp/prefab/ControlPanel.hpp @@ -18,6 +18,7 @@ #define EMP_CONTROL_PANEL_HPP #include +#include #include #include #include @@ -55,19 +56,16 @@ namespace internal { protected: int rate = 0; public: - virtual bool operator()(const web::Animate &) { - return true; - }; - void SetRate(int r) { - rate = r; - } - int GetRate() const { return rate; } + virtual ~RefreshChecker() {} + virtual bool ShouldRedraw(const web::Animate &) = 0; + void SetRefreshRate(const int r) { rate = r; } + int GetRefreshRate() const { return rate; } }; class MillisecondRefreshChecker: public RefreshChecker { public: int elapsed_milliseconds = 0; - bool operator()(const web::Animate & anim) override { + bool ShouldRedraw(const web::Animate & anim) override { elapsed_milliseconds += anim.GetStepTime(); if (elapsed_milliseconds > rate) { elapsed_milliseconds -= rate; @@ -80,7 +78,7 @@ namespace internal { class FrameRefreshChecker: public RefreshChecker { public: - bool operator()(const web::Animate & anim) override { + bool ShouldRedraw(const web::Animate & anim) override { return anim.GetFrameCount() % rate; } }; @@ -88,13 +86,13 @@ namespace internal { // ControlPanelInfo holds contains two specific instances of a // RefreshChecker for milliseconds and frames to keep independent rates // for both. - std::unordered_map refresh_checkers{ - { "MILLISECONDS", new MillisecondRefreshChecker{} }, - { "FRAMES", new FrameRefreshChecker{} } + std::unordered_map> refresh_checkers{ + { "MILLISECONDS", std::make_shared() }, + { "FRAMES", std::make_shared() } }; // The current redraw checker function - RefreshChecker * should_redraw; + std::weak_ptr cur_checker; // A list of widget that should be redrawn when do_redraw return true emp::vector refresh_list; @@ -109,31 +107,19 @@ namespace internal { */ ControlPanelInfo(const std::string & in_id="") : DivInfo(in_id), - should_redraw(refresh_checkers.at("MILLISECONDS")), + cur_checker(refresh_checkers.at("MILLISECONDS")), step_callback([](){ ; }) { ; } /** - * Desctructor for the shared pointer managing the ControlPanel's state. - * Cleans up any pointers to new RefreshCheckers. - */ - ~ControlPanelInfo() { - for (auto & p : anim_map) delete p.second; - } - - /** - * Get a constant reference to the redraw checker function - * @return a redraw checker + * Get current refresh rate. */ - const RefreshChecker * GetRefreshChecker() const { - return should_redraw; - } + int GetRefreshRate() const { return cur_checker.lock()->GetRefreshRate(); } /** - * Get a reference to the redraw checker function - * @return a redraw checker + * Set the active refresh checker's refresh rate. */ - RefreshChecker * GetRefreshChecker() { - return should_redraw; + void SetRefreshRate(const int rate) { + cur_checker.lock()->SetRefreshRate(rate); } /** @@ -143,31 +129,30 @@ namespace internal { */ template void AddRefreshChecker(const std::string & name, SPECIALIZED_REFRESH_CHECKER && checker) { - refresh_checkers[name] = new SPECIALIZED_REFRESH_CHECKER{std::move(checker)}; + refresh_checkers[name] = std::make_shared( + std::forward(checker) + ); } /** - * Gets a void callback function that will be called every frame (as often as possible). - * @return the void callback function to advance the state of some simulation or process - * by one update every call. + * Calls a void callback function to advance the state of some simulation or process. */ - const std::function & GetStepCallback() const { - return step_callback; - } + void CallStepCallback() const { step_callback(); } /** - * Get the refresh list for this control panel - * @return a list of Widgets that will be refreshed every update period + * Refresh all elements on the refresh list for this control panel. */ - emp::vector & GetRefreshList() { - return refresh_list; + void TryRefreshWidgets(const web::Animate & anim) { + if (cur_checker.lock()->ShouldRedraw(anim)) { + for (auto& widget : refresh_list) widget.Redraw(); + } } /** * Set the redraw checker function to the one specified. */ void SetRefreshChecker(const std::string & checker_name) { - should_redraw = refresh_checkers.at(checker_name); + cur_checker = refresh_checkers.at(checker_name); } /** @@ -178,6 +163,15 @@ namespace internal { void SetStepCallback(const std::function & step) { step_callback = step; } + + /** + * Adds a Widget to a list of widgets redrawn at the specified refresh rate. + * @param area a widget + */ + void AddToRefreshList(const web::Widget & area) { + refresh_list.push_back(area); + } + }; } // namspace internal @@ -256,18 +250,11 @@ namespace internal { button_line << step; AddAnimation(GetID(), - [&step_simulation=Info()->GetStepCallback(), - &refresh_list=Info()->GetRefreshList(), - &should_redraw=*(Info()->GetRefreshChecker())] - (const web::Animate & anim) mutable { + [info=Info()](const web::Animate & anim) mutable { // Run the simulation function every frame - step_simulation(); + info->CallStepCallback(); // Redraw widgets according to a rule - if (should_redraw(anim)) { - for (auto & widget : refresh_list) { - widget.Redraw(); - } - } + info->TryRefreshWidgets(anim); } ); @@ -341,8 +328,9 @@ namespace internal { * @param rate period in frames or milliseconds * @note rates are independent for "MILLISECONDS" and "FRAMES". */ - void SetRefreshRate(const int & rate) { - Info()->GetRefreshChecker()->SetRate(rate); + ControlPanel & SetRefreshRate(const int & rate) { + Info()->SetRefreshRate(rate); + return *this; } /** @@ -351,25 +339,24 @@ namespace internal { * @param unit either "MILLISECONDS" or "FRAMES" * @note rates are independent for "MILLISECONDS" and "FRAMES". */ - void SetRefreshRate(const int & rate, const std::string & units) { + ControlPanel & SetRefreshRate(const int & rate, const std::string & units) { SetRefreshUnit(units); SetRefreshRate(rate); + return *this; } /** * Gets the current refresh rate for the control panel. * @return the current refresh rate */ - int GetRate() const { - return Info()->GetRefreshChecker()->GetRate(); - } + int GetRefreshRate() const { return Info()->GetRefreshRate(); } /** * Adds a Widget to a list of widgets redrawn at the specified refresh rate. * @param area a widget */ - void AddToRefreshList(Widget & area) { - Info()->GetRefreshList().push_back(area); + void AddToRefreshList(const Widget & area) { + Info()->AddToRefreshList(area); } /** diff --git a/tests/web/ControlPanel.cpp b/tests/web/ControlPanel.cpp index 1bb1f8de74..4a7d686b4a 100644 --- a/tests/web/ControlPanel.cpp +++ b/tests/web/ControlPanel.cpp @@ -37,7 +37,6 @@ struct Test_Control_Panel : emp::web::BaseTest { emp::prefab::ToggleButtonGroup toggle{ "Auto", "Manual", "primary", "secondary", - true, false, "mode_toggle" }; cp << toggle; @@ -50,7 +49,7 @@ struct Test_Control_Panel : emp::web::BaseTest { }); emp::prefab::ButtonGroup real{"real"}; real << emp::web::Button([](){;}, "D", "d_button"); - real.TakeChildren(std::forward(husk)); + real.TakeChildren(std::forward(husk)); cp << real; cp << husk; @@ -153,4 +152,3 @@ int main() { test_runner.AddTest("Test emp::prefab::ControlPanel HTML Layout"); test_runner.Run(); } -