mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2024-11-26 04:31:34 +08:00
Improve mouse support for menu and toggle.
This commit is contained in:
parent
890a41a64c
commit
8037a5fa5f
@ -13,7 +13,7 @@ class MyComponent : public Component {
|
||||
MyComponent() {
|
||||
Add(&container);
|
||||
|
||||
for (Menu* menu : {&menu_1, &menu_2, &menu_3, &menu_4, &menu_5, &menu_6}) {
|
||||
for (Menu* menu : {&menu_1, &menu_2, &menu_3, &menu_4, &menu_5, &menu_6,}) {
|
||||
container.Add(menu);
|
||||
menu->entries = {
|
||||
L"Monkey", L"Dog", L"Cat", L"Bird", L"Elephant",
|
||||
@ -21,22 +21,27 @@ class MyComponent : public Component {
|
||||
menu->on_enter = [this]() { on_enter(); };
|
||||
}
|
||||
|
||||
menu_2.selected_style = color(Color::Blue);
|
||||
menu_2.focused_style = bold | color(Color::Blue);
|
||||
menu_2.selected_style = color(Color::Blue);
|
||||
menu_2.selected_focused_style = bold | color(Color::Blue);
|
||||
|
||||
menu_3.selected_style = color(Color::Blue);
|
||||
menu_3.focused_style = bgcolor(Color::Blue);
|
||||
menu_3.selected_focused_style = bgcolor(Color::Blue);
|
||||
|
||||
menu_4.selected_style = bgcolor(Color::Blue);
|
||||
menu_4.focused_style = bgcolor(Color::BlueLight);
|
||||
menu_4.selected_focused_style = bgcolor(Color::BlueLight);
|
||||
|
||||
menu_5.normal_style = bgcolor(Color::Blue);
|
||||
menu_5.selected_style = bgcolor(Color::Yellow);
|
||||
menu_5.focused_style = bgcolor(Color::Red);
|
||||
menu_5.selected_focused_style = bgcolor(Color::Red);
|
||||
|
||||
menu_6.normal_style = dim | color(Color::Blue);
|
||||
menu_6.selected_style = color(Color::Blue);
|
||||
menu_6.focused_style = bold | color(Color::Blue);
|
||||
menu_6.selected_focused_style = bold | color(Color::Blue);
|
||||
}
|
||||
|
||||
std::function<void()> on_enter = []() {};
|
||||
|
@ -17,7 +17,7 @@ class DrawKey : public Component {
|
||||
|
||||
Element Render() override {
|
||||
Elements children;
|
||||
for (size_t i = std::max(0, (int)keys.size() - 10); i < keys.size(); ++i) {
|
||||
for (size_t i = std::max(0, (int)keys.size() - 20); i < keys.size(); ++i) {
|
||||
std::wstring code;
|
||||
for (auto& it : keys[i].input())
|
||||
code += L" " + std::to_wstring((unsigned int)it);
|
||||
|
@ -36,6 +36,7 @@ struct Event {
|
||||
static Event MouseMiddleDown(std::string, int x, int y);
|
||||
static Event MouseRightMove(std::string, int x, int y);
|
||||
static Event MouseRightDown(std::string, int x, int y);
|
||||
static Event CursorReporting(std::string, int x, int y);
|
||||
|
||||
// --- Arrow ---
|
||||
static const Event ArrowLeft;
|
||||
@ -68,6 +69,7 @@ struct Event {
|
||||
bool is_mouse_right_move() const { return type_ == Type::MouseRightMove; }
|
||||
bool is_mouse_up() const { return type_ == Type::MouseUp; }
|
||||
bool is_mouse_move() const { return type_ == Type::MouseMove; }
|
||||
bool is_cursor_reporting() const { return type_ == Type::CursorReporting; }
|
||||
int mouse_x() const { return mouse_.x; }
|
||||
int mouse_y() const { return mouse_.y; }
|
||||
|
||||
@ -90,6 +92,7 @@ struct Event {
|
||||
MouseMiddleMove,
|
||||
MouseRightDown,
|
||||
MouseRightMove,
|
||||
CursorReporting,
|
||||
};
|
||||
|
||||
struct Mouse {
|
||||
|
@ -19,10 +19,12 @@ class Menu : public Component {
|
||||
// State.
|
||||
std::vector<std::wstring> entries = {};
|
||||
int selected = 0;
|
||||
int focused = 0;
|
||||
|
||||
Decorator normal_style = nothing;
|
||||
Decorator focused_style = inverted;
|
||||
Decorator selected_style = bold;
|
||||
Decorator normal_style = nothing;
|
||||
Decorator selected_focused_style = focused_style | selected_style;
|
||||
|
||||
// State update callback.
|
||||
std::function<void()> on_change = []() {};
|
||||
|
@ -52,6 +52,9 @@ class ScreenInteractive : public Screen {
|
||||
std::string reset_cursor_position;
|
||||
|
||||
std::atomic<bool> quit_ = false;
|
||||
|
||||
int cursor_x_ = 0;
|
||||
int cursor_y_ = 0;
|
||||
};
|
||||
|
||||
} // namespace ftxui
|
||||
|
@ -16,12 +16,14 @@ class Toggle : public Component {
|
||||
~Toggle() override = default;
|
||||
|
||||
// State.
|
||||
int selected = 0;
|
||||
std::vector<std::wstring> entries = {L"On", L"Off"};
|
||||
int selected = 0;
|
||||
int focused = 0;
|
||||
|
||||
Decorator normal_style = dim;
|
||||
Decorator focused_style = inverted;
|
||||
Decorator selected_style = bold;
|
||||
Decorator normal_style = dim;
|
||||
Decorator selected_focused_style = focused_style | selected_style;
|
||||
|
||||
// Callback.
|
||||
std::function<void()> on_change = []() {};
|
||||
|
@ -5,10 +5,8 @@
|
||||
namespace ftxui {
|
||||
|
||||
Element Button::Render() {
|
||||
return text(label) | //
|
||||
border | //
|
||||
(Focused() ? inverted : nothing) | //
|
||||
reflect(box_);
|
||||
auto style = Focused() ? inverted : nothing;
|
||||
return text(label) | border | style | reflect(box_);
|
||||
}
|
||||
|
||||
bool Button::OnEvent(Event event) {
|
||||
|
@ -107,10 +107,19 @@ Event Event::MouseMiddleDown(std::string input, int x, int y) {
|
||||
return event;
|
||||
}
|
||||
|
||||
Event Event::CursorReporting(std::string input, int x, int y) {
|
||||
Event event;
|
||||
event.input_ = std::move(input);
|
||||
event.type_ = Type::CursorReporting;
|
||||
event.mouse_ = {x, y};
|
||||
return event;
|
||||
}
|
||||
|
||||
bool Event::is_mouse() const {
|
||||
switch (type_) {
|
||||
case Type::Unknown:
|
||||
case Type::Character:
|
||||
case Type::CursorReporting:
|
||||
return false;
|
||||
case Type::MouseMove:
|
||||
case Type::MouseUp:
|
||||
|
@ -6,16 +6,21 @@
|
||||
namespace ftxui {
|
||||
|
||||
Element Menu::Render() {
|
||||
std::vector<Element> elements;
|
||||
bool is_focused = Focused();
|
||||
Elements elements;
|
||||
bool is_menu_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 |
|
||||
bool is_focused = (focused == int(i)) && is_menu_focused;
|
||||
bool is_selected = (selected == int(i));
|
||||
|
||||
auto style = is_selected
|
||||
? (is_focused ? selected_focused_style : selected_style)
|
||||
: (is_focused ? focused_style : normal_style);
|
||||
auto focus_management = !is_selected ? nothing
|
||||
: is_menu_focused ? focus
|
||||
: select;
|
||||
auto icon = is_selected ? L"> " : L" ";
|
||||
elements.push_back(text(icon + entries[i]) | style | focus_management |
|
||||
reflect(boxes_[i]));
|
||||
}
|
||||
return vbox(std::move(elements));
|
||||
@ -41,6 +46,7 @@ bool Menu::OnEvent(Event event) {
|
||||
selected = std::max(0, std::min(int(entries.size()) - 1, selected));
|
||||
|
||||
if (selected != old_selected) {
|
||||
focused = selected;
|
||||
on_change();
|
||||
return true;
|
||||
}
|
||||
@ -58,10 +64,11 @@ bool Menu::OnMouseEvent(Event event) {
|
||||
if (!boxes_[i].Contain(event.mouse_x(), event.mouse_y()))
|
||||
continue;
|
||||
|
||||
focused = i;
|
||||
if (event.is_mouse_left_down()) {
|
||||
TakeFocus();
|
||||
if (selected != i) {
|
||||
selected = i;
|
||||
TakeFocus();
|
||||
on_change();
|
||||
}
|
||||
return true;
|
||||
|
@ -156,7 +156,9 @@ static const char USE_ALTERNATIVE_SCREEN[] = "\x1B[?1049h";
|
||||
static const char USE_NORMAL_SCREEN[] = "\x1B[?1049l";
|
||||
|
||||
static const char ENABLE_MOUSE[] = "\x1B[?1000;1003;1006;1015h";
|
||||
static const char DISABLE_MOUSE[] = "\x1B[?1000;1003;10006;1015l";
|
||||
static const char DISABLE_MOUSE[] = "\x1B[?1000;1003;1006;1015l";
|
||||
|
||||
static const char REQUEST_CURSOR_LINE[] = "\x1b[6n";
|
||||
|
||||
using SignalHandler = void(int);
|
||||
std::stack<std::function<void()>> on_exit_functions;
|
||||
@ -304,17 +306,30 @@ void ScreenInteractive::Loop(Component* component) {
|
||||
while (!quit_) {
|
||||
if (!event_receiver_->HasPending()) {
|
||||
std::cout << reset_cursor_position << ResetPosition();
|
||||
static int i = -2;
|
||||
if (i % 30 == 0)
|
||||
std::cout << REQUEST_CURSOR_LINE;
|
||||
++i;
|
||||
Draw(component);
|
||||
std::cout << ToString() << set_cursor_position;
|
||||
Flush();
|
||||
Clear();
|
||||
}
|
||||
|
||||
Event event;
|
||||
if (event_receiver_->Receive(&event)) {
|
||||
if (event.is_mouse())
|
||||
event.MoveMouse(-1, -1);
|
||||
component->OnEvent(event);
|
||||
if (!event_receiver_->Receive(&event))
|
||||
break;
|
||||
|
||||
if (event.is_cursor_reporting()) {
|
||||
cursor_x_ = event.mouse_y();
|
||||
cursor_y_ = event.mouse_x();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (event.is_mouse())
|
||||
event.MoveMouse(-cursor_x_, -cursor_y_);
|
||||
|
||||
component->OnEvent(event);
|
||||
}
|
||||
|
||||
event_listener.join();
|
||||
|
@ -85,6 +85,11 @@ void TerminalInputParser::Send(TerminalInputParser::Output output) {
|
||||
out_->Send(Event::MouseRightMove(std::move(pending_), output.mouse.x,
|
||||
output.mouse.y));
|
||||
break;
|
||||
|
||||
case CURSOR_REPORTING:
|
||||
out_->Send(Event::CursorReporting(std::move(pending_), output.mouse.x,
|
||||
output.mouse.y));
|
||||
break;
|
||||
}
|
||||
pending_.clear();
|
||||
}
|
||||
@ -179,12 +184,14 @@ TerminalInputParser::Output TerminalInputParser::ParseCSI() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Current() >= ' ' && Current() <= '~') {
|
||||
if (Current() >= ' ' && Current() <= '~' && Current() != '<') {
|
||||
arguments.push_back(argument);
|
||||
argument = 0;
|
||||
switch (Current()) {
|
||||
case 'M':
|
||||
return ParseMouse(std::move(arguments));
|
||||
case 'R':
|
||||
return ParseCursorReporting(std::move(arguments));
|
||||
default:
|
||||
return SPECIAL;
|
||||
}
|
||||
@ -235,8 +242,18 @@ TerminalInputParser::Output TerminalInputParser::ParseMouse(
|
||||
return Output(MOUSE_UP, arguments[1], arguments[2]);
|
||||
case 67:
|
||||
return Output(MOUSE_MOVE, arguments[1], arguments[2]);
|
||||
|
||||
default:
|
||||
return Output(MOUSE_MOVE, arguments[1], arguments[2]);
|
||||
}
|
||||
return SPECIAL;
|
||||
}
|
||||
|
||||
TerminalInputParser::Output TerminalInputParser::ParseCursorReporting(
|
||||
std::vector<int> arguments) {
|
||||
if (arguments.size() != 2)
|
||||
return SPECIAL;
|
||||
return Output(CURSOR_REPORTING, arguments[0], arguments[1]);
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
@ -32,6 +32,7 @@ class TerminalInputParser {
|
||||
MOUSE_MIDDLE_MOVE,
|
||||
MOUSE_RIGHT_DOWN,
|
||||
MOUSE_RIGHT_MOVE,
|
||||
CURSOR_REPORTING,
|
||||
};
|
||||
|
||||
struct Mouse {
|
||||
@ -58,6 +59,7 @@ class TerminalInputParser {
|
||||
Output ParseCSI();
|
||||
Output ParseOSC();
|
||||
Output ParseMouse(std::vector<int> arguments);
|
||||
Output ParseCursorReporting(std::vector<int> arguments);
|
||||
|
||||
Sender<Event> out_;
|
||||
int position_ = -1;
|
||||
|
@ -66,6 +66,25 @@ TEST(Event, EscapeKeyEnoughWait) {
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
|
||||
TEST(Event, GnomeTerminalMouse) {
|
||||
auto event_receiver = MakeReceiver<Event>();
|
||||
{
|
||||
auto parser = TerminalInputParser(event_receiver->MakeSender());
|
||||
parser.Add('\x1B');
|
||||
parser.Add('[');
|
||||
parser.Add('<');
|
||||
parser.Add('1');
|
||||
parser.Add(';');
|
||||
parser.Add('1');
|
||||
parser.Add('M');
|
||||
}
|
||||
|
||||
Event received;
|
||||
EXPECT_TRUE(event_receiver->Receive(&received));
|
||||
EXPECT_TRUE(received.is_mouse());
|
||||
EXPECT_FALSE(event_receiver->Receive(&received));
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -5,22 +5,25 @@
|
||||
namespace ftxui {
|
||||
|
||||
Element Toggle::Render() {
|
||||
bool is_focused = Focused();
|
||||
|
||||
boxes_.resize(entries.size());
|
||||
|
||||
Elements children;
|
||||
bool is_toggle_focused = Focused();
|
||||
boxes_.resize(entries.size());
|
||||
for (size_t i = 0; i < entries.size(); ++i) {
|
||||
// Separator.
|
||||
if (i != 0)
|
||||
children.push_back(separator());
|
||||
|
||||
// Entry.
|
||||
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 | reflect(boxes_[i]));
|
||||
bool is_focused = (focused == int(i)) && is_toggle_focused;
|
||||
bool is_selected = (selected == int(i));
|
||||
|
||||
auto style = is_selected
|
||||
? (is_focused ? selected_focused_style : selected_style)
|
||||
: (is_focused ? focused_style : normal_style);
|
||||
auto focus_management = !is_selected ? nothing
|
||||
: is_toggle_focused ? focus
|
||||
: select;
|
||||
children.push_back(text(entries[i]) | style | focus_management |
|
||||
reflect(boxes_[i]));
|
||||
}
|
||||
return hbox(std::move(children));
|
||||
}
|
||||
@ -42,6 +45,7 @@ bool Toggle::OnEvent(Event event) {
|
||||
selected = std::max(0, std::min(int(entries.size()) - 1, selected));
|
||||
|
||||
if (old_selected != selected) {
|
||||
focused = selected;
|
||||
on_change();
|
||||
return true;
|
||||
}
|
||||
@ -59,10 +63,12 @@ bool Toggle::OnMouseEvent(Event event) {
|
||||
if (!boxes_[i].Contain(event.mouse_x(), event.mouse_y()))
|
||||
continue;
|
||||
|
||||
TakeFocus();
|
||||
focused = i;
|
||||
if (event.is_mouse_left_down()) {
|
||||
TakeFocus();
|
||||
if (selected != i) {
|
||||
selected = i;
|
||||
TakeFocus();
|
||||
on_change();
|
||||
}
|
||||
return true;
|
||||
|
Loading…
Reference in New Issue
Block a user