diff --git a/examples/component/CMakeLists.txt b/examples/component/CMakeLists.txt index c30dd6d..7e02bf0 100644 --- a/examples/component/CMakeLists.txt +++ b/examples/component/CMakeLists.txt @@ -9,6 +9,7 @@ example(gallery) example(homescreen) example(input) example(menu) +example(menu_in_frame) example(menu2) example(menu_multiple) example(menu_entries) diff --git a/examples/component/checkbox_in_frame.cpp b/examples/component/checkbox_in_frame.cpp index 63fe11b..6a40dbf 100644 --- a/examples/component/checkbox_in_frame.cpp +++ b/examples/component/checkbox_in_frame.cpp @@ -15,22 +15,20 @@ struct CheckboxState { }; int main(int argc, const char* argv[]) { - int size = 30; - std::vector states(size); + std::vector states(30); auto container = Container::Vertical({}); - for (int i = 0; i < size; ++i) { + for (int i = 0; i < 30; ++i) { states[i].checked = false; container->Add( Checkbox("Checkbox" + std::to_string(i), &states[i].checked)); } - auto component = Renderer(container, [&] { - return container->Render() | frame | ftxui::size(HEIGHT, LESS_THAN, 10) | - border; + auto renderer = Renderer(container, [&] { + return container->Render() | frame | size(HEIGHT, LESS_THAN, 10) | border; }); auto screen = ScreenInteractive::FitComponent(); - screen.Loop(component); + screen.Loop(renderer); return 0; } diff --git a/examples/component/homescreen.cpp b/examples/component/homescreen.cpp index 0ddb3b4..0ae19fa 100644 --- a/examples/component/homescreen.cpp +++ b/examples/component/homescreen.cpp @@ -137,17 +137,18 @@ int main(int argc, const char* argv[]) { int compiler_selected = 0; Component compiler = Radiobox(&compiler_entries, &compiler_selected); - std::array options_label = { + std::array options_label = { "-Wall", "-Werror", "-lpthread", "-O3", + "-Wabi-tag", + "-Wno-class-conversion", + "-Wcomma-subscript", + "-Wno-conversion-null", }; - std::array options_state = { - false, - false, - false, - false, + std::array options_state = { + false, false, false, false, false, false, false, false, }; std::vector input_entries; @@ -170,6 +171,10 @@ int main(int argc, const char* argv[]) { Checkbox(&options_label[1], &options_state[1]), Checkbox(&options_label[2], &options_state[2]), Checkbox(&options_label[3], &options_state[3]), + Checkbox(&options_label[4], &options_state[4]), + Checkbox(&options_label[5], &options_state[5]), + Checkbox(&options_label[6], &options_state[6]), + Checkbox(&options_label[7], &options_state[7]), }); auto compiler_component = Container::Horizontal({ @@ -189,7 +194,7 @@ int main(int argc, const char* argv[]) { // Compiler line.push_back(text(compiler_entries[compiler_selected]) | bold); // flags - for (int i = 0; i < 4; ++i) { + for (int i = 0; i < 8; ++i) { if (options_state[i]) { line.push_back(text(" ")); line.push_back(text(options_label[i]) | dim); @@ -210,7 +215,7 @@ int main(int argc, const char* argv[]) { auto compiler_renderer = Renderer(compiler_component, [&] { auto compiler_win = window(text("Compiler"), compiler->Render() | frame); - auto flags_win = window(text("Flags"), flags->Render()); + auto flags_win = window(text("Flags"), flags->Render() | frame); auto executable_win = window(text("Executable:"), executable_->Render()); auto input_win = window(text("Input"), @@ -228,14 +233,14 @@ int main(int argc, const char* argv[]) { })); return vbox({ hbox({ - compiler_win | size(HEIGHT, LESS_THAN, 6), + compiler_win, flags_win, vbox({ executable_win | size(WIDTH, EQUAL, 20), input_win | size(WIDTH, EQUAL, 60), }), filler(), - }), + }) | size(HEIGHT, LESS_THAN, 6), hflow(render_command()) | flex_grow, }) | flex_grow | border; diff --git a/examples/component/menu_in_frame.cpp b/examples/component/menu_in_frame.cpp new file mode 100644 index 0000000..1cfda32 --- /dev/null +++ b/examples/component/menu_in_frame.cpp @@ -0,0 +1,32 @@ +#include // for shared_ptr, __shared_ptr_access +#include // for string, basic_string, operator+, to_string +#include // for vector + +#include "ftxui/component/captured_mouse.hpp" // for ftxui +#include "ftxui/component/component.hpp" // for Radiobox, Renderer +#include "ftxui/component/component_base.hpp" // for ComponentBase +#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive +#include "ftxui/dom/elements.hpp" // for operator|, Element, size, border, frame, HEIGHT, LESS_THAN + +using namespace ftxui; + +int main(int argc, const char* argv[]) { + std::vector entries; + int selected = 0; + + for (int i = 0; i < 30; ++i) + entries.push_back("Entry " + std::to_string(i)); + auto radiobox = Menu(&entries, &selected); + auto renderer = Renderer(radiobox, [&] { + return radiobox->Render() | frame | size(HEIGHT, LESS_THAN, 10) | border; + }); + + auto screen = ScreenInteractive::FitComponent(); + screen.Loop(renderer); + + return 0; +} + +// 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. diff --git a/include/ftxui/component/component_options.hpp b/include/ftxui/component/component_options.hpp index dfcda02..03a16b7 100644 --- a/include/ftxui/component/component_options.hpp +++ b/include/ftxui/component/component_options.hpp @@ -45,8 +45,11 @@ struct ButtonOption { struct CheckboxOption { std::string style_checked = "▣ "; ///< Prefix for a "checked" state. std::string style_unchecked = "☐ "; ///< Prefix for a "unchecked" state. - Decorator style_focused = inverted; ///< Decorator used when focused. - Decorator style_unfocused = nothing; ///< Decorator used when unfocused. + Decorator style_normal = nothing; ///< style. + Decorator style_focused = inverted; ///< Style when focused. + Decorator style_selected = bold; ///< Style when selected. + Decorator style_selected_focused = + Decorator(inverted) | bold; ///< Style when selected and focused. /// Called when the user change the state. std::function on_change = []() {}; @@ -73,8 +76,11 @@ struct InputOption { struct RadioboxOption { std::string style_checked = "◉ "; ///< Prefix for a "checked" state. std::string style_unchecked = "○ "; ///< Prefix for a "unchecked" state. - Decorator style_focused = inverted; ///< Decorator used when focused. - Decorator style_unfocused = nothing; ///< Decorator used when unfocused. + Decorator style_normal = nothing; ///< style. + Decorator style_focused = inverted; ///< Style when focused. + Decorator style_selected = bold; ///< Style when selected. + Decorator style_selected_focused = + Decorator(inverted) | bold; ///< Style when selected and focused. /// Called when the selected entry changes. std::function on_change = []() {}; diff --git a/src/ftxui/component/checkbox.cpp b/src/ftxui/component/checkbox.cpp index 19f5edc..e8f0910 100644 --- a/src/ftxui/component/checkbox.cpp +++ b/src/ftxui/component/checkbox.cpp @@ -33,8 +33,12 @@ class CheckboxBase : public ComponentBase { // Component implementation. Element Render() override { bool is_focused = Focused(); - auto style = is_focused ? option_->style_focused : option_->style_unfocused; - auto focus_management = is_focused ? focus : *state_ ? select : nothing; + bool is_active = Active(); + auto style = is_focused ? (hovered_ ? option_->style_selected_focused + : option_->style_selected) + : (hovered_ ? option_->style_focused + : option_->style_normal); + auto focus_management = is_focused ? focus : is_active ? select : nothing; return hbox(text(*state_ ? option_->style_checked : option_->style_unchecked), text(*label_) | style | focus_management) | @@ -45,6 +49,7 @@ class CheckboxBase : public ComponentBase { if (event.is_mouse()) return OnMouseEvent(event); + hovered_ = false; if (event == Event::Character(' ') || event == Event::Return) { *state_ = !*state_; option_->on_change(); @@ -54,12 +59,13 @@ class CheckboxBase : public ComponentBase { } bool OnMouseEvent(Event event) { + hovered_ = box_.Contain(event.mouse().x, event.mouse().y); + if (!CaptureMouse(event)) return false; - if (!box_.Contain(event.mouse().x, event.mouse().y)) - return false; - TakeFocus(); + if (!hovered_) + return false; if (event.mouse().button == Mouse::Left && event.mouse().motion == Mouse::Pressed) { @@ -75,8 +81,9 @@ class CheckboxBase : public ComponentBase { ConstStringRef label_; bool* const state_; - Box box_; + bool hovered_ = false; Ref option_; + Box box_; }; } // namespace diff --git a/src/ftxui/component/container.cpp b/src/ftxui/component/container.cpp index 499b948..27156d5 100644 --- a/src/ftxui/component/container.cpp +++ b/src/ftxui/component/container.cpp @@ -54,11 +54,7 @@ class ContainerBase : public ComponentBase { virtual bool EventHandler(Event) { return false; } virtual bool OnMouseEvent(Event event) { - for (Component& child : children_) { - if (child->OnEvent(event)) - return true; - } - return false; + return ComponentBase::OnEvent(event); } int selected_ = 0; @@ -111,6 +107,27 @@ class VerticalContainer : public ContainerBase { *selector_ = std::max(0, std::min(int(children_.size()) - 1, *selector_)); return old_selected != *selector_; } + + bool OnMouseEvent(Event event) override { + if (ContainerBase::OnMouseEvent(event)) + return true; + + if (event.mouse().button != Mouse::WheelUp && + event.mouse().button != Mouse::WheelDown) { + return false; + } + + if (!Focusable()) + return false; + + if (event.mouse().button == Mouse::WheelUp) + MoveSelector(-1); + if (event.mouse().button == Mouse::WheelDown) + MoveSelector(+1); + *selector_ = std::max(0, std::min(int(children_.size()) - 1, *selector_)); + + return true; + } }; class HorizontalContainer : public ContainerBase { diff --git a/src/ftxui/component/input.cpp b/src/ftxui/component/input.cpp index baad765..c78bf60 100644 --- a/src/ftxui/component/input.cpp +++ b/src/ftxui/component/input.cpp @@ -52,14 +52,14 @@ class WideInputBase : public ComponentBase { if (content.size() == 0) { if (is_focused) return text(*placeholder_) | focus | dim | inverted | main_decorator | - reflect(input_box_); + reflect(box_); else - return text(*placeholder_) | dim | main_decorator | reflect(input_box_); + return text(*placeholder_) | dim | main_decorator | reflect(box_); } // Not focused. if (!is_focused) - return text(content) | main_decorator | reflect(input_box_); + return text(content) | main_decorator | reflect(box_); std::wstring part_before_cursor = content.substr(0, cursor_position()); std::wstring part_at_cursor = cursor_position() < (int)content.size() @@ -76,7 +76,7 @@ class WideInputBase : public ComponentBase { text(part_before_cursor), text(part_at_cursor) | underlined | focused | reflect(cursor_box_), text(part_after_cursor) - ) | flex | inverted | frame | main_decorator | reflect(input_box_); + ) | flex | inverted | frame | main_decorator | reflect(box_); // clang-format on } @@ -153,7 +153,7 @@ class WideInputBase : public ComponentBase { bool OnMouseEvent(Event event) { if (!CaptureMouse(event)) return false; - if (!input_box_.Contain(event.mouse().x, event.mouse().y)) + if (!box_.Contain(event.mouse().x, event.mouse().y)) return false; TakeFocus(); @@ -177,7 +177,7 @@ class WideInputBase : public ComponentBase { WideStringRef content_; ConstStringRef placeholder_; - Box input_box_; + Box box_; Box cursor_box_; Ref option_; }; diff --git a/src/ftxui/component/menu.cpp b/src/ftxui/component/menu.cpp index 7d5c728..e3d9382 100644 --- a/src/ftxui/component/menu.cpp +++ b/src/ftxui/component/menu.cpp @@ -26,7 +26,7 @@ class MenuBase : public ComponentBase { MenuBase(ConstStringListRef entries, int* selected, Ref option) : entries_(entries), selected_(selected), option_(option) {} - Element Render() { + Element Render() override { Elements elements; bool is_menu_focused = Focused(); boxes_.resize(entries_.size()); @@ -45,34 +45,34 @@ class MenuBase : public ComponentBase { elements.push_back(text(icon + entries_[i]) | style | focus_management | reflect(boxes_[i])); } - return vbox(std::move(elements)); + return vbox(std::move(elements)) | reflect(box_); } - bool OnEvent(Event event) { + bool OnEvent(Event event) override { if (!CaptureMouse(event)) return false; + if (event.is_mouse()) return OnMouseEvent(event); - if (!Focused()) - return false; + if (Focused()) { + int old_selected = *selected_; + if (event == Event::ArrowUp || event == Event::Character('k')) + (*selected_)--; + if (event == Event::ArrowDown || event == Event::Character('j')) + (*selected_)++; + if (event == Event::Tab && entries_.size()) + *selected_ = (*selected_ + 1) % entries_.size(); + if (event == Event::TabReverse && entries_.size()) + *selected_ = (*selected_ + entries_.size() - 1) % entries_.size(); - int old_selected = *selected_; - if (event == Event::ArrowUp || event == Event::Character('k')) - (*selected_)--; - if (event == Event::ArrowDown || event == Event::Character('j')) - (*selected_)++; - if (event == Event::Tab && entries_.size()) - *selected_ = (*selected_ + 1) % entries_.size(); - if (event == Event::TabReverse && entries_.size()) - *selected_ = (*selected_ + entries_.size() - 1) % entries_.size(); + *selected_ = std::max(0, std::min(int(entries_.size()) - 1, *selected_)); - *selected_ = std::max(0, std::min(int(entries_.size()) - 1, *selected_)); - - if (*selected_ != old_selected) { - focused_entry() = *selected_; - option_->on_change(); - return true; + if (*selected_ != old_selected) { + focused_entry() = *selected_; + option_->on_change(); + return true; + } } if (event == Event::Return) { @@ -84,6 +84,15 @@ class MenuBase : public ComponentBase { } bool OnMouseEvent(Event event) { + if (event.mouse().button == Mouse::WheelDown || + event.mouse().button == Mouse::WheelUp) { + return OnMouseWheel(event); + } + + if (event.mouse().button != Mouse::None && + event.mouse().button != Mouse::Left) { + return false; + } if (!CaptureMouse(event)) return false; for (int i = 0; i < int(boxes_.size()); ++i) { @@ -104,6 +113,23 @@ class MenuBase : public ComponentBase { return false; } + bool OnMouseWheel(Event event) { + if (!box_.Contain(event.mouse().x, event.mouse().y)) + return false; + int old_selected = *selected_; + + if (event.mouse().button == Mouse::WheelUp) + (*selected_)--; + if (event.mouse().button == Mouse::WheelDown) + (*selected_)++; + + *selected_ = std::max(0, std::min(int(entries_.size()) - 1, *selected_)); + + if (*selected_ != old_selected) + option_->on_change(); + return true; + } + bool Focusable() const final { return entries_.size(); } int& focused_entry() { return option_->focused_entry(); } @@ -113,6 +139,7 @@ class MenuBase : public ComponentBase { Ref option_; std::vector boxes_; + Box box_; }; /// @brief A list of text. The focused element is selected. @@ -158,10 +185,10 @@ Component MenuEntry(ConstStringRef label, Ref option) { private: Element Render() override { bool focused = Focused(); - auto style = - hovered_ ? (focused ? option_->style_selected_focused - : option_->style_selected) - : (focused ? option_->style_focused : option_->style_normal); + auto style = hovered_ ? (focused ? option_->style_selected_focused + : option_->style_selected) + : (focused ? option_->style_focused + : option_->style_normal); auto focus_management = focused ? select : nothing; auto label = focused ? "> " + (*label_) // : " " + (*label_); diff --git a/src/ftxui/component/radiobox.cpp b/src/ftxui/component/radiobox.cpp index 24a713d..0c5cff1 100644 --- a/src/ftxui/component/radiobox.cpp +++ b/src/ftxui/component/radiobox.cpp @@ -37,20 +37,25 @@ class RadioboxBase : public ComponentBase { if (option_->style_unchecked == "○ ") option_->style_unchecked = "( )"; #endif + hovered_ = *selected_; } private: Element Render() override { Elements elements; - bool is_focused = Focused(); + bool is_menu_focused = Focused(); boxes_.resize(entries_.size()); for (size_t i = 0; i < entries_.size(); ++i) { - auto style = (focused_entry() == int(i) && is_focused) - ? option_->style_focused - : option_->style_unfocused; - auto focus_management = (focused_entry() != int(i)) ? nothing - : is_focused ? focus - : select; + bool is_focused = (focused_entry() == int(i)) && is_menu_focused; + bool is_selected = (hovered_ == int(i)); + + auto style = is_selected ? (is_focused ? option_->style_selected_focused + : option_->style_selected) + : (is_focused ? option_->style_focused + : option_->style_normal); + auto focus_management = !is_selected ? nothing + : is_menu_focused ? focus + : select; const std::string& symbol = *selected_ == int(i) ? option_->style_checked @@ -58,37 +63,39 @@ class RadioboxBase : public ComponentBase { elements.push_back(hbox(text(symbol), text(entries_[i]) | style) | focus_management | reflect(boxes_[i])); } - return vbox(std::move(elements)); + return vbox(std::move(elements)) | reflect(box_); } bool OnEvent(Event event) override { if (!CaptureMouse(event)) return false; + if (event.is_mouse()) return OnMouseEvent(event); - if (!Focused()) - return false; + if (Focused()) { + 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::Tab && entries_.size()) + hovered_ = (hovered_ + 1) % entries_.size(); + if (event == Event::TabReverse && entries_.size()) + hovered_ = (hovered_ + entries_.size() - 1) % entries_.size(); - int new_focused = focused_entry(); - if (event == Event::ArrowUp || event == Event::Character('k')) - new_focused--; - if (event == Event::ArrowDown || event == Event::Character('j')) - new_focused++; - if (event == Event::Tab && entries_.size()) - new_focused = (new_focused + 1) % entries_.size(); - if (event == Event::TabReverse && entries_.size()) - new_focused = (new_focused + entries_.size() - 1) % entries_.size(); + hovered_ = std::max(0, std::min(int(entries_.size()) - 1, hovered_)); - new_focused = std::max(0, std::min(int(entries_.size()) - 1, new_focused)); - - if (focused_entry() != new_focused) { - focused_entry() = new_focused; - return true; + if (hovered_ != old_hovered) { + focused_entry() = hovered_; + option_->on_change(); + return true; + } } if (event == Event::Character(' ') || event == Event::Return) { - *selected_ = focused_entry(); + *selected_ = hovered_; + //*selected_ = focused_entry(); option_->on_change(); } @@ -98,35 +105,58 @@ class RadioboxBase : public ComponentBase { bool OnMouseEvent(Event event) { if (!CaptureMouse(event)) return false; + + if (event.mouse().button == Mouse::WheelDown || + event.mouse().button == Mouse::WheelUp) { + return OnMouseWheel(event); + } + for (int i = 0; i < int(boxes_.size()); ++i) { if (!boxes_[i].Contain(event.mouse().x, event.mouse().y)) continue; - focused_entry() = i; TakeFocus(); - + focused_entry() = i; if (event.mouse().button == Mouse::Left && - event.mouse().motion == Mouse::Pressed) { - cursor_position = i; - TakeFocus(); + event.mouse().motion == Mouse::Released) { if (*selected_ != i) { *selected_ = i; option_->on_change(); } + return true; } } return false; } + bool OnMouseWheel(Event event) { + if (!box_.Contain(event.mouse().x, event.mouse().y)) + return false; + + int old_hovered = hovered_; + + if (event.mouse().button == Mouse::WheelUp) + (hovered_)--; + if (event.mouse().button == Mouse::WheelDown) + (hovered_)++; + + hovered_ = std::max(0, std::min(int(entries_.size()) - 1, hovered_)); + + if (hovered_ != old_hovered) + option_->on_change(); + + return true; + } + bool Focusable() const final { return entries_.size(); } int& focused_entry() { return option_->focused_entry(); } ConstStringListRef entries_; - int* const selected_; - - int cursor_position = 0; + int* selected_; + int hovered_; std::vector boxes_; + Box box_; Ref option_; }; diff --git a/src/ftxui/component/resizable_split.cpp b/src/ftxui/component/resizable_split.cpp index 863d7d2..d6efd1e 100644 --- a/src/ftxui/component/resizable_split.cpp +++ b/src/ftxui/component/resizable_split.cpp @@ -43,7 +43,7 @@ class ResizableSplitLeftBase : public ComponentBase { } if (captured_mouse_) { - *main_size_ = event.mouse().x - global_box_.x_min; + *main_size_ = event.mouse().x - box_.x_min; return true; } @@ -56,7 +56,7 @@ class ResizableSplitLeftBase : public ComponentBase { separator() | reflect(separator_box_), child_->Render() | xflex, }) | - reflect(global_box_); + reflect(box_); }; private: @@ -65,7 +65,7 @@ class ResizableSplitLeftBase : public ComponentBase { int* const main_size_; CapturedMouse captured_mouse_; Box separator_box_; - Box global_box_; + Box box_; }; class ResizableSplitRightBase : public ComponentBase { @@ -99,7 +99,7 @@ class ResizableSplitRightBase : public ComponentBase { } if (captured_mouse_) { - *main_size_ = global_box_.x_max - event.mouse().x; + *main_size_ = box_.x_max - event.mouse().x; return true; } @@ -112,7 +112,7 @@ class ResizableSplitRightBase : public ComponentBase { separator() | reflect(separator_box_), main_->Render() | size(WIDTH, EQUAL, *main_size_), }) | - reflect(global_box_); + reflect(box_); }; private: @@ -121,7 +121,7 @@ class ResizableSplitRightBase : public ComponentBase { int* const main_size_; CapturedMouse captured_mouse_; Box separator_box_; - Box global_box_; + Box box_; }; class ResizableSplitTopBase : public ComponentBase { @@ -155,7 +155,7 @@ class ResizableSplitTopBase : public ComponentBase { } if (captured_mouse_) { - *main_size_ = event.mouse().y - global_box_.y_min; + *main_size_ = event.mouse().y - box_.y_min; return true; } @@ -168,7 +168,7 @@ class ResizableSplitTopBase : public ComponentBase { separator() | reflect(separator_box_), child_->Render() | yflex, }) | - reflect(global_box_); + reflect(box_); }; private: @@ -177,7 +177,7 @@ class ResizableSplitTopBase : public ComponentBase { int* const main_size_; CapturedMouse captured_mouse_; Box separator_box_; - Box global_box_; + Box box_; }; class ResizableSplitBottomBase : public ComponentBase { @@ -211,7 +211,7 @@ class ResizableSplitBottomBase : public ComponentBase { } if (captured_mouse_) { - *main_size_ = global_box_.y_max - event.mouse().y; + *main_size_ = box_.y_max - event.mouse().y; return true; } @@ -224,7 +224,7 @@ class ResizableSplitBottomBase : public ComponentBase { separator() | reflect(separator_box_), main_->Render() | size(HEIGHT, EQUAL, *main_size_), }) | - reflect(global_box_); + reflect(box_); }; private: @@ -233,7 +233,7 @@ class ResizableSplitBottomBase : public ComponentBase { int* const main_size_; CapturedMouse captured_mouse_; Box separator_box_; - Box global_box_; + Box box_; }; } // namespace diff --git a/src/ftxui/dom/reflect.cpp b/src/ftxui/dom/reflect.cpp index 2d4b2a6..75d5846 100644 --- a/src/ftxui/dom/reflect.cpp +++ b/src/ftxui/dom/reflect.cpp @@ -22,8 +22,13 @@ class Reflect : public Node { void SetBox(Box box) final { reflected_box_ = box; - Node::SetBox(reflected_box_); - children_[0]->SetBox(reflected_box_); + Node::SetBox(box); + children_[0]->SetBox(box); + } + + void Render(Screen& screen) final { + reflected_box_ = Box::Intersection(screen.stencil, reflected_box_); + return Node::Render(screen); } private: