mirror of
https://github.com/ArthurSonzogni/FTXUI.git
synced 2024-11-25 20:27:31 +08:00
Add the Hoverable wrapper. (#522)
This will make it easier for developers. For instance: https://github.com/ArthurSonzogni/FTXUI/issues/521
This commit is contained in:
parent
f21ca3aa14
commit
0d54285e19
@ -37,6 +37,7 @@ current (development)
|
||||
mouse.
|
||||
- Bugfix: Add implementation of `ButtonOption::Border()`. It was missing.
|
||||
- Bugfix: Provide the correct key for F1-F4 and F11.
|
||||
- Feature: Add the `Hoverable` component decorators.
|
||||
|
||||
### Screen
|
||||
- Feature: add `Box::Union(a,b) -> Box`
|
||||
|
@ -108,6 +108,7 @@ add_library(component
|
||||
src/ftxui/component/container.cpp
|
||||
src/ftxui/component/dropdown.cpp
|
||||
src/ftxui/component/event.cpp
|
||||
src/ftxui/component/hoverable.cpp
|
||||
src/ftxui/component/input.cpp
|
||||
src/ftxui/component/loop.cpp
|
||||
src/ftxui/component/maybe.cpp
|
||||
|
@ -29,14 +29,15 @@ add_executable(tests
|
||||
src/ftxui/component/component_test.cpp
|
||||
src/ftxui/component/component_test.cpp
|
||||
src/ftxui/component/container_test.cpp
|
||||
src/ftxui/component/hoverable_test.cpp
|
||||
src/ftxui/component/input_test.cpp
|
||||
src/ftxui/component/menu_test.cpp
|
||||
src/ftxui/component/modal_test.cpp
|
||||
src/ftxui/component/radiobox_test.cpp
|
||||
src/ftxui/component/receiver_test.cpp
|
||||
src/ftxui/component/slider_test.cpp
|
||||
src/ftxui/component/resizable_split_test.cpp
|
||||
src/ftxui/component/screen_interactive_test.cpp
|
||||
src/ftxui/component/slider_test.cpp
|
||||
src/ftxui/component/terminal_input_parser_test.cpp
|
||||
src/ftxui/component/toggle_test.cpp
|
||||
src/ftxui/dom/blink_test.cpp
|
||||
|
@ -110,6 +110,18 @@ ComponentDecorator Modal(Component modal, const bool* show_modal);
|
||||
Component Collapsible(ConstStringRef label,
|
||||
Component child,
|
||||
Ref<bool> show = false);
|
||||
|
||||
Component Hoverable(Component component, bool* hover);
|
||||
Component Hoverable(Component component,
|
||||
std::function<void()> on_enter,
|
||||
std::function<void()> on_leave);
|
||||
Component Hoverable(Component component, //
|
||||
std::function<void(bool)> on_change);
|
||||
ComponentDecorator Hoverable(bool* hover);
|
||||
ComponentDecorator Hoverable(std::function<void()> on_enter,
|
||||
std::function<void()> on_leave);
|
||||
ComponentDecorator Hoverable(std::function<void(bool)> on_change);
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
#endif /* end of include guard: FTXUI_COMPONENT_HPP */
|
||||
|
171
src/ftxui/component/hoverable.cpp
Normal file
171
src/ftxui/component/hoverable.cpp
Normal file
@ -0,0 +1,171 @@
|
||||
#include <memory> // for shared_ptr
|
||||
#include <utility> // for move
|
||||
|
||||
#include "ftxui/component/component.hpp" // for Make, Button
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::Return
|
||||
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Left, Mouse::Pressed
|
||||
#include "ftxui/component/screen_interactive.hpp" // for Component
|
||||
#include "ftxui/dom/elements.hpp" // for operator|, Decorator, Element, operator|=, bgcolor, color, reflect, text, bold, border, inverted, nothing
|
||||
#include "ftxui/screen/box.hpp" // for Box
|
||||
#include "ftxui/screen/color.hpp" // for Color
|
||||
#include "ftxui/util/ref.hpp" // for Ref, ConstStringRef
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
namespace {
|
||||
|
||||
void Post(std::function<void()> f) {
|
||||
if (auto* screen = ScreenInteractive::Active()) {
|
||||
screen->Post(std::move(f));
|
||||
return;
|
||||
}
|
||||
f();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/// @brief Wrap a component. Gives the ability to know if it is hovered by the
|
||||
/// mouse.
|
||||
/// @param component: The wrapped component.
|
||||
/// @param hover: The value to reflect whether the component is hovered or not.
|
||||
/// @ingroup component
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// auto button = Button("exit", screen.ExitLoopClosure());
|
||||
/// bool hover = false;
|
||||
/// auto button_hover = Hoverable(button, &hover);
|
||||
/// ```
|
||||
Component Hoverable(Component component, bool* hover) {
|
||||
class Impl : public ComponentBase {
|
||||
public:
|
||||
Impl(Component component, bool* hover)
|
||||
: component_(component), hover_(hover) {
|
||||
Add(component_);
|
||||
}
|
||||
|
||||
private:
|
||||
Element Render() override {
|
||||
return ComponentBase::Render() | reflect(box_);
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) override {
|
||||
if (event.is_mouse()) {
|
||||
*hover_ = box_.Contain(event.mouse().x, event.mouse().y) &&
|
||||
CaptureMouse(event);
|
||||
}
|
||||
|
||||
return ComponentBase::OnEvent(event);
|
||||
}
|
||||
|
||||
Component component_;
|
||||
bool* hover_;
|
||||
Box box_;
|
||||
};
|
||||
|
||||
return Make<Impl>(component, hover);
|
||||
}
|
||||
|
||||
/// @brief Wrap a component. Gives the ability to know if it is hovered by the
|
||||
/// mouse.
|
||||
/// @param component: The wrapped component.
|
||||
/// @param hover: The value to reflect whether the component is hovered or not.
|
||||
/// @ingroup component
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// auto button = Button("exit", screen.ExitLoopClosure());
|
||||
/// bool hover = false;
|
||||
/// auto button_hover = Hoverable(button, &hover);
|
||||
/// ```
|
||||
Component Hoverable(Component component,
|
||||
std::function<void()> on_enter,
|
||||
std::function<void()> on_leave) {
|
||||
class Impl : public ComponentBase {
|
||||
public:
|
||||
Impl(Component component,
|
||||
std::function<void()> on_enter,
|
||||
std::function<void()> on_leave)
|
||||
: component_(std::move(component)),
|
||||
on_enter_(std::move(on_enter)),
|
||||
on_leave_(std::move(on_leave)) {
|
||||
Add(component_);
|
||||
}
|
||||
|
||||
private:
|
||||
Element Render() override {
|
||||
return ComponentBase::Render() | reflect(box_);
|
||||
}
|
||||
|
||||
bool OnEvent(Event event) override {
|
||||
if (event.is_mouse()) {
|
||||
bool hover = box_.Contain(event.mouse().x, event.mouse().y) &&
|
||||
CaptureMouse(event);
|
||||
if (hover != hover_) {
|
||||
Post(hover ? on_enter_ : on_leave_);
|
||||
}
|
||||
hover_ = hover;
|
||||
}
|
||||
|
||||
return ComponentBase::OnEvent(event);
|
||||
}
|
||||
|
||||
Component component_;
|
||||
Box box_;
|
||||
bool hover_ = false;
|
||||
std::function<void()> on_enter_;
|
||||
std::function<void()> on_leave_;
|
||||
};
|
||||
|
||||
return Make<Impl>(component, on_enter, on_leave);
|
||||
}
|
||||
|
||||
/// @brief Wrap a component. Gives the ability to know if it is hovered by the
|
||||
/// mouse.
|
||||
/// @param hover: The value to reflect whether the component is hovered or not.
|
||||
/// @ingroup component
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```cpp
|
||||
/// bool hover = false;
|
||||
/// auto button = Button("exit", screen.ExitLoopClosure());
|
||||
/// button |= Hoverable(&hover);
|
||||
/// ```
|
||||
ComponentDecorator Hoverable(bool* hover) {
|
||||
return [hover](Component component) { return Hoverable(component, hover); };
|
||||
}
|
||||
|
||||
/// @brief Wrap a component. Two callback can be used to know when the mouse
|
||||
/// enter and leave its area.
|
||||
ComponentDecorator Hoverable(std::function<void()> on_enter,
|
||||
std::function<void()> on_leave) {
|
||||
return [on_enter, on_leave](Component component) {
|
||||
return Hoverable(component, on_enter, on_leave);
|
||||
};
|
||||
}
|
||||
|
||||
Component Hoverable(Component component, std::function<void(bool)> on_change) {
|
||||
return Hoverable(
|
||||
component, //
|
||||
[on_change] { on_change(true); }, //
|
||||
[on_change] { on_change(false); } //
|
||||
);
|
||||
}
|
||||
|
||||
/// @brief Wrap a component. Two callback can be used to know when the mouse
|
||||
/// enter and leave its area.
|
||||
ComponentDecorator Hoverable(std::function<void(bool)> on_change) {
|
||||
return [on_change](Component component) {
|
||||
return Hoverable(component, on_change);
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace ftxui
|
||||
|
||||
// Copyright 2022 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
192
src/ftxui/component/hoverable_test.cpp
Normal file
192
src/ftxui/component/hoverable_test.cpp
Normal file
@ -0,0 +1,192 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory> // for __shared_ptr_access, shared_ptr, allocator
|
||||
#include <string> // for string
|
||||
|
||||
#include "ftxui/component/component.hpp" // for Input
|
||||
#include "ftxui/component/component_base.hpp" // for ComponentBase, Component
|
||||
#include "ftxui/component/event.hpp" // for Event, Event::ArrowLeft, Event::ArrowRight, Event::Backspace, Event::Delete, Event::End, Event::Home
|
||||
#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::Button, Mouse::Left, Mouse::Motion, Mouse::Pressed
|
||||
#include "ftxui/dom/node.hpp" // for Render
|
||||
#include "ftxui/screen/screen.hpp" // for Screen
|
||||
|
||||
namespace ftxui {
|
||||
|
||||
namespace {
|
||||
Event HoverEvent(int x, int y) {
|
||||
Mouse mouse;
|
||||
mouse.button = Mouse::Left;
|
||||
mouse.motion = Mouse::Released;
|
||||
mouse.shift = false;
|
||||
mouse.meta = false;
|
||||
mouse.control = false;
|
||||
mouse.x = x;
|
||||
mouse.y = y;
|
||||
return Event::Mouse("jjj", mouse);
|
||||
}
|
||||
|
||||
Component BasicComponent() {
|
||||
return Renderer([] { return text("[ ]"); });
|
||||
}
|
||||
|
||||
TEST(HoverableTest, BasicBool) {
|
||||
bool hover_1 = false;
|
||||
bool hover_2 = false;
|
||||
auto c1 = Hoverable(BasicComponent(), &hover_1);
|
||||
auto c2 = Hoverable(BasicComponent(), &hover_2);
|
||||
auto layout = Container::Horizontal({c1, c2});
|
||||
auto screen = Screen(8, 2);
|
||||
Render(screen, layout->Render());
|
||||
EXPECT_FALSE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(0, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(1, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(2, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(3, 0)));
|
||||
EXPECT_FALSE(hover_1);
|
||||
EXPECT_TRUE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(0, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
}
|
||||
|
||||
TEST(HoverableTest, BasicCallback) {
|
||||
int on_enter_1 = 0;
|
||||
int on_enter_2 = 0;
|
||||
int on_leave_1 = 0;
|
||||
int on_leave_2 = 0;
|
||||
auto c1 = Hoverable(
|
||||
BasicComponent(), [&] { on_enter_1++; }, [&] { on_leave_1++; });
|
||||
auto c2 = Hoverable(
|
||||
BasicComponent(), [&] { on_enter_2++; }, [&] { on_leave_2++; });
|
||||
auto layout = Container::Horizontal({c1, c2});
|
||||
auto screen = Screen(8, 2);
|
||||
Render(screen, layout->Render());
|
||||
EXPECT_EQ(on_enter_1, 0);
|
||||
EXPECT_EQ(on_enter_2, 0);
|
||||
EXPECT_EQ(on_leave_1, 0);
|
||||
EXPECT_EQ(on_leave_2, 0);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(0, 0)));
|
||||
EXPECT_EQ(on_enter_1, 1);
|
||||
EXPECT_EQ(on_enter_2, 0);
|
||||
EXPECT_EQ(on_leave_1, 0);
|
||||
EXPECT_EQ(on_leave_2, 0);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(1, 0)));
|
||||
EXPECT_EQ(on_enter_1, 1);
|
||||
EXPECT_EQ(on_enter_2, 0);
|
||||
EXPECT_EQ(on_leave_1, 0);
|
||||
EXPECT_EQ(on_leave_2, 0);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(2, 0)));
|
||||
EXPECT_EQ(on_enter_1, 1);
|
||||
EXPECT_EQ(on_enter_2, 0);
|
||||
EXPECT_EQ(on_leave_1, 0);
|
||||
EXPECT_EQ(on_leave_2, 0);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(3, 0)));
|
||||
EXPECT_EQ(on_enter_1, 1);
|
||||
EXPECT_EQ(on_enter_2, 1);
|
||||
EXPECT_EQ(on_leave_1, 1);
|
||||
EXPECT_EQ(on_leave_2, 0);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(0, 0)));
|
||||
EXPECT_EQ(on_enter_1, 2);
|
||||
EXPECT_EQ(on_enter_2, 1);
|
||||
EXPECT_EQ(on_leave_1, 1);
|
||||
EXPECT_EQ(on_leave_2, 1);
|
||||
}
|
||||
|
||||
TEST(HoverableTest, BasicBoolCallback) {
|
||||
bool hover_1 = false;
|
||||
bool hover_2 = false;
|
||||
auto c1 = Hoverable(BasicComponent(), [&](bool hover) { hover_1 = hover; });
|
||||
auto c2 = Hoverable(BasicComponent(), [&](bool hover) { hover_2 = hover; });
|
||||
auto layout = Container::Horizontal({c1, c2});
|
||||
auto screen = Screen(8, 2);
|
||||
Render(screen, layout->Render());
|
||||
EXPECT_FALSE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(0, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(1, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(2, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(3, 0)));
|
||||
EXPECT_FALSE(hover_1);
|
||||
EXPECT_TRUE(hover_2);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(0, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
}
|
||||
|
||||
TEST(HoverableTest, Coverage) {
|
||||
bool hover_1 = false;
|
||||
bool hover_2 = false;
|
||||
int on_enter = 0;
|
||||
int on_leave = 0;
|
||||
auto c1 = BasicComponent();
|
||||
c1 |= Hoverable(&hover_1);
|
||||
c1 |= Hoverable([&](bool hover) { hover_2 = hover; });
|
||||
c1 |= Hoverable([&] { on_enter++; }, [&] { on_leave++; });
|
||||
auto c2 = BasicComponent();
|
||||
auto layout = Container::Horizontal({c1, c2});
|
||||
|
||||
auto screen = Screen(8, 2);
|
||||
Render(screen, layout->Render());
|
||||
EXPECT_FALSE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
EXPECT_EQ(on_enter, 0);
|
||||
EXPECT_EQ(on_leave, 0);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(0, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_TRUE(hover_2);
|
||||
EXPECT_EQ(on_enter, 1);
|
||||
EXPECT_EQ(on_leave, 0);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(1, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_TRUE(hover_2);
|
||||
EXPECT_EQ(on_enter, 1);
|
||||
EXPECT_EQ(on_leave, 0);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(3, 0)));
|
||||
EXPECT_FALSE(hover_1);
|
||||
EXPECT_FALSE(hover_2);
|
||||
EXPECT_EQ(on_enter, 1);
|
||||
EXPECT_EQ(on_leave, 1);
|
||||
|
||||
EXPECT_FALSE(layout->OnEvent(HoverEvent(0, 0)));
|
||||
EXPECT_TRUE(hover_1);
|
||||
EXPECT_TRUE(hover_2);
|
||||
EXPECT_EQ(on_enter, 2);
|
||||
EXPECT_EQ(on_leave, 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace ftxui
|
||||
|
||||
// Copyright 2021 Arthur Sonzogni. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be found in
|
||||
// the LICENSE file.
|
Loading…
Reference in New Issue
Block a user