mirror of
				https://github.com/ArthurSonzogni/FTXUI.git
				synced 2025-10-31 02:28:11 +08:00 
			
		
		
		
	Feature: Windows. (#690)
Into ftxui/component/, add: ``` Container::Stacked(...) Window(...); ``` Together, they can be used to display draggable/resizable windows. Bug:https://github.com/ArthurSonzogni/FTXUI/issues/682 * Fix typo.
This commit is contained in:
		| @@ -132,6 +132,7 @@ add_library(component | ||||
|   src/ftxui/component/terminal_input_parser.cpp | ||||
|   src/ftxui/component/terminal_input_parser.hpp | ||||
|   src/ftxui/component/util.cpp | ||||
|   src/ftxui/component/window.cpp | ||||
| ) | ||||
|  | ||||
| target_link_libraries(dom | ||||
|   | ||||
| @@ -43,4 +43,5 @@ example(tab_horizontal) | ||||
| example(tab_vertical) | ||||
| example(textarea) | ||||
| example(toggle) | ||||
| example(window) | ||||
| example(with_restored_io) | ||||
|   | ||||
							
								
								
									
										86
									
								
								examples/component/window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								examples/component/window.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| #include <ftxui/component/component.hpp> | ||||
| #include <ftxui/component/screen_interactive.hpp> | ||||
|  | ||||
| using namespace ftxui; | ||||
|  | ||||
| Component DummyWindowContent() { | ||||
|   class Impl : public ComponentBase { | ||||
|    private: | ||||
|     bool checked[3] = {false, false, false}; | ||||
|     float slider = 50; | ||||
|  | ||||
|    public: | ||||
|     Impl() { | ||||
|       Add(Container::Vertical({ | ||||
|           Checkbox("Check me", &checked[0]), | ||||
|           Checkbox("Check me", &checked[1]), | ||||
|           Checkbox("Check me", &checked[2]), | ||||
|           Slider("Slider", &slider, 0.f, 100.f), | ||||
|       })); | ||||
|     } | ||||
|   }; | ||||
|   return Make<Impl>(); | ||||
| } | ||||
|  | ||||
| int main() { | ||||
|  | ||||
|   int window_1_left = 20; | ||||
|   int window_1_top = 10; | ||||
|   int window_1_width = 40; | ||||
|   int window_1_height = 20; | ||||
|  | ||||
|   auto window_1 = Window({ | ||||
|       .inner = DummyWindowContent(), | ||||
|       .title = "First window", | ||||
|       .left = &window_1_left, | ||||
|       .top = &window_1_top, | ||||
|       .width = &window_1_width, | ||||
|       .height = &window_1_height, | ||||
|   }); | ||||
|  | ||||
|   auto window_2 = Window({ | ||||
|       .inner = DummyWindowContent(), | ||||
|       .title = "My window", | ||||
|       .left = 40, | ||||
|       .top = 20, | ||||
|   }); | ||||
|  | ||||
|   auto window_3 = Window({ | ||||
|       .inner = DummyWindowContent(), | ||||
|       .title = "My window", | ||||
|       .left = 60, | ||||
|       .top = 30, | ||||
|   }); | ||||
|  | ||||
|   auto window_4 = Window({ | ||||
|       .inner = DummyWindowContent(), | ||||
|   }); | ||||
|  | ||||
|   auto window_5 = Window({}); | ||||
|  | ||||
|   auto window_container = Container::Stacked({ | ||||
|       window_1, | ||||
|       window_2, | ||||
|       window_3, | ||||
|       window_4, | ||||
|       window_5, | ||||
|   }); | ||||
|  | ||||
|   auto display_win_1 = Renderer([&] { | ||||
|     return text("window_1: " +  // | ||||
|                 std::to_string(window_1_width) + "x" + | ||||
|                 std::to_string(window_1_height) + " + " + | ||||
|                 std::to_string(window_1_left) + "," + | ||||
|                 std::to_string(window_1_top)); | ||||
|   }); | ||||
|  | ||||
|   auto layout = Container::Vertical({ | ||||
|       display_win_1, | ||||
|       window_container, | ||||
|   }); | ||||
|  | ||||
|   auto screen = ScreenInteractive::Fullscreen(); | ||||
|   screen.Loop(layout); | ||||
|  | ||||
|   return EXIT_SUCCESS; | ||||
| } | ||||
| @@ -35,4 +35,3 @@ example(style_underlined_double) | ||||
| example(table) | ||||
| example(vbox_hbox) | ||||
| example(vflow) | ||||
| example(window) | ||||
|   | ||||
| @@ -1,34 +0,0 @@ | ||||
| #include <stdlib.h>                // for EXIT_SUCCESS | ||||
| #include <ftxui/dom/elements.hpp>  // for operator|=, Element, bgcolor, color, graph, border | ||||
| #include <ftxui/screen/screen.hpp>  // for Fixed, Screen | ||||
| #include <vector>                   // for vector | ||||
|  | ||||
| #include "ftxui/dom/node.hpp"      // for Render | ||||
| #include "ftxui/screen/color.hpp"  // for Color, Color::DarkBlue, Color::Red, ftxui | ||||
|  | ||||
| int main() { | ||||
|   using namespace ftxui; | ||||
|   Element document = graph([](int x, int y) { | ||||
|     std::vector<int> result(x, 0); | ||||
|     for (int i{0}; i < x; ++i) { | ||||
|       result[i] = ((3 * i) / 2) % y; | ||||
|     } | ||||
|     return result; | ||||
|   }); | ||||
|  | ||||
|   document |= color(Color::Red); | ||||
|   document |= bgcolor(Color::DarkBlue); | ||||
|   document |= border; | ||||
|  | ||||
|   const int width = 80; | ||||
|   const int height = 10; | ||||
|   auto screen = | ||||
|       Screen::Create(Dimension::Fixed(width), Dimension::Fixed(height)); | ||||
|   Render(screen, document); | ||||
|   screen.Print(); | ||||
|   return EXIT_SUCCESS; | ||||
| } | ||||
|  | ||||
| // 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. | ||||
| @@ -40,6 +40,7 @@ Component Vertical(Components children, int* selector); | ||||
| Component Horizontal(Components children); | ||||
| Component Horizontal(Components children, int* selector); | ||||
| Component Tab(Components children, int* selector); | ||||
| Component Stacked(Components children); | ||||
| }  // namespace Container | ||||
|  | ||||
| Component Button(ButtonOption options); | ||||
| @@ -131,6 +132,8 @@ ComponentDecorator Hoverable(std::function<void()> on_enter, | ||||
|                              std::function<void()> on_leave); | ||||
| ComponentDecorator Hoverable(std::function<void(bool)> on_change); | ||||
|  | ||||
| Component Window(WindowOptions option); | ||||
|  | ||||
| }  // namespace ftxui | ||||
|  | ||||
| #endif /* end of include guard: FTXUI_COMPONENT_HPP */ | ||||
|   | ||||
| @@ -226,6 +226,39 @@ struct SliderOption { | ||||
|   Color color_inactive = Color::GrayDark; | ||||
| }; | ||||
|  | ||||
| // Parameter pack used by `WindowOptions::render`. | ||||
| struct WindowRenderState { | ||||
|   Element inner;             /// < The element wrapped inside this window. | ||||
|   const std::string& title;  /// < The title of the window. | ||||
|   bool active = false;       /// < Whether the window is the active one. | ||||
|   bool drag = false;         /// < Whether the window is being dragged. | ||||
|   bool resize = false;       /// < Whether the window is being resized. | ||||
|   bool hover_left = false;   /// < Whether the resizeable left side is hovered. | ||||
|   bool hover_right = false;  /// < Whether the resizeable right side is hovered. | ||||
|   bool hover_top = false;    /// < Whether the resizeable top side is hovered. | ||||
|   bool hover_down = false;   /// < Whether the resizeable down side is hovered. | ||||
| }; | ||||
|  | ||||
| // @brief Option for the `Window` component. | ||||
| // @ingroup component | ||||
| struct WindowOptions { | ||||
|   Component inner;            /// < The component wrapped by this window. | ||||
|   ConstStringRef title = "";  /// < The title displayed by this window. | ||||
|  | ||||
|   Ref<int> left = 0;     /// < The left side position of the window. | ||||
|   Ref<int> top = 0;      /// < The top side position of the window. | ||||
|   Ref<int> width = 20;   /// < The width of the window. | ||||
|   Ref<int> height = 10;  /// < The height of the window. | ||||
|  | ||||
|   Ref<bool> resize_left = true;   /// < Can the left side be resized? | ||||
|   Ref<bool> resize_right = true;  /// < Can the right side be resized? | ||||
|   Ref<bool> resize_top = true;    /// < Can the top side be resized? | ||||
|   Ref<bool> resize_down = true;   /// < Can the down side be resized? | ||||
|  | ||||
|   /// An optional function to customize how the window looks like: | ||||
|   std::function<Element(const WindowRenderState&)> render; | ||||
| }; | ||||
|  | ||||
| }  // namespace ftxui | ||||
|  | ||||
| #endif /* end of include guard: FTXUI_COMPONENT_COMPONENT_OPTIONS_HPP */ | ||||
|   | ||||
| @@ -235,6 +235,62 @@ class TabContainer : public ContainerBase { | ||||
|   } | ||||
| }; | ||||
|  | ||||
| class StackedContainer : public ContainerBase{ | ||||
|  public: | ||||
|   StackedContainer(Components children) | ||||
|       : ContainerBase(std::move(children), nullptr) {} | ||||
|  | ||||
|  private: | ||||
|   Element Render() final { | ||||
|     Elements elements; | ||||
|     for (auto& child : children_) { | ||||
|       elements.push_back(child->Render()); | ||||
|     } | ||||
|     // Reverse the order of the elements. | ||||
|     std::reverse(elements.begin(), elements.end()); | ||||
|     return dbox(std::move(elements)); | ||||
|   } | ||||
|  | ||||
|   bool Focusable() const final { | ||||
|     for (auto& child : children_) { | ||||
|       if (child->Focusable()) { | ||||
|         return true; | ||||
|       } | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   Component ActiveChild() final { | ||||
|     if (children_.size() == 0) | ||||
|       return nullptr; | ||||
|     return children_[0]; | ||||
|   } | ||||
|  | ||||
|   void SetActiveChild(ComponentBase* child) final { | ||||
|     if (children_.size() == 0) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     // Find `child` and put it at the beginning without change the order of the | ||||
|     // other children. | ||||
|     auto it = std::find_if(children_.begin(), children_.end(), | ||||
|                            [child](const Component& c) { return c.get() == child; }); | ||||
|     if (it == children_.end()) { | ||||
|       return; | ||||
|     } | ||||
|     std::rotate(children_.begin(), it, it + 1); | ||||
|   } | ||||
|  | ||||
|   bool OnEvent(Event event) final { | ||||
|     for (auto& child : children_) { | ||||
|       if (child->OnEvent(event)) { | ||||
|         return true; | ||||
|       } | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| namespace Container { | ||||
|  | ||||
| /// @brief A list of components, drawn one by one vertically and navigated | ||||
| @@ -345,6 +401,33 @@ Component Tab(Components children, int* selector) { | ||||
|   return std::make_shared<TabContainer>(std::move(children), selector); | ||||
| } | ||||
|  | ||||
| /// @brief A list of components to be stacked on top of each other. | ||||
| /// Events are propagated to the first component, then the second if not | ||||
| /// handled, etc. | ||||
| /// The components are drawn in the reverse order they are given. | ||||
| /// When a component take focus, it is put at the front, without changing the | ||||
| /// relative order of the other elements. | ||||
| /// | ||||
| /// This should be used with the `Window` component.  | ||||
| /// | ||||
| /// @param children The list of components. | ||||
| /// @ingroup component | ||||
| /// @see Window | ||||
| /// | ||||
| /// ### Example | ||||
| /// | ||||
| /// ```cpp | ||||
| /// auto container = Container::Stacked({ | ||||
| ///   children_1, | ||||
| ///   children_2, | ||||
| ///   children_3, | ||||
| ///   children_4, | ||||
| /// }); | ||||
| /// ``` | ||||
| Component Stacked(Components children) { | ||||
|   return std::make_shared<StackedContainer>(std::move(children)); | ||||
| } | ||||
|  | ||||
| }  // namespace Container | ||||
|  | ||||
| }  // namespace ftxui | ||||
|   | ||||
| @@ -135,24 +135,12 @@ class SliderBase : public ComponentBase { | ||||
|   } | ||||
|  | ||||
|   bool OnMouseEvent(Event event) { | ||||
|     if (captured_mouse_ && event.mouse().motion == Mouse::Released) { | ||||
|     if (captured_mouse_) { | ||||
|       if (event.mouse().motion == Mouse::Released) { | ||||
|         captured_mouse_ = nullptr; | ||||
|         return true; | ||||
|       } | ||||
|  | ||||
|     if (gauge_box_.Contain(event.mouse().x, event.mouse().y) && | ||||
|         CaptureMouse(event)) { | ||||
|       TakeFocus(); | ||||
|     } | ||||
|  | ||||
|     if (event.mouse().button == Mouse::Left && | ||||
|         event.mouse().motion == Mouse::Pressed && | ||||
|         gauge_box_.Contain(event.mouse().x, event.mouse().y) && | ||||
|         !captured_mouse_) { | ||||
|       captured_mouse_ = CaptureMouse(event); | ||||
|     } | ||||
|  | ||||
|     if (captured_mouse_) { | ||||
|       switch (options_.direction) { | ||||
|         case Direction::Right: { | ||||
|           value_() = min_() + (event.mouse().x - gauge_box_.x_min) * | ||||
| @@ -182,6 +170,23 @@ class SliderBase : public ComponentBase { | ||||
|       value_() = std::max(min_(), std::min(max_(), value_())); | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     if (event.mouse().button != Mouse::Left || | ||||
|         event.mouse().motion != Mouse::Pressed) { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     if (!gauge_box_.Contain(event.mouse().x, event.mouse().y)) { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     captured_mouse_ = CaptureMouse(event); | ||||
|  | ||||
|     if (captured_mouse_) { | ||||
|       TakeFocus(); | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
| @@ -214,7 +219,9 @@ class SliderWithLabel : public ComponentBase { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     if (!box_.Contain(event.mouse().x, event.mouse().y)) { | ||||
|     mouse_hover_ = box_.Contain(event.mouse().x, event.mouse().y); | ||||
|  | ||||
|     if (!mouse_hover_) { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
| @@ -222,13 +229,13 @@ class SliderWithLabel : public ComponentBase { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     TakeFocus(); | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   Element Render() override { | ||||
|     auto focus_management = Focused() ? focus : Active() ? select : nothing; | ||||
|     auto gauge_color = Focused() ? color(Color::White) : color(Color::GrayDark); | ||||
|     auto gauge_color = (Focused() || mouse_hover_) ? color(Color::White) | ||||
|                                                    : color(Color::GrayDark); | ||||
|     return hbox({ | ||||
|                text(label_()) | dim | vcenter, | ||||
|                hbox({ | ||||
| @@ -242,6 +249,7 @@ class SliderWithLabel : public ComponentBase { | ||||
|  | ||||
|   ConstStringRef label_; | ||||
|   Box box_; | ||||
|   bool mouse_hover_ = false; | ||||
| }; | ||||
|  | ||||
| /// @brief An horizontal slider. | ||||
|   | ||||
| @@ -53,8 +53,9 @@ TEST(SliderTest, Right) { | ||||
|   }); | ||||
|   Screen screen(11, 1); | ||||
|   Render(screen, slider->Render()); | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(3, 0))); | ||||
|   EXPECT_EQ(value, 30); | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(9, 0))); | ||||
|   EXPECT_EQ(value, 90); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(9, 2))); | ||||
| @@ -76,8 +77,9 @@ TEST(SliderTest, Left) { | ||||
|   }); | ||||
|   Screen screen(11, 1); | ||||
|   Render(screen, slider->Render()); | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(3, 0))); | ||||
|   EXPECT_EQ(value, 70); | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(9, 0))); | ||||
|   EXPECT_EQ(value, 10); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(9, 2))); | ||||
| @@ -99,8 +101,9 @@ TEST(SliderTest, Down) { | ||||
|   }); | ||||
|   Screen screen(1, 11); | ||||
|   Render(screen, slider->Render()); | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(0, 3))); | ||||
|   EXPECT_EQ(value, 30); | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(0, 9))); | ||||
|   EXPECT_EQ(value, 90); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(2, 9))); | ||||
| @@ -122,8 +125,9 @@ TEST(SliderTest, Up) { | ||||
|   }); | ||||
|   Screen screen(1, 11); | ||||
|   Render(screen, slider->Render()); | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(0, 3))); | ||||
|   EXPECT_EQ(value, 70); | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(0, 9))); | ||||
|   EXPECT_EQ(value, 10); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(2, 9))); | ||||
|   | ||||
							
								
								
									
										306
									
								
								src/ftxui/component/window.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										306
									
								
								src/ftxui/component/window.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,306 @@ | ||||
