Add mouse implementation of most components.

This commit is contained in:
ArthurSonzogni 2021-04-18 22:33:41 +02:00
parent d685a8655e
commit 890a41a64c
No known key found for this signature in database
GPG Key ID: 41D98248C074CD6C
20 changed files with 239 additions and 12 deletions

View File

@ -65,6 +65,7 @@ add_library(dom
src/ftxui/dom/node.cpp src/ftxui/dom/node.cpp
src/ftxui/dom/node_decorator.cpp src/ftxui/dom/node_decorator.cpp
src/ftxui/dom/paragraph.cpp src/ftxui/dom/paragraph.cpp
src/ftxui/dom/reflect.cpp
src/ftxui/dom/separator.cpp src/ftxui/dom/separator.cpp
src/ftxui/dom/size.cpp src/ftxui/dom/size.cpp
src/ftxui/dom/spinner.cpp src/ftxui/dom/spinner.cpp

View File

@ -25,6 +25,8 @@ class Button : public Component {
// Component implementation. // Component implementation.
Element Render() override; Element Render() override;
bool OnEvent(Event) override; bool OnEvent(Event) override;
private:
Box box_;
}; };
} // namespace ftxui } // namespace ftxui

View File

@ -38,7 +38,11 @@ class CheckBox : public Component {
bool OnEvent(Event) override; bool OnEvent(Event) override;
private: private:
bool OnMouseEvent(Event event);
int cursor_position = 0; int cursor_position = 0;
Box box_;
}; };
} // namespace ftxui } // namespace ftxui

View File

@ -36,6 +36,9 @@ class Container : public Component {
int selected_ = 0; int selected_ = 0;
int* selector_ = nullptr; int* selector_ = nullptr;
private:
bool OnMouseEvent(Event event);
}; };
} // namespace ftxui } // namespace ftxui

View File

@ -59,6 +59,7 @@ struct Event {
bool is_character() const { return type_ == Type::Character;} bool is_character() const { return type_ == Type::Character;}
wchar_t character() const { return 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_down() const { return type_ == Type::MouseLeftDown; }
bool is_mouse_left_move() const { return type_ == Type::MouseLeftMove; } bool is_mouse_left_move() const { return type_ == Type::MouseLeftMove; }
bool is_mouse_middle_down() const { return type_ == Type::MouseMiddleDown; } 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_; } bool operator==(const Event& other) const { return input_ == other.input_; }
void MoveMouse(int dx, int dy);
//--- State section ---------------------------------------------------------- //--- State section ----------------------------------------------------------
private: private:
enum class Type { enum class Type {

View File

@ -31,6 +31,11 @@ class Menu : public Component {
// Component implementation. // Component implementation.
Element Render() override; Element Render() override;
bool OnEvent(Event) override; bool OnEvent(Event) override;
private:
bool OnMouseEvent(Event);
std::vector<Box> boxes_;
}; };
} // namespace ftxui } // namespace ftxui

View File

@ -39,7 +39,9 @@ class RadioBox : public Component {
bool OnEvent(Event) override; bool OnEvent(Event) override;
private: private:
bool OnMouseEvent(Event event);
int cursor_position = 0; int cursor_position = 0;
std::vector<Box> boxes_;
}; };
} // namespace ftxui } // namespace ftxui

View File

@ -30,6 +30,10 @@ class Toggle : public Component {
// Component implementation. // Component implementation.
Element Render() override; Element Render() override;
bool OnEvent(Event) override; bool OnEvent(Event) override;
private:
bool OnMouseEvent(Event event);
std::vector<Box> boxes_;
}; };
} // namespace ftxui } // namespace ftxui

View File

@ -5,6 +5,7 @@
#include <memory> #include <memory>
#include "ftxui/dom/node.hpp" #include "ftxui/dom/node.hpp"
#include "ftxui/screen/box.hpp"
#include "ftxui/screen/color.hpp" #include "ftxui/screen/color.hpp"
#include "ftxui/screen/screen.hpp" #include "ftxui/screen/screen.hpp"
@ -77,6 +78,9 @@ enum Direction { WIDTH, HEIGHT };
enum Constraint { LESS_THAN, EQUAL, GREATER_THAN }; enum Constraint { LESS_THAN, EQUAL, GREATER_THAN };
Decorator size(Direction, Constraint, int value); Decorator size(Direction, Constraint, int value);
// --
Decorator reflect(Box& box);
// --- Frame --- // --- Frame ---
// A frame is a scrollable area. The internal area is potentially larger than // 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 // the external one. The internal area is scrolled in order to make visible the

