FTXUI/src/ftxui/component/radiobox.cpp
Arthur Sonzogni 455998d759
Remove Ref<XxxOption> and add new interfaces. (#686)
1. Stop taking Ref<XxxOption> in Component constructors. Instead, use
   the XxxOption directly. Passing by copy avoid problems developers had
   where one was shared in between multiple component, causing issues.

2. Add variants of most component constructors taking a struct only.

This replaces:
https://github.com/ArthurSonzogni/FTXUI/pull/670

This fixes:
https://github.com/ArthurSonzogni/FTXUI/issues/426
2023-06-25 17:22:05 +02:00

250 lines
6.7 KiB
C++

#include <functional> // for function
#include <memory> // for allocator_traits<>::value_type
#include <utility> // for move
#include <vector> // for vector
#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse
#include "ftxui/component/component.hpp" // for Make, Radiobox
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/component_options.hpp" // for RadioboxOption, EntryState
#include "ftxui/component/event.hpp" // for Event, Event::ArrowDown, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp, Event::Return, Event::Tab, Event::TabReverse
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::WheelDown, Mouse::WheelUp, Mouse::Left, Mouse::Released
#include "ftxui/component/screen_interactive.hpp" // for Component
#include "ftxui/dom/elements.hpp" // for operator|, reflect, Element, vbox, Elements, focus, nothing, select
#include "ftxui/screen/box.hpp" // for Box
#include "ftxui/screen/util.hpp" // for clamp
#include "ftxui/util/ref.hpp" // for Ref, ConstStringListRef
namespace ftxui {
namespace {
/// @brief A list of selectable element. One and only one can be selected at
/// the same time.
/// @ingroup component
class RadioboxBase : public ComponentBase, public RadioboxOption {
public:
explicit RadioboxBase(RadioboxOption option) : RadioboxOption(option) {}
private:
Element Render() override {
Clamp();
Elements elements;
const bool is_menu_focused = Focused();
elements.reserve(size());
for (int i = 0; i < size(); ++i) {
const bool is_focused = (focused_entry() == i) && is_menu_focused;
const bool is_selected = (hovered_ == i);
auto focus_management = !is_selected ? nothing
: is_menu_focused ? focus
: select;
auto state = EntryState{
entries[i],
selected() == i,
is_selected,
is_focused,
};
auto element =
(transform ? transform : RadioboxOption::Simple().transform)(state);
elements.push_back(element | focus_management | reflect(boxes_[i]));
}
return vbox(std::move(elements)) | reflect(box_);
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
bool OnEvent(Event event) override {
Clamp();
if (!CaptureMouse(event)) {
return false;
}
if (event.is_mouse()) {
return OnMouseEvent(event);
}
if (Focused()) {
const int old_hovered = hovered_;
if (event == Event::ArrowUp || event == Event::Character('k')) {
(hovered_)--;
}
if (event == Event::ArrowDown || event == Event::Character('j')) {
(hovered_)++;
}
if (event == Event::PageUp) {
(hovered_) -= box_.y_max - box_.y_min;
}
if (event == Event::PageDown) {
(hovered_) += box_.y_max - box_.y_min;
}
if (event == Event::Home) {
(hovered_) = 0;
}
if (event == Event::End) {
(hovered_) = size() - 1;
}
if (event == Event::Tab && size()) {
hovered_ = (hovered_ + 1) % size();
}
if (event == Event::TabReverse && size()) {
hovered_ = (hovered_ + size() - 1) % size();
}
hovered_ = util::clamp(hovered_, 0, size() - 1);
if (hovered_ != old_hovered) {
focused_entry() = hovered_;
on_change();
return true;
}
}
if (event == Event::Character(' ') || event == Event::Return) {
selected() = hovered_;
on_change();
return true;
}
return false;
}
bool OnMouseEvent(Event event) {
if (event.mouse().button == Mouse::WheelDown ||
event.mouse().button == Mouse::WheelUp) {
return OnMouseWheel(event);
}
for (int i = 0; i < size(); ++i) {
if (!boxes_[i].Contain(event.mouse().x, event.mouse().y)) {
continue;
}
TakeFocus();
focused_entry() = i;
if (event.mouse().button == Mouse::Left &&
event.mouse().motion == Mouse::Released) {
if (selected() != i) {
selected() = i;
on_change();
}
return true;
}
}
return false;
}
bool OnMouseWheel(Event event) {
if (!box_.Contain(event.mouse().x, event.mouse().y)) {
return false;
}
const int old_hovered = hovered_;
if (event.mouse().button == Mouse::WheelUp) {
(hovered_)--;
}
if (event.mouse().button == Mouse::WheelDown) {
(hovered_)++;
}
hovered_ = util::clamp(hovered_, 0, size() - 1);
if (hovered_ != old_hovered) {
on_change();
}
return true;
}
void Clamp() {
boxes_.resize(size());
selected() = util::clamp(selected(), 0, size() - 1);
focused_entry() = util::clamp(focused_entry(), 0, size() - 1);
hovered_ = util::clamp(hovered_, 0, size() - 1);
}
bool Focusable() const final { return entries.size(); }
int size() const { return int(entries.size()); }
int hovered_ = selected();
std::vector<Box> boxes_;
Box box_;
};
} // namespace
/// @brief A list of element, where only one can be selected.
/// @param option The parameters
/// @ingroup component
/// @see RadioboxBase
///
/// ### Example
///
/// ```cpp
/// auto screen = ScreenInteractive::TerminalOutput();
/// std::vector<std::string> entries = {
/// "entry 1",
/// "entry 2",
/// "entry 3",
/// };
/// int selected = 0;
/// auto menu = Radiobox({
/// .entries = entries,
/// .selected = &selected,
/// });
/// screen.Loop(menu);
/// ```
///
/// ### Output
///
/// ```bash
/// ◉ entry 1
/// ○ entry 2
/// ○ entry 3
/// ```
Component Radiobox(RadioboxOption option) {
return Make<RadioboxBase>(std::move(option));
}
/// @brief A list of element, where only one can be selected.
/// @param entries The list of entries in the list.
/// @param selected The index of the currently selected element.
/// @param option Additional optional parameters.
/// @ingroup component
/// @see RadioboxBase
///
/// ### Example
///
/// ```cpp
/// auto screen = ScreenInteractive::TerminalOutput();
/// std::vector<std::string> entries = {
/// "entry 1",
/// "entry 2",
/// "entry 3",
/// };
/// int selected = 0;
/// auto menu = Radiobox(&entries, &selected);
/// screen.Loop(menu);
/// ```
///
/// ### Output
///
/// ```bash
/// ◉ entry 1
/// ○ entry 2
/// ○ entry 3
/// ```
Component Radiobox(ConstStringListRef entries,
int* selected,
RadioboxOption option) {
option.entries = entries;
option.selected = selected;
return Make<RadioboxBase>(std::move(option));
}
} // namespace ftxui
// Copyright 2020 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.