| #define NOMINMAX | ||||
| #include <algorithm> | ||||
| #include <ftxui/component/component.hpp> | ||||
| #include <ftxui/component/component_base.hpp> | ||||
| #include <ftxui/component/screen_interactive.hpp>  // for ScreenInteractive | ||||
| #include "ftxui/dom/node_decorator.hpp"            // for NodeDecorator | ||||
|  | ||||
| namespace ftxui { | ||||
|  | ||||
| namespace { | ||||
|  | ||||
| Decorator PositionAndSize(int left, int top, int width, int height) { | ||||
|   return [=](Element element) { | ||||
|     element |= size(WIDTH, EQUAL, width); | ||||
|     element |= size(HEIGHT, EQUAL, height); | ||||
|  | ||||
|     auto padding_left = emptyElement() | size(WIDTH, EQUAL, left); | ||||
|     auto padding_top = emptyElement() | size(HEIGHT, EQUAL, top); | ||||
|  | ||||
|     return vbox({ | ||||
|         padding_top, | ||||
|         hbox({ | ||||
|             padding_left, | ||||
|             element, | ||||
|         }), | ||||
|     }); | ||||
|   }; | ||||
| } | ||||
|  | ||||
| class ResizeDecorator : public NodeDecorator { | ||||
|  public: | ||||
|   ResizeDecorator(Element child, | ||||
|                   bool resize_left, | ||||
|                   bool resize_right, | ||||
|                   bool resize_top, | ||||
|                   bool resize_down, | ||||
|                   Color color) | ||||
|       : NodeDecorator(std::move(child)), | ||||
|         color_(color), | ||||
|         resize_left_(resize_left), | ||||
|         resize_right_(resize_right), | ||||
|         resize_top_(resize_top), | ||||
|         resize_down_(resize_down) {} | ||||
|  | ||||
|   void Render(Screen& screen) override { | ||||
|     NodeDecorator::Render(screen); | ||||
|  | ||||
|     if (resize_left_) { | ||||
|       for (int y = box_.y_min; y <= box_.y_max; ++y) { | ||||
|         auto& cell = screen.PixelAt(box_.x_min, y); | ||||
|         cell.foreground_color = color_; | ||||
|         cell.automerge = false; | ||||
|       } | ||||
|     } | ||||
|     if (resize_right_) { | ||||
|       for (int y = box_.y_min; y <= box_.y_max; ++y) { | ||||
|         auto& cell = screen.PixelAt(box_.x_max, y); | ||||
|         cell.foreground_color = color_; | ||||
|         cell.automerge = false; | ||||
|       } | ||||
|     } | ||||
|     if (resize_top_) { | ||||
|       for (int x = box_.x_min; x <= box_.x_max; ++x) { | ||||
|         auto& cell = screen.PixelAt(x, box_.y_min); | ||||
|         cell.foreground_color = color_; | ||||
|         cell.automerge = false; | ||||
|       } | ||||
|     } | ||||
|     if (resize_down_) { | ||||
|       for (int x = box_.x_min; x <= box_.x_max; ++x) { | ||||
|         auto& cell = screen.PixelAt(x, box_.y_max); | ||||
|         cell.foreground_color = color_; | ||||
|         cell.automerge = false; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Color color_; | ||||
|   const bool resize_left_; | ||||
|   const bool resize_right_; | ||||
|   const bool resize_top_; | ||||
|   const bool resize_down_; | ||||
| }; | ||||
|  | ||||
| Element DefaultRenderState(const WindowRenderState& state) { | ||||
|   Element element = state.inner; | ||||
|   if (state.active) { | ||||
|     element |= dim; | ||||
|   } | ||||
|  | ||||
|   element = window(text(state.title), element); | ||||
|   element |= clear_under; | ||||
|  | ||||
|  | ||||
|   Color color = Color::Red; | ||||
|  | ||||
|   element = std::make_shared<ResizeDecorator>(  // | ||||
|       element,                                  // | ||||
|       state.hover_left,                         // | ||||
|       state.hover_right,                        // | ||||
|       state.hover_top,                          // | ||||
|       state.hover_down,                         // | ||||
|       color                                     // | ||||
|   ); | ||||
|  | ||||
|   return element; | ||||
| } | ||||
|  | ||||
| class WindowImpl : public ComponentBase, public WindowOptions { | ||||
|  public: | ||||
|   WindowImpl(WindowOptions option) : WindowOptions(std::move(option)) { | ||||
|     if (!inner) { | ||||
|       inner = Make<ComponentBase>(); | ||||
|     } | ||||
|     Add(inner); | ||||
|   } | ||||
|  | ||||
|  private: | ||||
|   Element Render() final { | ||||
|     auto element = ComponentBase::Render(); | ||||
|  | ||||
|     bool captureable = | ||||
|         captured_mouse_ || ScreenInteractive::Active()->CaptureMouse(); | ||||
|  | ||||
|     const WindowRenderState state = { | ||||
|         element, | ||||
|         title(), | ||||
|         Active(), | ||||
|         drag_, | ||||
|         resize_left_ || resize_right_ || resize_down_ || resize_top_, | ||||
|         (resize_left_hover_ || resize_left_) && captureable, | ||||
|         (resize_right_hover_ || resize_right_) && captureable, | ||||
|         (resize_top_hover_ || resize_top_) && captureable, | ||||
|         (resize_down_hover_ || resize_down_) && captureable, | ||||
|     }; | ||||
|  | ||||
|     element = render ? render(state) : DefaultRenderState(state); | ||||
|  | ||||
|     // Position and record the drawn area of the window. | ||||
|     element |= reflect(box_window_); | ||||
|     element |= PositionAndSize(left(), top(), width(), height()); | ||||
|     element |= reflect(box_); | ||||
|  | ||||
|     return element; | ||||
|   } | ||||
|  | ||||
|   bool OnEvent(Event event) final { | ||||
|     if (ComponentBase::OnEvent(event)) { | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     if (!event.is_mouse()) { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     mouse_hover_ = box_window_.Contain(event.mouse().x, event.mouse().y); | ||||
|  | ||||
|     resize_down_hover_ = false; | ||||
|     resize_top_hover_ = false; | ||||
|     resize_left_hover_ = false; | ||||
|     resize_right_hover_ = false; | ||||
|  | ||||
|     if (mouse_hover_) { | ||||
|       resize_left_hover_ = event.mouse().x == left() + box_.x_min; | ||||
|       resize_right_hover_ = | ||||
|           event.mouse().x == left() + width() - 1 + box_.x_min; | ||||
|       resize_top_hover_ = event.mouse().y == top() + box_.y_min; | ||||
|       resize_down_hover_ = event.mouse().y == top() + height() - 1 + box_.y_min; | ||||
|  | ||||
|       // Apply the component options: | ||||
|       resize_top_hover_ &= resize_top(); | ||||
|       resize_left_hover_ &= resize_left(); | ||||
|       resize_down_hover_ &= resize_down(); | ||||
|       resize_right_hover_ &= resize_right(); | ||||
|     } | ||||
|  | ||||
|     if (captured_mouse_) { | ||||
|       if (event.mouse().motion == Mouse::Released) { | ||||
|         captured_mouse_ = nullptr; | ||||
|         return true; | ||||
|       } | ||||
|  | ||||
|       if (resize_left_) { | ||||
|         width() = left() + width() - event.mouse().x + box_.x_min; | ||||
|         left() = event.mouse().x - box_.x_min; | ||||
|       } | ||||
|  | ||||
|       if (resize_right_) { | ||||
|         width() = event.mouse().x - resize_start_x - box_.x_min; | ||||
|       } | ||||
|  | ||||
|       if (resize_top_) { | ||||
|         height() = top() + height() - event.mouse().y + box_.y_min; | ||||
|         top() = event.mouse().y - box_.y_min; | ||||
|       } | ||||
|  | ||||
|       if (resize_down_) { | ||||
|         height() = event.mouse().y - resize_start_y - box_.y_min; | ||||
|       } | ||||
|  | ||||
|       if (drag_) { | ||||
|         left() = event.mouse().x - drag_start_x - box_.x_min; | ||||
|         top() = event.mouse().y - drag_start_y - box_.y_min; | ||||
|       } | ||||
|  | ||||
|       // Clamp the window size. | ||||
|       width() = std::max<int>(width(), title().size() + 2); | ||||
|       height() = std::max<int>(height(), 2); | ||||
|  | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     resize_left_ = false; | ||||
|     resize_right_ = false; | ||||
|     resize_top_ = false; | ||||
|     resize_down_ = false; | ||||
|  | ||||
|     if (!mouse_hover_) { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     if (!CaptureMouse(event)) { | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     if (event.mouse().button != Mouse::Left || | ||||
|         event.mouse().motion != Mouse::Pressed) { | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     TakeFocus(); | ||||
|  | ||||
|     captured_mouse_ = CaptureMouse(event); | ||||
|     if (!captured_mouse_) { | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     resize_left_ = resize_left_hover_; | ||||
|     resize_right_ = resize_right_hover_; | ||||
|     resize_top_ = resize_top_hover_; | ||||
|     resize_down_ = resize_down_hover_; | ||||
|  | ||||
|     resize_start_x = event.mouse().x - width() - box_.x_min; | ||||
|     resize_start_y = event.mouse().y - height() - box_.y_min; | ||||
|     drag_start_x = event.mouse().x - left() - box_.x_min; | ||||
|     drag_start_y = event.mouse().y - top() - box_.y_min; | ||||
|  | ||||
|     // Drag only if we are not resizeing a border yet: | ||||
|     drag_ = !resize_right_ && !resize_down_ && !resize_top_ && !resize_left_; | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   Box box_; | ||||
|   Box box_window_; | ||||
|  | ||||
|   CapturedMouse captured_mouse_; | ||||
|   int drag_start_x = 0; | ||||
|   int drag_start_y = 0; | ||||
|   int resize_start_x = 0; | ||||
|   int resize_start_y = 0; | ||||
|  | ||||
|   bool mouse_hover_ = false; | ||||
|   bool drag_ = false; | ||||
|   bool resize_top_ = false; | ||||
|   bool resize_left_ = false; | ||||
|   bool resize_down_ = false; | ||||
|   bool resize_right_ = false; | ||||
|  | ||||
|   bool resize_top_hover_ = false; | ||||
|   bool resize_left_hover_ = false; | ||||
|   bool resize_down_hover_ = false; | ||||
|   bool resize_right_hover_ = false; | ||||
| }; | ||||
|  | ||||
| }  // namespace | ||||
|  | ||||
| /// @brief A draggeable / resizeable window. To use multiple of them, they must | ||||
| /// be stacked using `Container::Stacked({...})` component; | ||||
| /// | ||||
| /// @param option A struct holding every parameters. | ||||
| /// @ingroup component | ||||
| /// @see Window | ||||
| /// | ||||
| /// ### Example | ||||
| /// | ||||
| /// ```cpp | ||||
| /// auto window_1= Window({ | ||||
| ///     .inner = DummyWindowContent(), | ||||
| ///     .title = "First window", | ||||
| /// }); | ||||
| /// | ||||
| /// auto window_2= Window({ | ||||
| ///     .inner = DummyWindowContent(), | ||||
| ///     .title = "Second window", | ||||
| /// }); | ||||
| /// | ||||
| /// auto container = Container::Stacked({ | ||||
| ///   window_1, | ||||
| ///   window_2, | ||||
| /// }); | ||||
| /// ``` | ||||
| Component Window(WindowOptions option) { | ||||
|   return Make<WindowImpl>(std::move(option)); | ||||
| } | ||||
|  | ||||
| };  // namespace ftxui | ||||
		Reference in New Issue
	
	Block a user
	 Arthur Sonzogni
					Arthur Sonzogni