View File

@ -10,6 +10,7 @@ struct Box {
int y_max; int y_max;
static Box Intersection(Box a, Box b); static Box Intersection(Box a, Box b);
bool Contain(int x, int y);
}; };
} // namespace ftxui } // namespace ftxui

View File

@ -5,13 +5,26 @@
namespace ftxui { namespace ftxui {
Element Button::Render() { Element Button::Render() {
if (Focused()) return text(label) | //
return text(label) | border | inverted; border | //
else (Focused() ? inverted : nothing) | //
return text(label) | border; reflect(box_);
} }
bool Button::OnEvent(Event event) { 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) { if (event == Event::Return) {
on_click(); on_click();
return true; return true;

View File

@ -9,10 +9,14 @@ Element CheckBox::Render() {
auto style = is_focused ? focused_style : unfocused_style; auto style = is_focused ? focused_style : unfocused_style;
auto focus_management = is_focused ? focus : state ? select : nothing; auto focus_management = is_focused ? focus : state ? select : nothing;
return hbox(text(state ? checked : unchecked), return hbox(text(state ? checked : unchecked),
text(label) | style | focus_management); text(label) | style | focus_management) |
reflect(box_);
} }
bool CheckBox::OnEvent(Event event) { bool CheckBox::OnEvent(Event event) {
if (event.is_mouse())
return OnMouseEvent(event);
if (event == Event::Character(' ') || event == Event::Return) { if (event == Event::Character(' ') || event == Event::Return) {
state = !state; state = !state;
on_change(); on_change();
@ -21,6 +25,24 @@ bool CheckBox::OnEvent(Event event) {
return false; 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 } // namespace ftxui
// Copyright 2020 Arthur Sonzogni. All rights reserved. // Copyright 2020 Arthur Sonzogni. All rights reserved.

View File

@ -30,6 +30,9 @@ Container Container::Tab(int* selector) {
} }
bool Container::OnEvent(Event event) { bool Container::OnEvent(Event event) {
if (event.is_mouse())
return OnMouseEvent(event);
if (!Focused()) if (!Focused())
return false; return false;
@ -115,6 +118,17 @@ Element Container::TabRender() {
return text(L"Empty container"); 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 } // namespace ftxui
// Copyright 2020 Arthur Sonzogni. All rights reserved. // Copyright 2020 Arthur Sonzogni. All rights reserved.

View File

@ -107,6 +107,28 @@ Event Event::MouseMiddleDown(std::string input, int x, int y) {
return event; 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 --- // --- Arrow ---
const Event Event::ArrowLeft = Event::Special("\x1B[D"); const Event Event::ArrowLeft = Event::Special("\x1B[D");
const Event Event::ArrowRight = Event::Special("\x1B[C"); const Event Event::ArrowRight = Event::Special("\x1B[C");

View File

@ -8,18 +8,23 @@ namespace ftxui {
Element Menu::Render() { Element Menu::Render() {
std::vector<Element> elements; std::vector<Element> elements;
bool is_focused = Focused(); bool is_focused = Focused();
boxes_.resize(entries.size());
for (size_t i = 0; i < entries.size(); ++i) { for (size_t i = 0; i < entries.size(); ++i) {
auto style = (selected != int(i)) auto style = (selected != int(i))
? normal_style ? normal_style
: is_focused ? focused_style : selected_style; : is_focused ? focused_style : selected_style;
auto focused = (selected != int(i)) ? nothing : is_focused ? focus : select; auto focused = (selected != int(i)) ? nothing : is_focused ? focus : select;
auto icon = (selected != int(i)) ? L" " : L"> "; 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)); return vbox(std::move(elements));
} }
bool Menu::OnEvent(Event event) { bool Menu::OnEvent(Event event) {
if (event.is_mouse())
return OnMouseEvent(event);
if (!Focused()) if (!Focused())
return false; return false;
@ -48,6 +53,23 @@ bool Menu::OnEvent(Event event) {
return false; 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 } // namespace ftxui
// Copyright 2020 Arthur Sonzogni. All rights reserved. // Copyright 2020 Arthur Sonzogni. All rights reserved.

View File

@ -8,6 +8,7 @@ namespace ftxui {
Element RadioBox::Render() { Element RadioBox::Render() {
std::vector<Element> elements; std::vector<Element> elements;
bool is_focused = Focused(); bool is_focused = Focused();
boxes_.resize(entries.size());
for (size_t i = 0; i < entries.size(); ++i) { for (size_t i = 0; i < entries.size(); ++i) {
auto style = auto style =
(focused == int(i) && is_focused) ? focused_style : unfocused_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; const std::wstring& symbol = selected == int(i) ? checked : unchecked;
elements.push_back(hbox(text(symbol), text(entries[i]) | style) | elements.push_back(hbox(text(symbol), text(entries[i]) | style) |
focus_management); focus_management | reflect(boxes_[i]));
} }
return vbox(std::move(elements)); return vbox(std::move(elements));
} }
bool RadioBox::OnEvent(Event event) { bool RadioBox::OnEvent(Event event) {
if (event.is_mouse())
return OnMouseEvent(event);
if (!Focused()) if (!Focused())
return false; return false;
@ -50,6 +54,30 @@ bool RadioBox::OnEvent(Event event) {
return false; 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 } // namespace ftxui
// Copyright 2020 Arthur Sonzogni. All rights reserved. // Copyright 2020 Arthur Sonzogni. All rights reserved.

View File

@ -310,9 +310,12 @@ void ScreenInteractive::Loop(Component* component) {
Clear(); Clear();
} }
Event event; Event event;
if (event_receiver_->Receive(&event)) if (event_receiver_->Receive(&event)) {
if (event.is_mouse())
event.MoveMouse(-1, -1);
component->OnEvent(event); component->OnEvent(event);
} }
}
event_listener.join(); event_listener.join();
OnExit(0); OnExit(0);

View File

@ -7,6 +7,8 @@ namespace ftxui {
Element Toggle::Render() { Element Toggle::Render() {
bool is_focused = Focused(); bool is_focused = Focused();
boxes_.resize(entries.size());
Elements children; Elements children;
for (size_t i = 0; i < entries.size(); ++i) { for (size_t i = 0; i < entries.size(); ++i) {
// Separator. // Separator.
@ -14,16 +16,19 @@ Element Toggle::Render() {
children.push_back(separator()); children.push_back(separator());
// Entry. // Entry.
auto style = (selected != int(i)) auto style = (selected != int(i)) ? normal_style
? normal_style : is_focused ? focused_style
: is_focused ? focused_style : selected_style; : selected_style;
auto focused = (selected != int(i)) ? nothing : is_focused ? focus : select; 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)); return hbox(std::move(children));
} }
bool Toggle::OnEvent(Event event) { bool Toggle::OnEvent(Event event) {
if (event.is_mouse())
return OnMouseEvent(event);
int old_selected = selected; int old_selected = selected;
if (event == Event::ArrowLeft || event == Event::Character('h')) if (event == Event::ArrowLeft || event == Event::Character('h'))
selected--; selected--;
@ -49,6 +54,23 @@ bool Toggle::OnEvent(Event event) {
return false; 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 } // namespace ftxui
// Copyright 2020 Arthur Sonzogni. All rights reserved. // Copyright 2020 Arthur Sonzogni. All rights reserved.

42
src/ftxui/dom/reflect.cpp Normal file
View File

@ -0,0 +1,42 @@
#include <memory>
#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<Reflect>(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.

View File

@ -14,6 +14,16 @@ Box Box::Intersection(Box a, Box b) {
std::min(a.y_max, b.y_max), 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 } // namespace ftxui
// Copyright 2020 Arthur Sonzogni. All rights reserved. // Copyright 2020 Arthur Sonzogni. All rights reserved.