Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Control panel for simulations #443

Open
wants to merge 47 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
1d769c0
Simple button group class
lemniscate8 Aug 2, 2021
be39c0c
Idea for concatenating button groups
lemniscate8 Aug 2, 2021
1a160a4
Toggle button setup for play/pause button
lemniscate8 Aug 2, 2021
ce2d52e
Some setup for the toggle button
lemniscate8 Aug 3, 2021
8992466
Fix switch styling in Bootstrap v5.0
lemniscate8 Aug 3, 2021
22cb37f
Get toggle button working
lemniscate8 Aug 3, 2021
033ac59
Some documentation
lemniscate8 Aug 4, 2021
5db885f
Add styling for imbedded toggle button groups
lemniscate8 Aug 4, 2021
8f649f3
Input works on radio buttons now
lemniscate8 Aug 4, 2021
6ebd496
No need for components as members
lemniscate8 Aug 9, 2021
9f5a3fd
Control panel basics
lemniscate8 Aug 9, 2021
875640b
Allow grouping buttons in ControlPanel
lemniscate8 Aug 10, 2021
7408f8f
No more control panel for now
lemniscate8 Aug 18, 2021
2149bef
Ironing out
lemniscate8 Aug 19, 2021
b9fcedc
Use checker function
lemniscate8 Aug 19, 2021
c2e1aa0
Fix bug with toggle clicks
lemniscate8 Aug 20, 2021
3ae3215
Optimize checker and fix reference issues
lemniscate8 Aug 20, 2021
50ba064
Document control panel
lemniscate8 Aug 20, 2021
b099a3b
Remove step button disabling
lemniscate8 Aug 20, 2021
0d3256e
Add documentation
lemniscate8 Aug 20, 2021
b0be177
Swap rate and unit parameters in constructor
lemniscate8 Aug 25, 2021
5a4cc26
Add unit test for control panel
lemniscate8 Aug 25, 2021
21f8b50
Test for ToggleButtonGroup
lemniscate8 Aug 25, 2021
d0faa5c
Address feedback
lemniscate8 Sep 3, 2021
e4f3f2a
More documentation
lemniscate8 Sep 3, 2021
ca489f3
Simplify some stuff
lemniscate8 Sep 3, 2021
2aac8ac
Remove cout
lemniscate8 Sep 3, 2021
c9abe28
More descriptive template types
lemniscate8 Sep 9, 2021
8ba6455
Explain DivInfo constructor for Div
lemniscate8 Sep 9, 2021
e3db915
Use map to hold checker instances
lemniscate8 Sep 22, 2021
6be2bb2
Name change for clarity
lemniscate8 Sep 27, 2021
892c1d0
Merge branch 'master' into control-panel
mmore500 Sep 29, 2021
0ef6beb
Include string header
mmore500 Sep 29, 2021
7bab656
Include string header
mmore500 Sep 29, 2021
9179f2e
Implement and test DisjointVariant
mmore500 Sep 29, 2021
30e0ed1
Use relative path for in-library includes
mmore500 Sep 29, 2021
9927976
Fix duplicated constructor
mmore500 Sep 29, 2021
30ae1ca
Add missing header
mmore500 Sep 29, 2021
e65067c
Add Assign and Activate methods
mmore500 Sep 29, 2021
b150900
Refactor ControlPanel to use DisjointVariant
mmore500 Sep 29, 2021
359be34
Add DyanmicRefreshChecker
mmore500 Sep 29, 2021
167ce5b
Add zero-overhead static test
mmore500 Sep 29, 2021
4deacf4
Buff DisjointVariant static asserts
mmore500 Sep 29, 2021
6083037
Implement and test ApplyToAll
mmore500 Oct 1, 2021
eae6c72
Mock up describe functions
mmore500 Oct 1, 2021
a656377
Revert "Refactor ControlPanel to use DisjointVariant"
mmore500 Oct 1, 2021
414e349
Minor refactoring on @lemniscate8's approach
mmore500 Oct 1, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactor ControlPanel to use DisjointVariant
  • Loading branch information
mmore500 committed Sep 29, 2021

Unverified

The email in this signature doesn’t match the committer email.
commit b1509009158bc9496b660e60f3c163f03659716a
244 changes: 102 additions & 142 deletions include/emp/prefab/ControlPanel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 {

/**
Expand All @@ -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<std::string, RefreshChecker * > 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<web::Widget> refresh_list;
Expand All @@ -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<typename Checker>
void ConfigureRefreshChecker(Checker&& checker) {
checkers.AssignToElement<Checker>( std::forward<Checker>(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<class SPECIALIZED_REFRESH_CHECKER>
void AddRefreshChecker(const std::string & name, SPECIALIZED_REFRESH_CHECKER && checker) {
refresh_checkers[name] = new SPECIALIZED_REFRESH_CHECKER{std::move(checker)};
}
template<typename T>
void ActivateRefreshChecker() { checkers.Activate<T>(); }


/**
* 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<void()> & GetStepCallback() const {
return step_callback;
}
void CallStepCallback() const { step_callback(); }

/**
* Get the refresh list for this control panel
Expand All @@ -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
Expand All @@ -178,6 +166,7 @@ namespace internal {
void SetStepCallback(const std::function<void()> & step) {
step_callback = step;
}

};
} // namspace internal

Expand All @@ -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.
Expand Down Expand Up @@ -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{
Expand All @@ -245,7 +231,7 @@ namespace internal {
"role", "toolbar",
"aria-label", "Toolbar with simulation controls"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good accessibility move

);
SetRefreshRate(refresh_rate, refresh_unit);
SetRefreshChecker(std::forward<RefreshChecker>(checker) );
step.AddAttr(
"class", "btn",
"class", "btn-success"
Expand All @@ -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();
}
}
Expand All @@ -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<typename RefreshChecker>
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<RefreshChecker>( refresh_checker ),
new internal::ControlPanelInfo(in_id)
) { ; }

Expand All @@ -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<class SPECIALIZED_REFRESH_CHECKER>
void AddRefreshMode(const std::string & name, SPECIALIZED_REFRESH_CHECKER && checker) {
Info()->AddRefreshChecker(name, std::forward<SPECIALIZED_REFRESH_CHECKER>(checker));
}
template <typename Checker>
void ActivateRefreshChecker() { Info()->ActivateRefreshChecker<Checker>(); }

/**
* 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 <typename Checker>
void ConfigureRefreshChecker( Checker&& checker ) {
Info()->ConfigureRefreshChecker<Checker>(
std::forward<Checker>(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 <typename Checker>
void SetRefreshChecker( Checker&& checker ) {
ConfigureRefreshChecker<Checker>( std::forward<Checker>(checker) );
ActivateRefreshChecker<Checker>();
}

/**
Expand Down
8 changes: 3 additions & 5 deletions tests/web/ControlPanel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"};
Expand All @@ -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;
Expand All @@ -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<ButtonGroup>(husk));
real.TakeChildren(std::forward<emp::prefab::ButtonGroup>(husk));
cp << real;
cp << husk;

Expand Down Expand Up @@ -153,4 +152,3 @@ int main() {
test_runner.AddTest<Test_Control_Panel>("Test emp::prefab::ControlPanel HTML Layout");
test_runner.Run();
}