Skip to content

Commit

Permalink
Issue #164: Add ForEach, which would allow a list of Controllers to b…
Browse files Browse the repository at this point in the history
…e added
  • Loading branch information
rmpowell77 committed Dec 10, 2023
1 parent 7df2c33 commit 55b5a48
Show file tree
Hide file tree
Showing 14 changed files with 1,024 additions and 5 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ add_library(
include/wxUI/Choice.h
include/wxUI/ComboBox.h
include/wxUI/Custom.h
include/wxUI/ForEach.h
include/wxUI/Generic.h
include/wxUI/GetterSetter.h
include/wxUI/HelperMacros.h
Expand Down
1 change: 1 addition & 0 deletions LATEST_RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ Other changes:
* [#156](../../issues/156) We should be able to take ranges of std::string for choices, list boxes, etc
* [#157](../../issues/157) Text needs withWrap
* [#161](../../issues/161) Need to have a setEnabled for Widgets
* [#164](../../issues/164) Add ForEach, which would allow a list of Controllers to be added

35 changes: 35 additions & 0 deletions docs/ProgrammersGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ Handlers are callable items that handle events. The handler can be declared wit
GenericExample dialog(this);
dialog.ShowModal();
} },
wxUI::Item { "&ForEachExample...", [this] {
ForEachExample dialog(this);
dialog.ShowModal();
} },
wxUI::Item { "&Example Item...", [] {
wxLogMessage("Hello World!");
} },
Expand Down Expand Up @@ -109,6 +113,10 @@ Items { "Name", "Help", Handler }
GenericExample dialog(this);
dialog.ShowModal();
} },
wxUI::Item { "&ForEachExample...", [this] {
ForEachExample dialog(this);
dialog.ShowModal();
} },
wxUI::Item { "&Example Item...", [] {
wxLogMessage("Hello World!");
} },
Expand Down Expand Up @@ -207,6 +215,33 @@ Essentially, you supply a object that converts to `wxSizer*` or `wxWindow*`, or
.attachTo(this);
```

#### ForEach

Often times you will need to layout several widgets which only are different in their wxWindowID and Name. Or perhaps there are cases where the items to be laid out are dynamic. `ForEach` allows you to specify a range of values or `std::tuples` that are arguements to a closure that will returns a *Controller*. These will then be added one at a time.

```
HSizer {
ForEach {
{ wxART_PLUS, wxART_MINUS, wxART_FIND },
[](auto identity) {
return wxUI::BitmapButton { wxArtProvider::GetBitmap(identity) };
} },
},
```

*Ranges* are valid arguments for `ForEach`, which allows you to build up complicated layouts at run time.

```
HSizer {
ForEach<wxUI::Button, std::tuple<wxWindowID, std::string>> {
std::vector<std::tuple<wxWindowID, std::string>> { { wxID_CANCEL, "A" }, { wxID_OK, "B" } } | std::views::filter([](auto s) { return std::get<1>(s) == "B"; }),
[](auto identityAndName) {
return wxUI::Button { std::get<0>(identityAndName), std::get<1>(identityAndName) };
} },
},
```


### Controllers

*Controllers* are the general term to refer to items that behave like a [`wxContol`](https://docs.wxwidgets.org/3.0/classwx_control.html). In `wxUI` we attempt to conform a consistent style that favors the common things you do with a specific `wxControl`.
Expand Down
15 changes: 15 additions & 0 deletions docs/src/docs/ProgrammersGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,21 @@ Essentially, you supply a object that converts to `wxSizer*` or `wxWindow*`, or
{{{ examples/HelloWorld/ExtendedExample.cpp SplitterExample " // ..." }}}
```

#### ForEach

Often times you will need to layout several widgets which only are different in their wxWindowID and Name. Or perhaps there are cases where the items to be laid out are dynamic. `ForEach` allows you to specify a range of values or `std::tuples` that are arguements to a closure that will returns a *Controller*. These will then be added one at a time.

```
{{{ examples/HelloWorld/ExtendedExample.cpp ForEachExample " // ..." }}}
```

*Ranges* are valid arguments for `ForEach`, which allows you to build up complicated layouts at run time.

```
{{{ examples/HelloWorld/ExtendedExample.cpp ComplicatedForEachExample " // ..." }}}
```


### Controllers

*Controllers* are the general term to refer to items that behave like a [`wxContol`](https://docs.wxwidgets.org/3.0/classwx_control.html). In `wxUI` we attempt to conform a consistent style that favors the common things you do with a specific `wxControl`.
Expand Down
122 changes: 122 additions & 0 deletions examples/HelloWorld/ExtendedExample.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ SOFTWARE.
// wxUI "Hello World" example

#include "ExtendedExample.h"
#include <wx/artprov.h>
#include <wxUI/wxUI.h>

ExtendedExample::ExtendedExample(wxWindow* parent)
Expand Down Expand Up @@ -223,3 +224,124 @@ GenericExample::GenericExample(wxWindow* parent)
assert(proxy1->GetLabel() == "Raw 1");
assert(proxy2->GetLabel() == "Raw 2");
}

ForEachExample::ForEachExample(wxWindow* parent)
: wxDialog(parent, wxID_ANY, "ForEach Example", wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
using namespace wxUI;
using namespace std::literals;
VSizer {
wxSizerFlags {}.Border(wxALL, 2),
HSizer {
ForEach<wxUI::Button, std::string> {
std::vector<std::string> { "A", "B", "C" },
[](auto name) {
return wxUI::Button { name };
} },
},
HSizer {
ForEach {
std::vector<std::string> { "A", "B", "C" },
[](auto name) {
return wxUI::Button { name };
} },
},
HSizer {
ForEach {
std::vector { "A", "B", "C" },
[](auto name) {
return wxUI::Button { name };
} },
},
HSizer {
ForEach {
{ "A"s, "B"s, "C"s },
[](auto name) {
return wxUI::Button { name };
} },
},
HSizer {
ForEach {
{ "A", "B", "C" },
[](auto name) {
return wxUI::Button { name };
} },
},
HSizer {
ForEach<wxUI::BitmapButton, wxArtID> {
std::vector<wxArtID> { wxART_PLUS, wxART_MINUS, wxART_FIND },
[](auto identity) {
return wxUI::BitmapButton { wxArtProvider::GetBitmap(identity) };
} },
},
HSizer {
ForEach {
std::vector<wxArtID> { wxART_PLUS, wxART_MINUS, wxART_FIND },
[](auto identity) {
return wxUI::BitmapButton { wxArtProvider::GetBitmap(identity) };
} },
},
HSizer {
ForEach {
std::vector { wxART_PLUS, wxART_MINUS, wxART_FIND },
[](auto identity) {
return wxUI::BitmapButton { wxArtProvider::GetBitmap(identity) };
} },
},
// snippet ForEachExample
HSizer {
ForEach {
{ wxART_PLUS, wxART_MINUS, wxART_FIND },
[](auto identity) {
return wxUI::BitmapButton { wxArtProvider::GetBitmap(identity) };
} },
},
// endsnippet ForEachExample
HSizer {
ForEach<wxUI::Button, std::string> {
std::vector { "Long string", "Ball", "S", "Tools" } | std::views::filter([](auto s) { return std::string(s).size() < 5; }),
[](auto name) {
return wxUI::Button { name };
} },
},
HSizer {
ForEach {
std::vector { std::tuple<wxWindowID, std::string> { wxID_CANCEL, "A" }, std::tuple<wxWindowID, std::string> { wxID_OK, "B" } },
[](auto identityAndName) {
return wxUI::Button { std::get<0>(identityAndName), std::get<1>(identityAndName) };
} },
},
HSizer {
ForEach {
{ std::tuple<wxWindowID, std::string> { wxID_CANCEL, "A" }, std::tuple<wxWindowID, std::string> { wxID_OK, "B" } },
[](auto identityAndName) {
return wxUI::Button { std::get<0>(identityAndName), std::get<1>(identityAndName) };
} },
},
HSizer {
ForEach<wxUI::Button, std::tuple<wxWindowID, std::string>> {
std::vector<std::tuple<wxWindowID, std::string>> { { wxID_CANCEL, "A" }, { wxID_OK, "B" } },
[](auto identityAndName) {
return wxUI::Button { std::get<0>(identityAndName), std::get<1>(identityAndName) };
} },
},
// snippet ComplicatedForEachExample
HSizer {
ForEach<wxUI::Button, std::tuple<wxWindowID, std::string>> {
std::vector<std::tuple<wxWindowID, std::string>> { { wxID_CANCEL, "A" }, { wxID_OK, "B" } } | std::views::filter([](auto s) { return std::get<1>(s) == "B"; }),
[](auto identityAndName) {
return wxUI::Button { std::get<0>(identityAndName), std::get<1>(identityAndName) };
} },
},
// endsnippet ComplicatedForEachExample
HSizer {
ForEach {
std::vector<std::tuple<wxWindowID, std::string>> { { wxID_CANCEL, "A" }, { wxID_OK, "B" } } | std::views::filter([](auto s) { return std::get<1>(s) == "B"; }),
[](auto identityAndName) {
return wxUI::Button { std::get<0>(identityAndName), std::get<1>(identityAndName) };
} },
},
CreateStdDialogButtonSizer(wxOK),
}
.attachTo(this);
}
5 changes: 5 additions & 0 deletions examples/HelloWorld/ExtendedExample.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,8 @@ class GenericExample : public wxDialog {

private:
};

class ForEachExample : public wxDialog {
public:
explicit ForEachExample(wxWindow* parent);
};
4 changes: 4 additions & 0 deletions examples/HelloWorld/HelloWorld.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ HelloWorldFrame::HelloWorldFrame()
GenericExample dialog(this);
dialog.ShowModal();
} },
wxUI::Item { "&ForEachExample...", [this] {
ForEachExample dialog(this);
dialog.ShowModal();
} },
wxUI::Item { "&Example Item...", [] {
wxLogMessage("Hello World!");
} },
Expand Down
113 changes: 113 additions & 0 deletions include/wxUI/ForEach.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
MIT License
Copyright (c) 2022 Richard Powell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once

#include "Widget.h"

namespace wxUI {

// how does this work. We want to write:
// wxUI::ForEach({ "These", "are", "some", "buttons"}, [](wxWindow*, auto str) { return wxUI::Button{str}; });
// or is this better:
// wxUI::ForEach([](wxWindow*, auto str) { return wxUI::Button{str}; }, "These", "are", "some", "buttons" );
// wxUI::ForEach{ { "These", "are", "some", "buttons" } | std::views::filter([](auto a) { return a.size() < 5; }), [](wxWindow*, auto str) { return wxUI::Button{str}; }};
// would create an array of Buttons.
namespace details {
// // Concept to check if a type is callable
// template <typename F, typename... Args>
// concept Callable = std::invocable<F, Args...>;

// Concept to check if a callable object returns something callable
template <typename F, typename... Args>
concept CallableAndReturnsCreateAndAddable = requires(F func, Args... args) {
requires CreateAndAddable<std::invoke_result_t<F, Args...>>;
};

template <typename F, typename... Args>
concept CallableAndReturnsWindow = std::is_convertible_v<std::invoke_result_t<F, wxWindow*, Args...>, wxWindow*>;
}

template <details::CreateAndAddable W, typename Arg>
struct ForEach {

using LocalCreateForEach = std::function<W(Arg)>;

template <details::CallableAndReturnsCreateAndAddable<Arg> CreateFunction>
ForEach(std::initializer_list<Arg> args, CreateFunction createFunction)
: args(args)
, createFunction(createFunction)
{
}

template <details::CallableAndReturnsCreateAndAddable<Arg> CreateFunction>
ForEach(wxSizerFlags const& flags, std::initializer_list<Arg> args, CreateFunction createFunction)
: flags(flags)
, args(args)
, createFunction(createFunction)
{
}

template <details::CallableAndReturnsCreateAndAddable<Arg> CreateFunction>
ForEach(details::Ranges::input_range_of<Arg> auto&& args, CreateFunction createFunction)
: args(details::Ranges::ToVector<Arg>(std::forward<decltype(args)>(args)))
, createFunction(createFunction)
{
}

template <details::CallableAndReturnsCreateAndAddable<Arg> CreateFunction>
ForEach(wxSizerFlags const& flags, details::Ranges::input_range_of<Arg> auto&& args, CreateFunction createFunction)
: flags(flags)
, args(details::Ranges::ToVector<Arg>(std::forward<decltype(args)>(args)))
, createFunction(createFunction)
{
}

void createAndAdd(wxWindow* parent, wxSizer* parentSizer, wxSizerFlags const& parentFlags) const
{
for (auto& item : args) {
createFunction(item).createAndAdd(parent, parentSizer, flags ? *flags : parentFlags);
}
}

private:
std::optional<wxSizerFlags> flags {};
std::vector<Arg> args;
LocalCreateForEach createFunction;
};

template <typename Function, typename Arg>
ForEach(std::initializer_list<Arg> args, Function createFunction) -> ForEach<std::invoke_result_t<Function, Arg>, Arg>;

template <typename Function, typename Arg>
ForEach(wxSizerFlags const& flags, std::initializer_list<Arg> args, Function createFunction) -> ForEach<std::invoke_result_t<Function, Arg>, Arg>;

template <typename Function, std::ranges::input_range Range>
ForEach(Range&& args, Function createFunction) -> ForEach<std::invoke_result_t<Function, std::ranges::range_value_t<Range>>, std::ranges::range_value_t<Range>>;

template <typename Function, std::ranges::input_range Range>
ForEach(wxSizerFlags const& flags, Range&& args, Function createFunction) -> ForEach<std::invoke_result_t<Function, std::ranges::range_value_t<Range>>, std::ranges::range_value_t<Range>>;

}

