From 890a41a64c9109c8867f8e7eab46e8212cf5995f Mon Sep 17 00:00:00 2001 From: ArthurSonzogni Date: Sun, 18 Apr 2021 22:33:41 +0200 Subject: [PATCH] Add mouse implementation of most components. --- CMakeLists.txt | 1 + include/ftxui/component/button.hpp | 2 ++ include/ftxui/component/checkbox.hpp | 4 +++ include/ftxui/component/container.hpp | 3 ++ include/ftxui/component/event.hpp | 3 ++ include/ftxui/component/menu.hpp | 5 +++ include/ftxui/component/radiobox.hpp | 2 ++ include/ftxui/component/toggle.hpp | 4 +++ include/ftxui/dom/elements.hpp | 4 +++ include/ftxui/screen/box.hpp | 1 + src/ftxui/component/button.cpp | 21 ++++++++--- src/ftxui/component/checkbox.cpp | 24 ++++++++++++- src/ftxui/component/container.cpp | 14 ++++++++ src/ftxui/component/event.cpp | 22 ++++++++++++ src/ftxui/component/menu.cpp | 24 ++++++++++++- src/ftxui/component/radiobox.cpp | 30 +++++++++++++++- src/ftxui/component/screen_interactive.cpp | 5 ++- src/ftxui/component/toggle.cpp | 30 +++++++++++++--- src/ftxui/dom/reflect.cpp | 42 ++++++++++++++++++++++ src/ftxui/screen/box.cpp | 10 ++++++ 20 files changed, 239 insertions(+), 12 deletions(-) create mode 100644 src/ftxui/dom/reflect.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b237613..92814d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,7 @@ add_library(dom src/ftxui/dom/node.cpp src/ftxui/dom/node_decorator.cpp src/ftxui/dom/paragraph.cpp + src/ftxui/dom/reflect.cpp src/ftxui/dom/separator.cpp src/ftxui/dom/size.cpp src/ftxui/dom/spinner.cpp diff --git a/include/ftxui/component/button.hpp b/include/ftxui/component/button.hpp index c734e75..76e4186 100644 --- a/include/ftxui/component/button.hpp +++ b/include/ftxui/component/button.hpp @@ -25,6 +25,8 @@ class Button : public Component { // Component implementation. Element Render() override; bool OnEvent(Event) override; + private: + Box box_; }; } // namespace ftxui diff --git a/include/ftxui/component/checkbox.hpp b/include/ftxui/component/checkbox.hpp index a92b8cd..f21121b 100644 --- a/include/ftxui/component/checkbox.hpp +++ b/include/ftxui/component/checkbox.hpp @@ -38,7 +38,11 @@ class CheckBox : public Component { bool OnEvent(Event) override; private: + bool OnMouseEvent(Event event); + int cursor_position = 0; + Box box_; + }; } // namespace ftxui diff --git a/include/ftxui/component/container.hpp b/include/ftxui/component/container.hpp index 7054be1..03cee39 100644 --- a/include/ftxui/component/container.hpp +++ b/include/ftxui/component/container.hpp @@ -36,6 +36,9 @@ class Container : public Component { int selected_ = 0; int* selector_ = nullptr; + + private: + bool OnMouseEvent(Event event); }; } // namespace ftxui diff --git a/include/ftxui/component/event.hpp b/include/ftxui/component/event.hpp index 833ac7d..5c05578 100644 --- a/include/ftxui/component/event.hpp +++ b/include/ftxui/component/event.hpp @@ -59,6 +59,7 @@ struct Event { bool is_character() const { return type_ == Type::Character;} wchar_t character() const { return character_; } + bool is_mouse() const; bool is_mouse_left_down() const { return type_ == Type::MouseLeftDown; } bool is_mouse_left_move() const { return type_ == Type::MouseLeftMove; } bool is_mouse_middle_down() const { return type_ == Type::MouseMiddleDown; } @@ -74,6 +75,8 @@ struct Event { bool operator==(const Event& other) const { return input_ == other.input_; } + void MoveMouse(int dx, int dy); + //--- State section ---------------------------------------------------------- private: enum class Type { diff --git a/include/ftxui/component/menu.hpp b/include/ftxui/component/menu.hpp index ece6fe8..51fb148 100644 --- a/include/ftxui/component/menu.hpp +++ b/include/ftxui/component/menu.hpp @@ -31,6 +31,11 @@ class Menu : public Component { // Component implementation. Element Render() override; bool OnEvent(Event) override; + + private: + bool OnMouseEvent(Event); + + std::vector boxes_; }; } // namespace ftxui diff --git a/include/ftxui/component/radiobox.hpp b/include/ftxui/component/radiobox.hpp index aaf9828..bbfc6f7 100644 --- a/include/ftxui/component/radiobox.hpp +++ b/include/ftxui/component/radiobox.hpp @@ -39,7 +39,9 @@ class RadioBox : public Component { bool OnEvent(Event) override; private: + bool OnMouseEvent(Event event); int cursor_position = 0; + std::vector boxes_; }; } // namespace ftxui diff --git a/include/ftxui/component/toggle.hpp b/include/ftxui/component/toggle.hpp index bbe095c..846d11b 100644 --- a/include/ftxui/component/toggle.hpp +++ b/include/ftxui/component/toggle.hpp @@ -30,6 +30,10 @@ class Toggle : public Component { // Component implementation. Element Render() override; bool OnEvent(Event) override; + + private: + bool OnMouseEvent(Event event); + std::vector boxes_; }; } // namespace ftxui diff --git a/include/ftxui/dom/elements.hpp b/include/ftxui/dom/elements.hpp index 3019163..614b1c1 100644 --- a/include/ftxui/dom/elements.hpp +++ b/include/ftxui/dom/elements.hpp @@ -5,6 +5,7 @@ #include #include "ftxui/dom/node.hpp" +#include "ftxui/screen/box.hpp" #include "ftxui/screen/color.hpp" #include "ftxui/screen/screen.hpp" @@ -77,6 +78,9 @@ enum Direction { WIDTH, HEIGHT }; enum Constraint { LESS_THAN, EQUAL, GREATER_THAN }; Decorator size(Direction, Constraint, int value); +// -- +Decorator reflect(Box& box); + // --- Frame --- // A frame is a scrollable area. The internal area is potentially larger than // the external one. The internal area is scrolled in order to make visible the diff --git a/include/ftxui/screen/box.hpp b/include/ftxui/screen/box.hpp index 3a64302..719e1bd 100644 --- a/include/ftxui/screen/box.hpp +++ b/include/ftxui/screen/box.hpp @@ -10,6 +10,7 @@ struct Box { int y_max; static Box Intersection(Box a, Box b); + bool Contain(int x, int y); }; } // namespace ftxui diff --git a/src/ftxui/component/button.cpp b/src/ftxui/component/button.cpp index 581298e..8281268 100644 --- a/src/ftxui/component/button.cpp +++ b/src/ftxui/component/button.cpp @@ -5,13 +5,26 @@ namespace ftxui { Element Button::Render() { - if (Focused()) - return text(label) | border | inverted; - else - return text(label) | border; + return text(label) | // + border | // + (Focused() ? inverted : nothing) | // + reflect(box_); } bool Button::OnEvent(Event event) { + if (event.is_mouse() && box_.Contain(event.mouse_x(), event.mouse_y())) { + if (event.is_mouse_move()) { + TakeFocus(); + return true; + } + if (event.is_mouse_left_down()) { + on_click(); + return true; + } + + return false; + } + if (event == Event::Return) { on_click(); return true; diff --git a/src/ftxui/component/checkbox.cpp b/src/ftxui/component/checkbox.cpp index 9f9cfd9..ae3dc0c 100644 --- a/src/ftxui/component/checkbox.cpp +++ b/src/ftxui/component/checkbox.cpp @@ -9,10 +9,14 @@ Element CheckBox::Render() { auto style = is_focused ? focused_style : unfocused_style; auto focus_management = is_focused ? focus : state ? select : nothing; return hbox(text(state ? checked : unchecked), - text(label) | style | focus_management); + text(label) | style | focus_management) | + reflect(box_); } bool CheckBox::OnEvent(Event event) { + if (event.is_mouse()) + return OnMouseEvent(event); + if (event == Event::Character(' ') || event == Event::Return) { state = !state; on_change(); @@ -21,6 +25,24 @@ bool CheckBox::OnEvent(Event event) { return false; } +bool CheckBox::OnMouseEvent(Event event) { + if (!box_.Contain(event.mouse_x(), event.mouse_y())) + return false; + + if (event.is_mouse_move()) { + TakeFocus(); + return true; + } + + if (event.is_mouse_left_down()) { + state = !state; + on_change(); + return true; + } + + return false; +} + } // namespace ftxui // Copyright 2020 Arthur Sonzogni. All rights reserved. diff --git a/src/ftxui/component/container.cpp b/src/ftxui/component/container.cpp index 4b55bcd..0fdd438 100644 --- a/src/ftxui/component/container.cpp +++ b/src/ftxui/component/container.cpp @@ -30,6 +30,9 @@ Container Container::Tab(int* selector) { } bool Container::OnEvent(Event event) { + if (event.is_mouse()) + return OnMouseEvent(event); + if (!Focused()) return false; @@ -115,6 +118,17 @@ Element Container::TabRender() { return text(L"Empty container"); } +bool Container::OnMouseEvent(Event event) { + if (selector_) + return ActiveChild()->OnEvent(event); + + for (Component* child : children_) { + if (child->OnEvent(event)) + return true; + } + return false; +} + } // namespace ftxui // Copyright 2020 Arthur Sonzogni. All rights reserved. diff --git a/src/ftxui/component/event.cpp b/src/ftxui/component/event.cpp index d55d46b..607ea7f 100644 --- a/src/ftxui/component/event.cpp +++ b/src/ftxui/component/event.cpp @@ -107,6 +107,28 @@ Event Event::MouseMiddleDown(std::string input, int x, int y) { return event; } +bool Event::is_mouse() const { + switch (type_) { + case Type::Unknown: + case Type::Character: + return false; + case Type::MouseMove: + case Type::MouseUp: + case Type::MouseLeftDown: + case Type::MouseLeftMove: + case Type::MouseMiddleDown: + case Type::MouseMiddleMove: + case Type::MouseRightDown: + case Type::MouseRightMove: + return true; + }; +} + +void Event::MoveMouse(int dx, int dy) { + mouse_.x += dx; + mouse_.y += dy; +} + // --- Arrow --- const Event Event::ArrowLeft = Event::Special("\x1B[D"); const Event Event::ArrowRight = Event::Special("\x1B[C"); diff --git a/src/ftxui/component/menu.cpp b/src/ftxui/component/menu.cpp index 12b5ee3..a511493 100644 --- a/src/ftxui/component/menu.cpp +++ b/src/ftxui/component/menu.cpp @@ -8,18 +8,23 @@ namespace ftxui { Element Menu::Render() { std::vector elements; bool is_focused = Focused(); + boxes_.resize(entries.size()); for (size_t i = 0; i < entries.size(); ++i) { auto style = (selected != int(i)) ? normal_style : is_focused ? focused_style : selected_style; auto focused = (selected != int(i)) ? nothing : is_focused ? focus : select; auto icon = (selected != int(i)) ? L" " : L"> "; - elements.push_back(text(icon + entries[i]) | style | focused); + elements.push_back(text(icon + entries[i]) | style | focused | + reflect(boxes_[i])); } return vbox(std::move(elements)); } bool Menu::OnEvent(Event event) { + if (event.is_mouse()) + return OnMouseEvent(event); + if (!Focused()) return false; @@ -48,6 +53,23 @@ bool Menu::OnEvent(Event event) { return false; } +bool Menu::OnMouseEvent(Event event) { + for (int i = 0; i < boxes_.size(); ++i) { + if (!boxes_[i].Contain(event.mouse_x(), event.mouse_y())) + continue; + + if (event.is_mouse_left_down()) { + if (selected != i) { + selected = i; + TakeFocus(); + on_change(); + } + return true; + } + } + return false; +} + } // namespace ftxui // Copyright 2020 Arthur Sonzogni. All rights reserved. diff --git a/src/ftxui/component/radiobox.cpp b/src/ftxui/component/radiobox.cpp index ff7d169..7147c86 100644 --- a/src/ftxui/component/radiobox.cpp +++ b/src/ftxui/component/radiobox.cpp @@ -8,6 +8,7 @@ namespace ftxui { Element RadioBox::Render() { std::vector elements; bool is_focused = Focused(); + boxes_.resize(entries.size()); for (size_t i = 0; i < entries.size(); ++i) { auto style = (focused == int(i) && is_focused) ? focused_style : unfocused_style; @@ -16,12 +17,15 @@ Element RadioBox::Render() { const std::wstring& symbol = selected == int(i) ? checked : unchecked; elements.push_back(hbox(text(symbol), text(entries[i]) | style) | - focus_management); + focus_management | reflect(boxes_[i])); } return vbox(std::move(elements)); } bool RadioBox::OnEvent(Event event) { + if (event.is_mouse()) + return OnMouseEvent(event); + if (!Focused()) return false; @@ -50,6 +54,30 @@ bool RadioBox::OnEvent(Event event) { return false; } +bool RadioBox::OnMouseEvent(Event event) { + for (int i = 0; i < boxes_.size(); ++i) { + if (!boxes_[i].Contain(event.mouse_x(), event.mouse_y())) + continue; + + if (event.is_mouse_move()) { + focused = i; + TakeFocus(); + return true; + } + + if (event.is_mouse_left_down()) { + cursor_position = i; + TakeFocus(); + if (selected != i) { + selected = i; + on_change(); + } + return true; + } + } + return false; +} + } // namespace ftxui // Copyright 2020 Arthur Sonzogni. All rights reserved. diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index 36c4c54..6b3365c 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -310,8 +310,11 @@ void ScreenInteractive::Loop(Component* component) { Clear(); } Event event; - if (event_receiver_->Receive(&event)) + if (event_receiver_->Receive(&event)) { + if (event.is_mouse()) + event.MoveMouse(-1, -1); component->OnEvent(event); + } } event_listener.join(); diff --git a/src/ftxui/component/toggle.cpp b/src/ftxui/component/toggle.cpp index c7be19e..0437d00 100644 --- a/src/ftxui/component/toggle.cpp +++ b/src/ftxui/component/toggle.cpp @@ -7,6 +7,8 @@ namespace ftxui { Element Toggle::Render() { bool is_focused = Focused(); + boxes_.resize(entries.size()); + Elements children; for (size_t i = 0; i < entries.size(); ++i) { // Separator. @@ -14,16 +16,19 @@ Element Toggle::Render() { children.push_back(separator()); // Entry. - auto style = (selected != int(i)) - ? normal_style - : is_focused ? focused_style : selected_style; + auto style = (selected != int(i)) ? normal_style + : is_focused ? focused_style + : selected_style; auto focused = (selected != int(i)) ? nothing : is_focused ? focus : select; - children.push_back(text(entries[i]) | style | focused); + children.push_back(text(entries[i]) | style | focused | reflect(boxes_[i])); } return hbox(std::move(children)); } bool Toggle::OnEvent(Event event) { + if (event.is_mouse()) + return OnMouseEvent(event); + int old_selected = selected; if (event == Event::ArrowLeft || event == Event::Character('h')) selected--; @@ -49,6 +54,23 @@ bool Toggle::OnEvent(Event event) { return false; } +bool Toggle::OnMouseEvent(Event event) { + for (int i = 0; i < boxes_.size(); ++i) { + if (!boxes_[i].Contain(event.mouse_x(), event.mouse_y())) + continue; + + if (event.is_mouse_left_down()) { + if (selected != i) { + selected = i; + TakeFocus(); + on_change(); + } + return true; + } + } + return false; +} + } // namespace ftxui // Copyright 2020 Arthur Sonzogni. All rights reserved. diff --git a/src/ftxui/dom/reflect.cpp b/src/ftxui/dom/reflect.cpp new file mode 100644 index 0000000..10e7194 --- /dev/null +++ b/src/ftxui/dom/reflect.cpp @@ -0,0 +1,42 @@ +#include +#include "ftxui/dom/elements.hpp" +#include "ftxui/dom/node.hpp" +#include "ftxui/dom/node_decorator.hpp" + +namespace ftxui { + +Box box; + +// Helper class. +class Reflect : public Node { + public: + Reflect(Element child, Box& box) + : Node(unpack(std::move(child))), box_(box) {} + ~Reflect() override {} + + void ComputeRequirement() final { + Node::ComputeRequirement(); + requirement_ = children[0]->requirement(); + } + + void SetBox(Box box) final { + box_ = box; + Node::SetBox(box_); + children[0]->SetBox(box_); + } + + private: + Box& box_; +}; + +Decorator reflect(Box& box) { + return [&](Element child) -> Element { + return std::make_shared(std::move(child), box); + }; +} + +} // 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. diff --git a/src/ftxui/screen/box.cpp b/src/ftxui/screen/box.cpp index b8ed1ee..07826f8 100644 --- a/src/ftxui/screen/box.cpp +++ b/src/ftxui/screen/box.cpp @@ -14,6 +14,16 @@ Box Box::Intersection(Box a, Box b) { std::min(a.y_max, b.y_max), }; } + +/// @return whether (x,y) is contained inside the box. +/// @ingroup screen +bool Box::Contain(int x, int y) { + return x_min <= x && // + x_max >= x && // + y_min <= y && // + y_max >= y; +} + } // namespace ftxui // Copyright 2020 Arthur Sonzogni. All rights reserved.