#include "ZapMacros.h"
6 changes: 1 addition & 5 deletions include/wxUI/Splitter.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,7 @@ struct HSplitter : public details::WidgetDetails<HSplitter<W1, W2>, wxSplitterWi
auto createImpl(wxWindow* parent) -> wxWindow* override
{
auto* widget = super::setProxy(new underlying_t(parent, super::getIdentity(), super::getPos(), super::getSize(), super::getStyle()));
auto* widget0 = std::get<0>(widgets).create(widget);
auto* widget1 = std::get<1>(widgets).create(widget);

widget->SplitHorizontally(widget0, widget1);

widget->SplitHorizontally(std::get<0>(widgets).create(widget), std::get<1>(widgets).create(widget));
return widget;
}
};
Expand Down
1 change: 1 addition & 0 deletions include/wxUI/wxUI.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include <wxUI/Choice.h>
#include <wxUI/ComboBox.h>
#include <wxUI/Custom.h>
#include <wxUI/ForEach.h>
#include <wxUI/Generic.h>
#include <wxUI/Hyperlink.h>
#include <wxUI/Layout.h>
Expand Down
1 change: 1 addition & 0 deletions tests/BuildTests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ target_sources(wxUI_Tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/Choice.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ComboBox.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Custom.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ForEach.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Generic.cpp
${CMAKE_CURRENT_SOURCE_DIR}/GetterSetter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/HelperMacros.cpp
Expand Down
Loading

0 comments on commit 55b5a48

Please sign in to comment.