mirror of
				https://github.com/ArthurSonzogni/FTXUI.git
				synced 2025-10-31 18:48:11 +08:00 
			
		
		
		
	Merge dom and component focus (#978)
Instead of two levels of focus with `focus` and `selected`, use a recursive level. The components set the one "active" and hbox/vbox/dbox Co-authored-by: ArthurSonzogni <sonzogniarthur@gmail.com>
This commit is contained in:
		| @@ -61,6 +61,8 @@ current (development) | ||||
|   - See `selectionForegroundColor` decorator. | ||||
|   - See `selectionStyle(style)` decorator. | ||||
|   - See `selectionStyleReset` decorator. | ||||
| - Breaking change: Change how "focus"/"select" are handled. This fixes the | ||||
|   behavior. | ||||
|  | ||||
| ### Screen | ||||
| - Feature: Add `Box::IsEmpty()`. | ||||
|   | ||||
| @@ -18,6 +18,7 @@ example(focus_cursor) | ||||
| example(gallery) | ||||
| example(homescreen) | ||||
| example(input) | ||||
| example(input_in_frame) | ||||
| example(input_style) | ||||
| example(linear_gradient_gallery) | ||||
| example(maybe) | ||||
| @@ -38,8 +39,8 @@ example(radiobox) | ||||
| example(radiobox_in_frame) | ||||
| example(renderer) | ||||
| example(resizable_split) | ||||
| example(selection) | ||||
| example(scrollbar) | ||||
| example(selection) | ||||
| example(slider) | ||||
| example(slider_direction) | ||||
| example(slider_rgb) | ||||
|   | ||||
| @@ -1,30 +1,38 @@ | ||||
| // 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. | ||||
| #include <memory>  // for allocator, __shared_ptr_access | ||||
| #include <string>  // for string, basic_string, operator+, to_string | ||||
| #include <vector>  // for vector | ||||
| #include <array>  // for array | ||||
| #include <iostream> | ||||
| #include <memory>  // for shared_ptr, __shared_ptr_access | ||||
| #include <string>  // for operator+, to_string | ||||
|  | ||||
| #include "ftxui/component/captured_mouse.hpp"  // for ftxui | ||||
| #include "ftxui/component/component.hpp"       // for Input, Renderer, Vertical | ||||
| #include "ftxui/component/component_base.hpp"  // for ComponentBase | ||||
| #include "ftxui/component/screen_interactive.hpp"  // for Component, ScreenInteractive | ||||
| #include "ftxui/component/component.hpp"  // for Checkbox, Renderer, Vertical | ||||
| #include "ftxui/component/component_base.hpp"      // for ComponentBase | ||||
| #include "ftxui/component/screen_interactive.hpp"  // for ScreenInteractive | ||||
| #include "ftxui/dom/elements.hpp"  // for operator|, Element, size, border, frame, vscroll_indicator, HEIGHT, LESS_THAN | ||||
|  | ||||
| using namespace ftxui; | ||||
|  | ||||
| int main() { | ||||
|   using namespace ftxui; | ||||
|   bool download = false; | ||||
|   bool upload = false; | ||||
|   bool ping = false; | ||||
|  | ||||
|   Component input_list = Container::Vertical({}); | ||||
|   std::vector<std::string> items(100, ""); | ||||
|   for (size_t i = 0; i < items.size(); ++i) { | ||||
|     input_list->Add(Input(&(items[i]), "placeholder " + std::to_string(i))); | ||||
|   } | ||||
|  | ||||
|   auto renderer = Renderer(input_list, [&] { | ||||
|     return input_list->Render() | vscroll_indicator | frame | border | | ||||
|            size(HEIGHT, LESS_THAN, 10); | ||||
|   auto container = Container::Vertical({ | ||||
|       Checkbox("Download", &download), | ||||
|       Checkbox("Upload", &upload), | ||||
|       Checkbox("Ping", &ping), | ||||
|   }); | ||||
|  | ||||
|   auto screen = ScreenInteractive::TerminalOutput(); | ||||
|   screen.Loop(renderer); | ||||
|   auto screen = ScreenInteractive::FitComponent(); | ||||
|   screen.Loop(container); | ||||
|  | ||||
|   std::cout << "---" << std::endl; | ||||
|   std::cout << "Download: " << download << std::endl; | ||||
|   std::cout << "Upload: " << upload << std::endl; | ||||
|   std::cout << "Ping: " << ping << std::endl; | ||||
|   std::cout << "---" << std::endl; | ||||
|  | ||||
|   return 0; | ||||
| } | ||||
|   | ||||
							
								
								
									
										30
									
								
								examples/component/input_in_frame.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								examples/component/input_in_frame.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| // 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. | ||||
| #include <memory>  // for allocator, __shared_ptr_access | ||||
| #include <string>  // for string, basic_string, operator+, to_string | ||||
| #include <vector>  // for vector | ||||
|  | ||||
| #include "ftxui/component/captured_mouse.hpp"  // for ftxui | ||||
| #include "ftxui/component/component.hpp"       // for Input, Renderer, Vertical | ||||
| #include "ftxui/component/component_base.hpp"  // for ComponentBase | ||||
| #include "ftxui/component/screen_interactive.hpp"  // for Component, ScreenInteractive | ||||
| #include "ftxui/dom/elements.hpp"  // for operator|, Element, size, border, frame, vscroll_indicator, HEIGHT, LESS_THAN | ||||
|  | ||||
| int main() { | ||||
|   using namespace ftxui; | ||||
|  | ||||
|   Component input_list = Container::Vertical({}); | ||||
|   std::vector<std::string> items(100, ""); | ||||
|   for (size_t i = 0; i < items.size(); ++i) { | ||||
|     input_list->Add(Input(&(items[i]), "placeholder " + std::to_string(i))); | ||||
|   } | ||||
|  | ||||
|   auto renderer = Renderer(input_list, [&] { | ||||
|     return input_list->Render() | vscroll_indicator | frame | border | | ||||
|            size(HEIGHT, LESS_THAN, 10); | ||||
|   }); | ||||
|  | ||||
|   auto screen = ScreenInteractive::TerminalOutput(); | ||||
|   screen.Loop(renderer); | ||||
| } | ||||
| @@ -50,7 +50,10 @@ class ComponentBase { | ||||
|   void DetachAllChildren(); | ||||
|  | ||||
|   // Renders the component. | ||||
|   virtual Element Render(); | ||||
|   Element Render(); | ||||
|  | ||||
|   // Override this function modify how `Render` works. | ||||
|   virtual Element OnRender(); | ||||
|  | ||||
|   // Handles an event. | ||||
|   // By default, reduce on children with a lazy OR. | ||||
| @@ -94,6 +97,7 @@ class ComponentBase { | ||||
|  | ||||
|  private: | ||||
|   ComponentBase* parent_ = nullptr; | ||||
|   bool in_render = false; | ||||
| }; | ||||
|  | ||||
| }  // namespace ftxui | ||||
|   | ||||
| @@ -161,7 +161,7 @@ Element frame(Element); | ||||
| Element xframe(Element); | ||||
| Element yframe(Element); | ||||
| Element focus(Element); | ||||
| Element select(Element); | ||||
| Element select(Element e);  // Deprecated - Alias for focus. | ||||
|  | ||||
| // --- Cursor --- | ||||
| // Those are similar to `focus`, but also change the shape of the cursor. | ||||
|   | ||||
| @@ -59,6 +59,8 @@ class Node { | ||||
|   }; | ||||
|   virtual void Check(Status* status); | ||||
|  | ||||
|   friend void Render(Screen& screen, Node* node, Selection& selection); | ||||
|  | ||||
|  protected: | ||||
|   Elements children_; | ||||
|   Requirement requirement_; | ||||
|   | ||||
| @@ -5,8 +5,10 @@ | ||||
| #define FTXUI_DOM_REQUIREMENT_HPP | ||||
|  | ||||
| #include "ftxui/screen/box.hpp" | ||||
| #include "ftxui/screen/screen.hpp" | ||||
|  | ||||
| namespace ftxui { | ||||
| class Node; | ||||
|  | ||||
| struct Requirement { | ||||
|   // The required size to fully draw the element. | ||||
| @@ -20,13 +22,28 @@ struct Requirement { | ||||
|   int flex_shrink_y = 0; | ||||
|  | ||||
|   // Focus management to support the frame/focus/select element. | ||||
|   enum Selection { | ||||
|     NORMAL = 0, | ||||
|     SELECTED = 1, | ||||
|     FOCUSED = 2, | ||||
|   struct Focused { | ||||
|     bool enabled = false; | ||||
|     Box box; | ||||
|     Node* node = nullptr; | ||||
|     Screen::Cursor::Shape cursor_shape = Screen::Cursor::Shape::Hidden; | ||||
|  | ||||
|     // Internal for interactions with components. | ||||
|     bool component_active = false; | ||||
|  | ||||
|     // Return whether this requirement should be preferred over the other. | ||||
|     bool Prefer(const Focused& other) const { | ||||
|       if (!other.enabled) { | ||||
|         return false; | ||||
|       } | ||||
|       if (!enabled) { | ||||
|         return true; | ||||
|       } | ||||
|  | ||||
|       return other.component_active && !component_active; | ||||
|     } | ||||
|   }; | ||||
|   Selection selection = NORMAL; | ||||
|   Box selected_box; | ||||
|   Focused focused; | ||||
| }; | ||||
|  | ||||
| }  // namespace ftxui | ||||
|   | ||||
| @@ -14,6 +14,7 @@ struct Box { | ||||
|  | ||||
|   static auto Intersection(Box a, Box b) -> Box; | ||||
|   static auto Union(Box a, Box b) -> Box; | ||||
|   void Shift(int x, int y); | ||||
|   bool Contain(int x, int y) const; | ||||
|   bool IsEmpty() const; | ||||
|   bool operator==(const Box& other) const; | ||||
|   | ||||
| @@ -37,7 +37,7 @@ class ButtonBase : public ComponentBase, public ButtonOption { | ||||
|   explicit ButtonBase(ButtonOption option) : ButtonOption(std::move(option)) {} | ||||
|  | ||||
|   // Component implementation: | ||||
|   Element Render() override { | ||||
|   Element OnRender() override { | ||||
|     const bool active = Active(); | ||||
|     const bool focused = Focused(); | ||||
|     const bool focused_or_hover = focused || mouse_hover_; | ||||
| @@ -47,14 +47,16 @@ class ButtonBase : public ComponentBase, public ButtonOption { | ||||
|       SetAnimationTarget(target); | ||||
|     } | ||||
|  | ||||
|     auto focus_management = focused ? focus : active ? select : nothing; | ||||
|     const EntryState state{ | ||||
|         *label, false, active, focused_or_hover, Index(), | ||||
|     }; | ||||
|  | ||||
|     auto element = (transform ? transform : DefaultTransform)  // | ||||
|         (state); | ||||
|     return element | AnimatedColorStyle() | focus_management | reflect(box_); | ||||
|     element |= AnimatedColorStyle(); | ||||
|     element |= focus; | ||||
|     element |= reflect(box_); | ||||
|     return element; | ||||
|   } | ||||
|  | ||||
|   Decorator AnimatedColorStyle() { | ||||
|   | ||||
| @@ -23,16 +23,17 @@ class CheckboxBase : public ComponentBase, public CheckboxOption { | ||||
|  | ||||
|  private: | ||||
|   // Component implementation. | ||||
|   Element Render() override { | ||||
|   Element OnRender() override { | ||||
|     const bool is_focused = Focused(); | ||||
|     const bool is_active = Active(); | ||||
|     auto focus_management = is_focused ? focus : is_active ? select : nothing; | ||||
|     auto entry_state = EntryState{ | ||||
|         *label, *checked, is_active, is_focused || hovered_, -1, | ||||
|     }; | ||||
|     auto element = (transform ? transform : CheckboxOption::Simple().transform)( | ||||
|         entry_state); | ||||
|     return element | focus_management | reflect(box_); | ||||
|     element |= focus; | ||||
|     element |= reflect(box_); | ||||
|     return element; | ||||
|   } | ||||
|  | ||||
|   bool OnEvent(Event event) override { | ||||
| @@ -69,7 +70,6 @@ class CheckboxBase : public ComponentBase, public CheckboxOption { | ||||
|         event.mouse().motion == Mouse::Pressed) { | ||||
|       *checked = !*checked; | ||||
|       on_change(); | ||||
|       TakeFocus(); | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -15,6 +15,7 @@ | ||||
| #include "ftxui/component/event.hpp"           // for Event | ||||
| #include "ftxui/component/screen_interactive.hpp"  // for Component, ScreenInteractive | ||||
| #include "ftxui/dom/elements.hpp"                  // for text, Element | ||||
| #include "ftxui/dom/node.hpp"                      // for Node, Elements | ||||
|  | ||||
| namespace ftxui::animation { | ||||
| class Params; | ||||
| @@ -103,10 +104,46 @@ void ComponentBase::DetachAllChildren() { | ||||
| } | ||||
|  | ||||
| /// @brief Draw the component. | ||||
| /// Build a ftxui::Element to be drawn on the ftxi::Screen representing this | ||||
| /// ftxui::ComponentBase. | ||||
| /// Build a ftxui::Element to be drawn on the ftxui::Screen representing this | ||||
| /// ftxui::ComponentBase. Please override OnRender() to modify the rendering. | ||||
| /// @ingroup component | ||||
| Element ComponentBase::Render() { | ||||
|   // Some users might call `ComponentBase::Render()` from | ||||
|   // `T::OnRender()`. To avoid infinite recursion, we use a flag. | ||||
|   if (in_render) { | ||||
|     return ComponentBase::OnRender(); | ||||
|   } | ||||
|  | ||||
|   in_render = true; | ||||
|   Element element = OnRender(); | ||||
|   in_render = false; | ||||
|  | ||||
|   class Wrapper : public Node { | ||||
|    public: | ||||
|     bool active_ = false; | ||||
|  | ||||
|     Wrapper(Element child, bool active) | ||||
|         : Node({std::move(child)}), active_(active) {} | ||||
|  | ||||
|     void SetBox(Box box) override { | ||||
|       Node::SetBox(box); | ||||
|       children_[0]->SetBox(box); | ||||
|     } | ||||
|  | ||||
|     void ComputeRequirement() override { | ||||
|       Node::ComputeRequirement(); | ||||
|       requirement_.focused.component_active = active_; | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   return std::make_shared<Wrapper>(std::move(element), Active()); | ||||
| } | ||||
|  | ||||
| /// @brief Draw the component. | ||||
| /// Build a ftxui::Element to be drawn on the ftxi::Screen representing this | ||||
| /// ftxui::ComponentBase. This function is means to be overridden. | ||||
| /// @ingroup component | ||||
| Element ComponentBase::OnRender() { | ||||
|   if (children_.size() == 1) { | ||||
|     return children_.front()->Render(); | ||||
|   } | ||||
|   | ||||
| @@ -98,7 +98,7 @@ class VerticalContainer : public ContainerBase { | ||||
|  public: | ||||
|   using ContainerBase::ContainerBase; | ||||
|  | ||||
|   Element Render() override { | ||||
|   Element OnRender() override { | ||||
|     Elements elements; | ||||
|     elements.reserve(children_.size()); | ||||
|     for (auto& it : children_) { | ||||
| @@ -182,7 +182,7 @@ class HorizontalContainer : public ContainerBase { | ||||
|  public: | ||||
|   using ContainerBase::ContainerBase; | ||||
|  | ||||
|   Element Render() override { | ||||
|   Element OnRender() override { | ||||
|     Elements elements; | ||||
|     elements.reserve(children_.size()); | ||||
|     for (auto& it : children_) { | ||||
| @@ -218,7 +218,7 @@ class TabContainer : public ContainerBase { | ||||
|  public: | ||||
|   using ContainerBase::ContainerBase; | ||||
|  | ||||
|   Element Render() override { | ||||
|   Element OnRender() override { | ||||
|     const Component active_child = ActiveChild(); | ||||
|     if (active_child) { | ||||
|       return active_child->Render(); | ||||
| @@ -244,7 +244,7 @@ class StackedContainer : public ContainerBase { | ||||
|       : ContainerBase(std::move(children), nullptr) {} | ||||
|  | ||||
|  private: | ||||
|   Element Render() final { | ||||
|   Element OnRender() final { | ||||
|     Elements elements; | ||||
|     for (auto& child : children_) { | ||||
|       elements.push_back(child->Render()); | ||||
| @@ -334,7 +334,7 @@ Component Vertical(Components children) { | ||||
| ///   children_2, | ||||
| ///   children_3, | ||||
| ///   children_4, | ||||
| /// }); | ||||
| /// }, &selected_children); | ||||
| /// ``` | ||||
| Component Vertical(Components children, int* selector) { | ||||
|   return std::make_shared<VerticalContainer>(std::move(children), selector); | ||||
| @@ -355,7 +355,7 @@ Component Vertical(Components children, int* selector) { | ||||
| ///   children_2, | ||||
| ///   children_3, | ||||
| ///   children_4, | ||||
| /// }, &selected_children); | ||||
| /// }); | ||||
| /// ``` | ||||
| Component Horizontal(Components children) { | ||||
|   return Horizontal(std::move(children), nullptr); | ||||
|   | ||||
| @@ -44,12 +44,12 @@ Component Dropdown(DropdownOption option) { | ||||
|       })); | ||||
|     } | ||||
|  | ||||
|     Element Render() override { | ||||
|       radiobox.selected = | ||||
|     Element OnRender() override { | ||||
|       selected_ = | ||||
|           util::clamp(radiobox.selected(), 0, int(radiobox.entries.size()) - 1); | ||||
|       selected_ = util::clamp(selected_(), 0, int(radiobox.entries.size()) - 1); | ||||
|  | ||||
|       if (selected_() >= 0) { | ||||
|       if (selected_() >= 0 && selected_() < int(radiobox.entries.size())) { | ||||
|         title_ = radiobox.entries[selected_()]; | ||||
|       } | ||||
|  | ||||
| @@ -70,10 +70,13 @@ Component Dropdown(DropdownOption option) { | ||||
|       // Auto-close the dropdown when the user selects an item, even if the item | ||||
|       // it the same as the previous one. | ||||
|       if (open_old && open_()) { | ||||
|         const bool should_close = (selected_() != selected_old) ||     // | ||||
|                                   (event == Event::Return) ||          // | ||||
|                                   (event == Event::Character(' ')) ||  // | ||||
|                                   (event == Event::Escape);            // | ||||
|         const bool should_close = | ||||
|             (selected_() != selected_old) ||     // | ||||
|             (event == Event::Return) ||          // | ||||
|             (event == Event::Character(' ')) ||  // | ||||
|             (event == Event::Escape) ||          // | ||||
|             (event.is_mouse() && event.mouse().button == Mouse::Left && | ||||
|              event.mouse().motion == Mouse::Pressed); | ||||
|  | ||||
|         if (should_close) { | ||||
|           checkbox_->TakeFocus(); | ||||
|   | ||||
| @@ -49,8 +49,8 @@ Component Hoverable(Component component, bool* hover) { | ||||
|     } | ||||
|  | ||||
|    private: | ||||
|     Element Render() override { | ||||
|       return ComponentBase::Render() | reflect(box_); | ||||
|     Element OnRender() override { | ||||
|       return ComponentBase::OnRender() | reflect(box_); | ||||
|     } | ||||
|  | ||||
|     bool OnEvent(Event event) override { | ||||
| @@ -98,8 +98,8 @@ Component Hoverable(Component component, | ||||
|     } | ||||
|  | ||||
|    private: | ||||
|     Element Render() override { | ||||
|       return ComponentBase::Render() | reflect(box_); | ||||
|     Element OnRender() override { | ||||
|       return ComponentBase::OnRender() | reflect(box_); | ||||
|     } | ||||
|  | ||||
|     bool OnEvent(Event event) override { | ||||
|   | ||||
| @@ -96,9 +96,9 @@ class InputBase : public ComponentBase, public InputOption { | ||||
|  | ||||
|  private: | ||||
|   // Component implementation: | ||||
|   Element Render() override { | ||||
|   Element OnRender() override { | ||||
|     const bool is_focused = Focused(); | ||||
|     const auto focused = (!is_focused && !hovered_) ? select | ||||
|     const auto focused = (!is_focused && !hovered_) ? nothing | ||||
|                          : insert()                 ? focusCursorBarBlinking | ||||
|                                                     : focusCursorBlockBlinking; | ||||
|  | ||||
| @@ -108,15 +108,12 @@ class InputBase : public ComponentBase, public InputOption { | ||||
|     // placeholder. | ||||
|     if (content->empty()) { | ||||
|       auto element = text(placeholder()) | xflex | frame; | ||||
|       if (is_focused) { | ||||
|         element |= focus; | ||||
|       } | ||||
|  | ||||
|       return transform_func({ | ||||
|                  std::move(element), hovered_, is_focused, | ||||
|                  true  // placeholder | ||||
|              }) | | ||||
|              reflect(box_); | ||||
|              focus | reflect(box_); | ||||
|     } | ||||
|  | ||||
|     Elements elements; | ||||
| @@ -176,7 +173,7 @@ class InputBase : public ComponentBase, public InputOption { | ||||
|       elements.push_back(element); | ||||
|     } | ||||
|  | ||||
|     auto element = vbox(std::move(elements)) | frame; | ||||
|     auto element = vbox(std::move(elements), cursor_line) | frame; | ||||
|     return transform_func({ | ||||
|                std::move(element), hovered_, is_focused, | ||||
|                false  // placeholder | ||||
|   | ||||
| @@ -24,8 +24,8 @@ Component Maybe(Component child, std::function<bool()> show) { | ||||
|     explicit Impl(std::function<bool()> show) : show_(std::move(show)) {} | ||||
|  | ||||
|    private: | ||||
|     Element Render() override { | ||||
|       return show_() ? ComponentBase::Render() : std::make_unique<Node>(); | ||||
|     Element OnRender() override { | ||||
|       return show_() ? ComponentBase::OnRender() : std::make_unique<Node>(); | ||||
|     } | ||||
|     bool Focusable() const override { | ||||
|       return show_() && ComponentBase::Focusable(); | ||||
|   | ||||
| @@ -105,7 +105,7 @@ class MenuBase : public ComponentBase, public MenuOption { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Element Render() override { | ||||
|   Element OnRender() override { | ||||
|     Clamp(); | ||||
|     UpdateAnimationTarget(); | ||||
|  | ||||
| @@ -126,16 +126,15 @@ class MenuBase : public ComponentBase, public MenuOption { | ||||
|           entries[i], false, is_selected, is_focused, i, | ||||
|       }; | ||||
|  | ||||
|       auto focus_management = (selected_focus_ != i) ? nothing | ||||
|                               : is_menu_focused      ? focus | ||||
|                                                      : select; | ||||
|  | ||||
|       const Element element = | ||||
|           (entries_option.transform ? entries_option.transform | ||||
|                                     : DefaultOptionTransform)  // | ||||
|       Element element = (entries_option.transform ? entries_option.transform | ||||
|                                                   : DefaultOptionTransform)  // | ||||
|           (state); | ||||
|       elements.push_back(element | AnimatedColorStyle(i) | reflect(boxes_[i]) | | ||||
|                          focus_management); | ||||
|       if (selected_focus_ == i) { | ||||
|         element |= focus; | ||||
|       } | ||||
|       element |= AnimatedColorStyle(i); | ||||
|       element |= reflect(boxes_[i]); | ||||
|       elements.push_back(element); | ||||
|     } | ||||
|     if (elements_postfix) { | ||||
|       elements.push_back(elements_postfix()); | ||||
| @@ -145,8 +144,9 @@ class MenuBase : public ComponentBase, public MenuOption { | ||||
|       std::reverse(elements.begin(), elements.end()); | ||||
|     } | ||||
|  | ||||
|     const Element bar = | ||||
|         IsHorizontal() ? hbox(std::move(elements)) : vbox(std::move(elements)); | ||||
|     const Element bar = IsHorizontal() | ||||
|                             ? hbox(std::move(elements), selected_focus_) | ||||
|                             : vbox(std::move(elements), selected_focus_); | ||||
|  | ||||
|     if (!underline.enabled) { | ||||
|       return bar | reflect(box_); | ||||
| @@ -618,20 +618,22 @@ Component MenuEntry(MenuEntryOption option) { | ||||
|         : MenuEntryOption(std::move(option)) {} | ||||
|  | ||||
|    private: | ||||
|     Element Render() override { | ||||
|       const bool focused = Focused(); | ||||
|     Element OnRender() override { | ||||
|       const bool is_focused = Focused(); | ||||
|       UpdateAnimationTarget(); | ||||
|  | ||||
|       const EntryState state{ | ||||
|           label(), false, hovered_, focused, Index(), | ||||
|           label(), false, hovered_, is_focused, Index(), | ||||
|       }; | ||||
|  | ||||
|       const Element element = | ||||
|           (transform ? transform : DefaultOptionTransform)  // | ||||
|       Element element = (transform ? transform : DefaultOptionTransform)  // | ||||
|           (state); | ||||
|  | ||||
|       auto focus_management = focused ? select : nothing; | ||||
|       return element | AnimatedColorStyle() | focus_management | reflect(box_); | ||||
|       if (is_focused) { | ||||
|         element |= focus; | ||||
|       } | ||||
|  | ||||
|       return element | AnimatedColorStyle() | reflect(box_); | ||||
|     } | ||||
|  | ||||
|     void UpdateAnimationTarget() { | ||||
|   | ||||
| @@ -26,7 +26,7 @@ Component Modal(Component main, Component modal, const bool* show_modal) { | ||||
|     } | ||||
|  | ||||
|    private: | ||||
|     Element Render() override { | ||||
|     Element OnRender() override { | ||||
|       selector_ = *show_modal_; | ||||
|       auto document = main_->Render(); | ||||
|       if (*show_modal_) { | ||||
|   | ||||
| @@ -28,7 +28,7 @@ class RadioboxBase : public ComponentBase, public RadioboxOption { | ||||
|       : RadioboxOption(option) {} | ||||
|  | ||||
|  private: | ||||
|   Element Render() override { | ||||
|   Element OnRender() override { | ||||
|     Clamp(); | ||||
|     Elements elements; | ||||
|     const bool is_menu_focused = Focused(); | ||||
| @@ -36,18 +36,17 @@ class RadioboxBase : public ComponentBase, public RadioboxOption { | ||||
|     for (int i = 0; i < size(); ++i) { | ||||
|       const bool is_focused = (focused_entry() == i) && is_menu_focused; | ||||
|       const bool is_selected = (hovered_ == i); | ||||
|       auto focus_management = !is_selected      ? nothing | ||||
|                               : is_menu_focused ? focus | ||||
|                                                 : select; | ||||
|       auto state = EntryState{ | ||||
|           entries[i], selected() == i, is_selected, is_focused, i, | ||||
|       }; | ||||
|       auto element = | ||||
|           (transform ? transform : RadioboxOption::Simple().transform)(state); | ||||
|  | ||||
|       elements.push_back(element | focus_management | reflect(boxes_[i])); | ||||
|       if (is_selected) { | ||||
|         element |= focus; | ||||
|       } | ||||
|       elements.push_back(element | reflect(boxes_[i])); | ||||
|     } | ||||
|     return vbox(std::move(elements)) | reflect(box_); | ||||
|     return vbox(std::move(elements), hovered_) | reflect(box_); | ||||
|   } | ||||
|  | ||||
|   // NOLINTNEXTLINE(readability-function-cognitive-complexity) | ||||
|   | ||||
| @@ -31,7 +31,7 @@ Component Renderer(std::function<Element()> render) { | ||||
|    public: | ||||
|     explicit Impl(std::function<Element()> render) | ||||
|         : render_(std::move(render)) {} | ||||
|     Element Render() override { return render_(); } | ||||
|     Element OnRender() override { return render_(); } | ||||
|     std::function<Element()> render_; | ||||
|   }; | ||||
|  | ||||
| @@ -88,7 +88,7 @@ Component Renderer(std::function<Element(bool)> render) { | ||||
|         : render_(std::move(render)) {} | ||||
|  | ||||
|    private: | ||||
|     Element Render() override { return render_(Focused()) | reflect(box_); } | ||||
|     Element OnRender() override { return render_(Focused()) | reflect(box_); } | ||||
|     bool Focusable() const override { return true; } | ||||
|     bool OnEvent(Event event) override { | ||||
|       if (event.is_mouse() && box_.Contain(event.mouse().x, event.mouse().y)) { | ||||
|   | ||||
| @@ -94,7 +94,7 @@ class ResizableSplitBase : public ComponentBase { | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   Element Render() final { | ||||
|   Element OnRender() final { | ||||
|     switch (options_->direction()) { | ||||
|       case Direction::Left: | ||||
|         return RenderLeft(); | ||||
|   | ||||
| @@ -39,7 +39,7 @@ class SliderBase : public SliderOption<T>, public ComponentBase { | ||||
|  public: | ||||
|   explicit SliderBase(SliderOption<T> options) : SliderOption<T>(options) {} | ||||
|  | ||||
|   Element Render() override { | ||||
|   Element OnRender() override { | ||||
|     auto gauge_color = | ||||
|         Focused() ? color(this->color_active) : color(this->color_inactive); | ||||
|     const float percent = | ||||
| @@ -134,53 +134,52 @@ class SliderBase : public SliderOption<T>, public ComponentBase { | ||||
|     return ComponentBase::OnEvent(event); | ||||
|   } | ||||
|  | ||||
|   bool OnCapturedMouseEvent(Event event) { | ||||
|     if (event.mouse().motion == Mouse::Released) { | ||||
|       captured_mouse_ = nullptr; | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     T old_value = this->value(); | ||||
|     switch (this->direction) { | ||||
|       case Direction::Right: { | ||||
|         this->value() = this->min() + (event.mouse().x - gauge_box_.x_min) * | ||||
|                                           (this->max() - this->min()) / | ||||
|                                           (gauge_box_.x_max - gauge_box_.x_min); | ||||
|  | ||||
|         break; | ||||
|       } | ||||
|       case Direction::Left: { | ||||
|         this->value() = this->max() - (event.mouse().x - gauge_box_.x_min) * | ||||
|                                           (this->max() - this->min()) / | ||||
|                                           (gauge_box_.x_max - gauge_box_.x_min); | ||||
|         break; | ||||
|       } | ||||
|       case Direction::Down: { | ||||
|         this->value() = this->min() + (event.mouse().y - gauge_box_.y_min) * | ||||
|                                           (this->max() - this->min()) / | ||||
|                                           (gauge_box_.y_max - gauge_box_.y_min); | ||||
|         break; | ||||
|       } | ||||
|       case Direction::Up: { | ||||
|         this->value() = this->max() - (event.mouse().y - gauge_box_.y_min) * | ||||
|                                           (this->max() - this->min()) / | ||||
|                                           (gauge_box_.y_max - gauge_box_.y_min); | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     this->value() = std::max(this->min(), std::min(this->max(), this->value())); | ||||
|  | ||||
|     if (old_value != this->value() && this->on_change) { | ||||
|       this->on_change(); | ||||
|     } | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   bool OnMouseEvent(Event event) { | ||||
|     if (captured_mouse_) { | ||||
|       if (event.mouse().motion == Mouse::Released) { | ||||
|         captured_mouse_ = nullptr; | ||||
|         return true; | ||||
|       } | ||||
|  | ||||
|       T old_value = this->value(); | ||||
|       switch (this->direction) { | ||||
|         case Direction::Right: { | ||||
|           this->value() = | ||||
|               this->min() + (event.mouse().x - gauge_box_.x_min) * | ||||
|                                 (this->max() - this->min()) / | ||||
|                                 (gauge_box_.x_max - gauge_box_.x_min); | ||||
|  | ||||
|           break; | ||||
|         } | ||||
|         case Direction::Left: { | ||||
|           this->value() = | ||||
|               this->max() - (event.mouse().x - gauge_box_.x_min) * | ||||
|                                 (this->max() - this->min()) / | ||||
|                                 (gauge_box_.x_max - gauge_box_.x_min); | ||||
|           break; | ||||
|         } | ||||
|         case Direction::Down: { | ||||
|           this->value() = | ||||
|               this->min() + (event.mouse().y - gauge_box_.y_min) * | ||||
|                                 (this->max() - this->min()) / | ||||
|                                 (gauge_box_.y_max - gauge_box_.y_min); | ||||
|           break; | ||||
|         } | ||||
|         case Direction::Up: { | ||||
|           this->value() = | ||||
|               this->max() - (event.mouse().y - gauge_box_.y_min) * | ||||
|                                 (this->max() - this->min()) / | ||||
|                                 (gauge_box_.y_max - gauge_box_.y_min); | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       this->value() = | ||||
|           std::max(this->min(), std::min(this->max(), this->value())); | ||||
|  | ||||
|       if (old_value != this->value() && this->on_change) { | ||||
|         this->on_change(); | ||||
|       } | ||||
|       return true; | ||||
|       return OnCapturedMouseEvent(event); | ||||
|     } | ||||
|  | ||||
|     if (event.mouse().button != Mouse::Left) { | ||||
| @@ -198,7 +197,7 @@ class SliderBase : public SliderOption<T>, public ComponentBase { | ||||
|  | ||||
|     if (captured_mouse_) { | ||||
|       TakeFocus(); | ||||
|       return true; | ||||
|       return OnCapturedMouseEvent(event); | ||||
|     } | ||||
|  | ||||
|     return false; | ||||
| @@ -242,19 +241,21 @@ class SliderWithLabel : public ComponentBase { | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   Element Render() override { | ||||
|     auto focus_management = Focused() ? focus : Active() ? select : nothing; | ||||
|   Element OnRender() override { | ||||
|     auto gauge_color = (Focused() || mouse_hover_) ? color(Color::White) | ||||
|                                                    : color(Color::GrayDark); | ||||
|     return hbox({ | ||||
|                text(label_()) | dim | vcenter, | ||||
|                hbox({ | ||||
|                    text("["), | ||||
|                    ComponentBase::Render() | underlined, | ||||
|                    text("]"), | ||||
|                }) | xflex, | ||||
|            }) | | ||||
|            gauge_color | xflex | reflect(box_) | focus_management; | ||||
|     auto element = hbox({ | ||||
|                        text(label_()) | dim | vcenter, | ||||
|                        hbox({ | ||||
|                            text("["), | ||||
|                            ComponentBase::Render() | underlined, | ||||
|                            text("]"), | ||||
|                        }) | xflex, | ||||
|                    }) | | ||||
|                    gauge_color | xflex | reflect(box_); | ||||
|  | ||||
|     element |= focus; | ||||
|     return element; | ||||
|   } | ||||
|  | ||||
|   ConstStringRef label_; | ||||
|   | ||||
| @@ -60,17 +60,17 @@ TEST(SliderTest, Right) { | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_EQ(updated, 0); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(3, 0))); | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_EQ(updated, 0); | ||||
|   EXPECT_EQ(value, 30); | ||||
|   EXPECT_EQ(updated, 1); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(9, 0))); | ||||
|   EXPECT_EQ(value, 90); | ||||
|   EXPECT_EQ(updated, 1); | ||||
|   EXPECT_EQ(updated, 2); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(9, 2))); | ||||
|   EXPECT_EQ(value, 90); | ||||
|   EXPECT_EQ(updated, 1); | ||||
|   EXPECT_EQ(updated, 2); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(5, 2))); | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_EQ(updated, 2); | ||||
|   EXPECT_EQ(updated, 3); | ||||
|   EXPECT_TRUE(slider->OnEvent(MouseReleased(5, 2))); | ||||
|   EXPECT_FALSE(slider->OnEvent(MousePressed(5, 2))); | ||||
|   EXPECT_EQ(value, 50); | ||||
| @@ -92,17 +92,17 @@ TEST(SliderTest, Left) { | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_EQ(updated, 0); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(3, 0))); | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_EQ(updated, 0); | ||||
|   EXPECT_EQ(value, 70); | ||||
|   EXPECT_EQ(updated, 1); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(9, 0))); | ||||
|   EXPECT_EQ(value, 10); | ||||
|   EXPECT_EQ(updated, 1); | ||||
|   EXPECT_EQ(updated, 2); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(9, 2))); | ||||
|   EXPECT_EQ(value, 10); | ||||
|   EXPECT_EQ(updated, 1); | ||||
|   EXPECT_EQ(updated, 2); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(5, 2))); | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_EQ(updated, 2); | ||||
|   EXPECT_EQ(updated, 3); | ||||
|   EXPECT_TRUE(slider->OnEvent(MouseReleased(5, 2))); | ||||
|   EXPECT_FALSE(slider->OnEvent(MousePressed(5, 2))); | ||||
|   EXPECT_EQ(value, 50); | ||||
| @@ -124,21 +124,21 @@ TEST(SliderTest, Down) { | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_EQ(updated, 0); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(0, 3))); | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_EQ(updated, 0); | ||||
|   EXPECT_EQ(value, 30); | ||||
|   EXPECT_EQ(updated, 1); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(0, 9))); | ||||
|   EXPECT_EQ(value, 90); | ||||
|   EXPECT_EQ(updated, 1); | ||||
|   EXPECT_EQ(updated, 2); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(2, 9))); | ||||
|   EXPECT_EQ(value, 90); | ||||
|   EXPECT_EQ(updated, 1); | ||||
|   EXPECT_EQ(updated, 2); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(2, 5))); | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_EQ(updated, 2); | ||||
|   EXPECT_EQ(updated, 3); | ||||
|   EXPECT_TRUE(slider->OnEvent(MouseReleased(2, 5))); | ||||
|   EXPECT_FALSE(slider->OnEvent(MousePressed(2, 5))); | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_EQ(updated, 2); | ||||
|   EXPECT_EQ(updated, 3); | ||||
| } | ||||
|  | ||||
| TEST(SliderTest, Up) { | ||||
| @@ -157,17 +157,17 @@ TEST(SliderTest, Up) { | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_EQ(updated, 0); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(0, 3))); | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_EQ(updated, 0); | ||||
|   EXPECT_EQ(value, 70); | ||||
|   EXPECT_EQ(updated, 1); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(0, 9))); | ||||
|   EXPECT_EQ(value, 10); | ||||
|   EXPECT_EQ(updated, 1); | ||||
|   EXPECT_EQ(updated, 2); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(2, 9))); | ||||
|   EXPECT_EQ(value, 10); | ||||
|   EXPECT_EQ(updated, 1); | ||||
|   EXPECT_EQ(updated, 2); | ||||
|   EXPECT_TRUE(slider->OnEvent(MousePressed(2, 5))); | ||||
|   EXPECT_EQ(value, 50); | ||||
|   EXPECT_EQ(updated, 2); | ||||
|   EXPECT_EQ(updated, 3); | ||||
|   EXPECT_TRUE(slider->OnEvent(MouseReleased(2, 5))); | ||||
|   EXPECT_FALSE(slider->OnEvent(MousePressed(2, 5))); | ||||
|   EXPECT_EQ(value, 50); | ||||
|   | ||||
| @@ -124,7 +124,7 @@ class WindowImpl : public ComponentBase, public WindowOptions { | ||||
|   } | ||||
|  | ||||
|  private: | ||||
|   Element Render() final { | ||||
|   Element OnRender() final { | ||||
|     auto element = ComponentBase::Render(); | ||||
|  | ||||
|     const bool captureable = | ||||
|   | ||||
| @@ -54,10 +54,10 @@ class Border : public Node { | ||||
|       requirement_.min_x = | ||||
|           std::max(requirement_.min_x, children_[1]->requirement().min_x + 2); | ||||
|     } | ||||
|     requirement_.selected_box.x_min++; | ||||
|     requirement_.selected_box.x_max++; | ||||
|     requirement_.selected_box.y_min++; | ||||
|     requirement_.selected_box.y_max++; | ||||
|     requirement_.focused.box.x_min++; | ||||
|     requirement_.focused.box.x_max++; | ||||
|     requirement_.focused.box.y_min++; | ||||
|     requirement_.focused.box.y_max++; | ||||
|   } | ||||
|  | ||||
|   void SetBox(Box box) override { | ||||
| @@ -65,7 +65,8 @@ class Border : public Node { | ||||
|     if (children_.size() == 2) { | ||||
|       Box title_box; | ||||
|       title_box.x_min = box.x_min + 1; | ||||
|       title_box.x_max = std::min(box.x_max - 1, box.x_min + children_[1]->requirement().min_x); | ||||
|       title_box.x_max = std::min(box.x_max - 1, | ||||
|                                  box.x_min + children_[1]->requirement().min_x); | ||||
|       title_box.y_min = box.y_min; | ||||
|       title_box.y_max = box.y_min; | ||||
|       children_[1]->SetBox(title_box); | ||||
| @@ -145,10 +146,8 @@ class BorderPixel : public Node { | ||||
|       requirement_.min_x = | ||||
|           std::max(requirement_.min_x, children_[1]->requirement().min_x + 2); | ||||
|     } | ||||
|     requirement_.selected_box.x_min++; | ||||
|     requirement_.selected_box.x_max++; | ||||
|     requirement_.selected_box.y_min++; | ||||
|     requirement_.selected_box.y_max++; | ||||
|  | ||||
|     requirement_.focused.box.Shift(1, 1); | ||||
|   } | ||||
|  | ||||
|   void SetBox(Box box) override { | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| #define FTXUI_DOM_BOX_HELPER_HPP | ||||
|  | ||||
| #include <vector> | ||||
| #include "ftxui/dom/requirement.hpp" | ||||
|  | ||||
| namespace ftxui::box_helper { | ||||
|  | ||||
| @@ -19,7 +20,6 @@ struct Element { | ||||
| }; | ||||
|  | ||||
| void Compute(std::vector<Element>* elements, int target_size); | ||||
|  | ||||
| }  // namespace ftxui::box_helper | ||||
|  | ||||
| #endif /* end of include guard: FTXUI_DOM_BOX_HELPER_HPP */ | ||||
|   | ||||
| @@ -21,24 +21,21 @@ class DBox : public Node { | ||||
|   explicit DBox(Elements children) : Node(std::move(children)) {} | ||||
|  | ||||
|   void ComputeRequirement() override { | ||||
|     requirement_.min_x = 0; | ||||
|     requirement_.min_y = 0; | ||||
|     requirement_.flex_grow_x = 0; | ||||
|     requirement_.flex_grow_y = 0; | ||||
|     requirement_.flex_shrink_x = 0; | ||||
|     requirement_.flex_shrink_y = 0; | ||||
|     requirement_.selection = Requirement::NORMAL; | ||||
|     requirement_ = Requirement{}; | ||||
|     for (auto& child : children_) { | ||||
|  | ||||
|       child->ComputeRequirement(); | ||||
|  | ||||
|       // Propagate the focused requirement. | ||||
|       if (requirement_.focused.Prefer(*child->requirement().focused)) { | ||||
|         requirement_.focused = child->requirement().focused; | ||||
|       } | ||||
|  | ||||
|       // Extend the min_x and min_y to contain all the children | ||||
|       requirement_.min_x = | ||||
|           std::max(requirement_.min_x, child->requirement().min_x); | ||||
|       requirement_.min_y = | ||||
|           std::max(requirement_.min_y, child->requirement().min_y); | ||||
|  | ||||
|       if (requirement_.selection < child->requirement().selection) { | ||||
|         requirement_.selection = child->requirement().selection; | ||||
|         requirement_.selected_box = child->requirement().selected_box; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -89,6 +89,7 @@ class Flexbox : public Node { | ||||
|   } | ||||
|  | ||||
|   void ComputeRequirement() override { | ||||
|     requirement_ = Requirement{}; | ||||
|     for (auto& child : children_) { | ||||
|       child->ComputeRequirement(); | ||||
|     } | ||||
| @@ -103,12 +104,6 @@ class Flexbox : public Node { | ||||
|     } | ||||
|     Layout(global, true); | ||||
|  | ||||
|     // Reset: | ||||
|     requirement_.selection = Requirement::Selection::NORMAL; | ||||
|     requirement_.selected_box = Box(); | ||||
|     requirement_.min_x = 0; | ||||
|     requirement_.min_y = 0; | ||||
|  | ||||
|     if (global.blocks.empty()) { | ||||
|       return; | ||||
|     } | ||||
| @@ -130,19 +125,14 @@ class Flexbox : public Node { | ||||
|  | ||||
|     // Find the selection: | ||||
|     for (size_t i = 0; i < children_.size(); ++i) { | ||||
|       if (requirement_.selection >= children_[i]->requirement().selection) { | ||||
|         continue; | ||||
|       if (requirement_.focused.Prefer(children_[i]->requirement().focused)) { | ||||
|         requirement_.focused = children_[i]->requirement().focused; | ||||
|         // Shift |focused.box| according to its position inside this component: | ||||
|         auto& b = global.blocks[i]; | ||||
|         requirement_.focused.box.Shift(b.x, b.y); | ||||
|         requirement_.focused.box = | ||||
|             Box::Intersection(requirement_.focused.box, box); | ||||
|       } | ||||
|       requirement_.selection = children_[i]->requirement().selection; | ||||
|       Box selected_box = children_[i]->requirement().selected_box; | ||||
|  | ||||
|       // Shift |selected_box| according to its position inside this component: | ||||
|       auto& b = global.blocks[i]; | ||||
|       selected_box.x_min += b.x; | ||||
|       selected_box.y_min += b.y; | ||||
|       selected_box.x_max += b.x; | ||||
|       selected_box.y_max += b.y; | ||||
|       requirement_.selected_box = Box::Intersection(selected_box, box); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -36,13 +36,12 @@ Decorator focusPositionRelative(float x, float y) { | ||||
|  | ||||
|     void ComputeRequirement() override { | ||||
|       NodeDecorator::ComputeRequirement(); | ||||
|       requirement_.selection = Requirement::Selection::NORMAL; | ||||
|  | ||||
|       Box& box = requirement_.selected_box; | ||||
|       box.x_min = int(float(requirement_.min_x) * x_); | ||||
|       box.y_min = int(float(requirement_.min_y) * y_); | ||||
|       box.x_max = int(float(requirement_.min_x) * x_); | ||||
|       box.y_max = int(float(requirement_.min_y) * y_); | ||||
|       requirement_.focused.enabled = false; | ||||
|       requirement_.focused.node = this; | ||||
|       requirement_.focused.box.x_min = int(float(requirement_.min_x) * x_); | ||||
|       requirement_.focused.box.y_min = int(float(requirement_.min_y) * y_); | ||||
|       requirement_.focused.box.x_max = int(float(requirement_.min_x) * x_); | ||||
|       requirement_.focused.box.y_max = int(float(requirement_.min_y) * y_); | ||||
|     } | ||||
|  | ||||
|    private: | ||||
| @@ -75,9 +74,9 @@ Decorator focusPosition(int x, int y) { | ||||
|  | ||||
|     void ComputeRequirement() override { | ||||
|       NodeDecorator::ComputeRequirement(); | ||||
|       requirement_.selection = Requirement::Selection::NORMAL; | ||||
|       requirement_.focused.enabled = false; | ||||
|  | ||||
|       Box& box = requirement_.selected_box; | ||||
|       Box& box = requirement_.focused.box; | ||||
|       box.x_min = x_; | ||||
|       box.y_min = y_; | ||||
|       box.x_max = x_; | ||||
|   | ||||
| @@ -6,28 +6,28 @@ | ||||
| #include <utility>    // for move | ||||
|  | ||||
| #include "ftxui/dom/elements.hpp"  // for Element, unpack, Elements, focus, frame, select, xframe, yframe | ||||
| #include "ftxui/dom/node.hpp"  // for Node, Elements | ||||
| #include "ftxui/dom/requirement.hpp"  // for Requirement, Requirement::FOCUSED, Requirement::SELECTED | ||||
| #include "ftxui/screen/box.hpp"      // for Box | ||||
| #include "ftxui/screen/screen.hpp"   // for Screen, Screen::Cursor | ||||
| #include "ftxui/util/autoreset.hpp"  // for AutoReset | ||||
| #include "ftxui/dom/node.hpp"         // for Node, Elements | ||||
| #include "ftxui/dom/requirement.hpp"  // for Requirement | ||||
| #include "ftxui/screen/box.hpp"       // for Box | ||||
| #include "ftxui/screen/screen.hpp"    // for Screen, Screen::Cursor | ||||
| #include "ftxui/util/autoreset.hpp"   // for AutoReset | ||||
|  | ||||
| namespace ftxui { | ||||
|  | ||||
| namespace { | ||||
| class Select : public Node { | ||||
| class Focus : public Node { | ||||
|  public: | ||||
|   explicit Select(Elements children) : Node(std::move(children)) {} | ||||
|   explicit Focus(Elements children) : Node(std::move(children)) {} | ||||
|  | ||||
|   void ComputeRequirement() override { | ||||
|     Node::ComputeRequirement(); | ||||
|     requirement_ = children_[0]->requirement(); | ||||
|     auto& selected_box = requirement_.selected_box; | ||||
|     selected_box.x_min = 0; | ||||
|     selected_box.y_min = 0; | ||||
|     selected_box.x_max = requirement_.min_x - 1; | ||||
|     selected_box.y_max = requirement_.min_y - 1; | ||||
|     requirement_.selection = Requirement::SELECTED; | ||||
|     requirement_.focused.enabled = true; | ||||
|     requirement_.focused.node = this; | ||||
|     requirement_.focused.box.x_min = 0; | ||||
|     requirement_.focused.box.y_min = 0; | ||||
|     requirement_.focused.box.x_max = requirement_.min_x - 1; | ||||
|     requirement_.focused.box.y_max = requirement_.min_y - 1; | ||||
|   } | ||||
|  | ||||
|   void SetBox(Box box) override { | ||||
| @@ -36,65 +36,21 @@ class Select : public Node { | ||||
|   } | ||||
| }; | ||||
|  | ||||
| class Focus : public Select { | ||||
|  public: | ||||
|   using Select::Select; | ||||
|  | ||||
|   void ComputeRequirement() override { | ||||
|     Select::ComputeRequirement(); | ||||
|     requirement_.selection = Requirement::FOCUSED; | ||||
|   } | ||||
|  | ||||
|   void Render(Screen& screen) override { | ||||
|     Select::Render(screen); | ||||
|  | ||||
|     // Setting the cursor to the right position allow folks using CJK (China, | ||||
|     // Japanese, Korean, ...) characters to see their [input method editor] | ||||
|     // displayed at the right location. See [issue]. | ||||
|     // | ||||
|     // [input method editor]: | ||||
|     // https://en.wikipedia.org/wiki/Input_method | ||||
|     // | ||||
|     // [issue]: | ||||
|     // https://github.com/ArthurSonzogni/FTXUI/issues/2#issuecomment-505282355 | ||||
|     // | ||||
|     // Unfortunately, Microsoft terminal do not handle properly hidding the | ||||
|     // cursor. Instead the character under the cursor is hidden, which is a big | ||||
|     // problem. As a result, we can't enable setting cursor to the right | ||||
|     // location. It will be displayed at the bottom right corner. | ||||
|     // See: | ||||
|     // https://github.com/microsoft/terminal/issues/1203 | ||||
|     // https://github.com/microsoft/terminal/issues/3093 | ||||
| #if !defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK) | ||||
|     screen.SetCursor(Screen::Cursor{ | ||||
|         box_.x_min, | ||||
|         box_.y_min, | ||||
|         Screen::Cursor::Shape::Hidden, | ||||
|     }); | ||||
| #endif | ||||
|   } | ||||
| }; | ||||
|  | ||||
| class Frame : public Node { | ||||
|  public: | ||||
|   Frame(Elements children, bool x_frame, bool y_frame) | ||||
|       : Node(std::move(children)), x_frame_(x_frame), y_frame_(y_frame) {} | ||||
|  | ||||
|   void ComputeRequirement() override { | ||||
|     Node::ComputeRequirement(); | ||||
|     requirement_ = children_[0]->requirement(); | ||||
|   } | ||||
|  | ||||
|   void SetBox(Box box) override { | ||||
|     Node::SetBox(box); | ||||
|     auto& selected_box = requirement_.selected_box; | ||||
|     auto& focused_box = requirement_.focused.box; | ||||
|     Box children_box = box; | ||||
|  | ||||
|     if (x_frame_) { | ||||
|       const int external_dimx = box.x_max - box.x_min; | ||||
|       const int internal_dimx = std::max(requirement_.min_x, external_dimx); | ||||
|       const int focused_dimx = selected_box.x_max - selected_box.x_min; | ||||
|       int dx = selected_box.x_min - external_dimx / 2 + focused_dimx / 2; | ||||
|       const int focused_dimx = focused_box.x_max - focused_box.x_min; | ||||
|       int dx = focused_box.x_min - external_dimx / 2 + focused_dimx / 2; | ||||
|       dx = std::max(0, std::min(internal_dimx - external_dimx - 1, dx)); | ||||
|       children_box.x_min = box.x_min - dx; | ||||
|       children_box.x_max = box.x_min + internal_dimx - dx; | ||||
| @@ -103,8 +59,8 @@ class Frame : public Node { | ||||
|     if (y_frame_) { | ||||
|       const int external_dimy = box.y_max - box.y_min; | ||||
|       const int internal_dimy = std::max(requirement_.min_y, external_dimy); | ||||
|       const int focused_dimy = selected_box.y_max - selected_box.y_min; | ||||
|       int dy = selected_box.y_min - external_dimy / 2 + focused_dimy / 2; | ||||
|       const int focused_dimy = focused_box.y_max - focused_box.y_min; | ||||
|       int dy = focused_box.y_min - external_dimy / 2 + focused_dimy / 2; | ||||
|       dy = std::max(0, std::min(internal_dimy - external_dimy - 1, dy)); | ||||
|       children_box.y_min = box.y_min - dy; | ||||
|       children_box.y_max = box.y_min + internal_dimy - dy; | ||||
| @@ -130,33 +86,29 @@ class FocusCursor : public Focus { | ||||
|       : Focus(std::move(children)), shape_(shape) {} | ||||
|  | ||||
|  private: | ||||
|   void Render(Screen& screen) override { | ||||
|     Select::Render(screen);  // NOLINT | ||||
|     screen.SetCursor(Screen::Cursor{ | ||||
|         box_.x_min, | ||||
|         box_.y_min, | ||||
|         shape_, | ||||
|     }); | ||||
|   void ComputeRequirement() override { | ||||
|     Focus::ComputeRequirement();  // NOLINT | ||||
|     requirement_.focused.cursor_shape = shape_; | ||||
|   } | ||||
|   Screen::Cursor::Shape shape_; | ||||
| }; | ||||
|  | ||||
| }  // namespace | ||||
|  | ||||
| /// @brief Set the `child` to be the one selected among its siblings. | ||||
| /// @param child The element to be selected. | ||||
| /// @ingroup dom | ||||
| Element select(Element child) { | ||||
|   return std::make_shared<Select>(unpack(std::move(child))); | ||||
| } | ||||
|  | ||||
| /// @brief Set the `child` to be the one in focus globally. | ||||
| /// @brief Set the `child` to be the one focused among its siblings. | ||||
| /// @param child The element to be focused. | ||||
| /// @ingroup dom | ||||
| Element focus(Element child) { | ||||
|   return std::make_shared<Focus>(unpack(std::move(child))); | ||||
| } | ||||
|  | ||||
| /// @deprecated Use `focus` instead. | ||||
| /// @brief Set the `child` to be the one focused among its siblings. | ||||
| /// @param child The element to be focused. | ||||
| Element select(Element e) { | ||||
|   return focus(std::move(e)); | ||||
| } | ||||
|  | ||||
| /// @brief Allow an element to be displayed inside a 'virtual' area. It size can | ||||
| /// be larger than its container. In this case only a smaller portion is | ||||
| /// displayed. The view is scrollable to make the focused element visible. | ||||
|   | ||||
| @@ -49,13 +49,7 @@ class GridBox : public Node { | ||||
|   } | ||||
|  | ||||
|   void ComputeRequirement() override { | ||||
|     requirement_.min_x = 0; | ||||
|     requirement_.min_y = 0; | ||||
|     requirement_.flex_grow_x = 0; | ||||
|     requirement_.flex_grow_y = 0; | ||||
|     requirement_.flex_shrink_x = 0; | ||||
|     requirement_.flex_shrink_y = 0; | ||||
|  | ||||
|     requirement_ = Requirement{}; | ||||
|     for (auto& line : lines_) { | ||||
|       for (auto& cell : line) { | ||||
|         cell->ComputeRequirement(); | ||||
| @@ -75,19 +69,15 @@ class GridBox : public Node { | ||||
|     requirement_.min_x = Integrate(size_x); | ||||
|     requirement_.min_y = Integrate(size_y); | ||||
|  | ||||
|     // Forward the selected/focused child state: | ||||
|     requirement_.selection = Requirement::NORMAL; | ||||
|     // Forward the focused/focused child state: | ||||
|     for (int x = 0; x < x_size; ++x) { | ||||
|       for (int y = 0; y < y_size; ++y) { | ||||
|         if (requirement_.selection >= lines_[y][x]->requirement().selection) { | ||||
|         if (requirement_.focused.enabled || | ||||
|             !lines_[y][x]->requirement().focused.enabled) { | ||||
|           continue; | ||||
|         } | ||||
|         requirement_.selection = lines_[y][x]->requirement().selection; | ||||
|         requirement_.selected_box = lines_[y][x]->requirement().selected_box; | ||||
|         requirement_.selected_box.x_min += size_x[x]; | ||||
|         requirement_.selected_box.x_max += size_x[x]; | ||||
|         requirement_.selected_box.y_min += size_y[y]; | ||||
|         requirement_.selected_box.y_max += size_y[y]; | ||||
|         requirement_.focused = lines_[y][x]->requirement().focused; | ||||
|         requirement_.focused.box.Shift(size_x[x], size_y[y]); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
| #include <string>     // for allocator, basic_string, string | ||||
| #include <vector>     // for vector | ||||
|  | ||||
| #include "ftxui/dom/elements.hpp"  // for text, operator|, Element, flex, Elements, flex_grow, flex_shrink, vtext, gridbox, vbox, focus, operator|=, border, frame | ||||
| #include "ftxui/dom/elements.hpp"  // for text, operator|, Element, flex, Elements, flex_grow, flex_shrink, vtext, gridbox, vbox, select, operator|=, border, frame | ||||
| #include "ftxui/dom/node.hpp"      // for Render | ||||
| #include "ftxui/screen/screen.hpp"  // for Screen | ||||
|  | ||||
|   | ||||
| @@ -20,22 +20,20 @@ class HBox : public Node { | ||||
|  public: | ||||
|   explicit HBox(Elements children) : Node(std::move(children)) {} | ||||
|  | ||||
|  private: | ||||
|   void ComputeRequirement() override { | ||||
|     requirement_.min_x = 0; | ||||
|     requirement_.min_y = 0; | ||||
|     requirement_.flex_grow_x = 0; | ||||
|     requirement_.flex_grow_y = 0; | ||||
|     requirement_.flex_shrink_x = 0; | ||||
|     requirement_.flex_shrink_y = 0; | ||||
|     requirement_.selection = Requirement::NORMAL; | ||||
|     requirement_ = Requirement{}; | ||||
|  | ||||
|     for (auto& child : children_) { | ||||
|       child->ComputeRequirement(); | ||||
|       if (requirement_.selection < child->requirement().selection) { | ||||
|         requirement_.selection = child->requirement().selection; | ||||
|         requirement_.selected_box = child->requirement().selected_box; | ||||
|         requirement_.selected_box.x_min += requirement_.min_x; | ||||
|         requirement_.selected_box.x_max += requirement_.min_x; | ||||
|  | ||||
|       // Propagate the focused requirement. | ||||
|       if (requirement_.focused.Prefer(child->requirement().focused)) { | ||||
|         requirement_.focused = child->requirement().focused; | ||||
|         requirement_.focused.box.Shift(requirement_.min_x, 0); | ||||
|       } | ||||
|  | ||||
|       // Extend the min_x and min_y to contain all the children | ||||
|       requirement_.min_x += child->requirement().min_x; | ||||
|       requirement_.min_y = | ||||
|           std::max(requirement_.min_y, child->requirement().min_y); | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| #include <iostream> | ||||
| // 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. | ||||
| @@ -7,6 +6,7 @@ | ||||
|  | ||||
| #include "ftxui/dom/node.hpp" | ||||
| #include "ftxui/screen/screen.hpp"  // for Screen | ||||
| #include "ftxui/screen/util.hpp"    // for clamp | ||||
|  | ||||
| namespace ftxui { | ||||
|  | ||||
| @@ -17,9 +17,23 @@ Node::~Node() = default; | ||||
| /// @brief Compute how much space an elements needs. | ||||
| /// @ingroup dom | ||||
| void Node::ComputeRequirement() { | ||||
|   if (children_.empty()) { | ||||
|     return; | ||||
|   } | ||||
|   for (auto& child : children_) { | ||||
|     child->ComputeRequirement(); | ||||
|   } | ||||
|  | ||||
|   // By default, the requirement is the one of the first child. | ||||
|   requirement_ = children_[0]->requirement(); | ||||
|  | ||||
|   // Propagate the focused requirement. | ||||
|   for (size_t i = 1; i < children_.size(); ++i) { | ||||
|     if (!requirement_.focused.enabled && | ||||
|         children_[i]->requirement().focused.enabled) { | ||||
|       requirement_.focused = children_[i]->requirement().focused; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| /// @brief Assign a position and a dimension to an element for drawing. | ||||
| @@ -109,6 +123,42 @@ void Render(Screen& screen, Node* node, Selection& selection) { | ||||
|     node->Select(selection); | ||||
|   } | ||||
|  | ||||
|   // Setting the cursor to the right position allow folks using CJK (China, | ||||
|   // Japanese, Korean, ...) characters to see their [input method editor] | ||||
|   // displayed at the right location. See [issue]. | ||||
|   // | ||||
|   // [input method editor]: | ||||
|   // https://en.wikipedia.org/wiki/Input_method | ||||
|   // | ||||
|   // [issue]: | ||||
|   // https://github.com/ArthurSonzogni/FTXUI/issues/2#issuecomment-505282355 | ||||
|   // | ||||
|   // Unfortunately, Microsoft terminal do not handle properly hiding the | ||||
|   // cursor. Instead the character under the cursor is hidden, which is a big | ||||
|   // problem. As a result, we can't enable setting cursor to the right | ||||
|   // location. It will be displayed at the bottom right corner. | ||||
|   // See: | ||||
|   // https://github.com/microsoft/terminal/issues/1203 | ||||
|   // https://github.com/microsoft/terminal/issues/3093 | ||||
|   if (node->requirement().focused.enabled | ||||
| #if defined(FTXUI_MICROSOFT_TERMINAL_FALLBACK) | ||||
|       || | ||||
|       node->requirement().focused.cursor_shape == Screen::Cursor::Shape::Hidden | ||||
| #endif | ||||
|   ) { | ||||
|     screen.SetCursor(Screen::Cursor{ | ||||
|         node->requirement().focused.node->box_.x_max, | ||||
|         node->requirement().focused.node->box_.y_max, | ||||
|         node->requirement().focused.cursor_shape, | ||||
|     }); | ||||
|   } else { | ||||
|     screen.SetCursor(Screen::Cursor{ | ||||
|         screen.dimx() - 1, | ||||
|         screen.dimy() - 1, | ||||
|         Screen::Cursor::Shape::Hidden, | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   // Step 4: Draw the element. | ||||
|   screen.stencil = box; | ||||
|   node->Render(screen); | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| #include <string>   // for allocator, to_string, string | ||||
| #include <utility>  // for move | ||||
|  | ||||
| #include "ftxui/dom/elements.hpp"  // for operator|, Element, operator|=, text, vbox, Elements, border, focus, frame, vscroll_indicator | ||||
| #include "ftxui/dom/elements.hpp"  // for operator|, Element, operator|=, text, vbox, Elements, border, select, frame, vscroll_indicator | ||||
| #include "ftxui/dom/node.hpp"      // for Render | ||||
| #include "ftxui/screen/color.hpp"  // for Color, Color::Red | ||||
| #include "ftxui/screen/screen.hpp"  // for Screen | ||||
|   | ||||
| @@ -20,22 +20,20 @@ class VBox : public Node { | ||||
|  public: | ||||
|   explicit VBox(Elements children) : Node(std::move(children)) {} | ||||
|  | ||||
|  private: | ||||
|   void ComputeRequirement() override { | ||||
|     requirement_.min_x = 0; | ||||
|     requirement_.min_y = 0; | ||||
|     requirement_.flex_grow_x = 0; | ||||
|     requirement_.flex_grow_y = 0; | ||||
|     requirement_.flex_shrink_x = 0; | ||||
|     requirement_.flex_shrink_y = 0; | ||||
|     requirement_.selection = Requirement::NORMAL; | ||||
|     requirement_ = Requirement{}; | ||||
|  | ||||
|     for (auto& child : children_) { | ||||
|       child->ComputeRequirement(); | ||||
|       if (requirement_.selection < child->requirement().selection) { | ||||
|         requirement_.selection = child->requirement().selection; | ||||
|         requirement_.selected_box = child->requirement().selected_box; | ||||
|         requirement_.selected_box.y_min += requirement_.min_y; | ||||
|         requirement_.selected_box.y_max += requirement_.min_y; | ||||
|  | ||||
|       // Propagate the focused requirement. | ||||
|       if (requirement_.focused.Prefer(child->requirement().focused)) { | ||||
|         requirement_.focused = child->requirement().focused; | ||||
|         requirement_.focused.box.Shift(0, requirement_.min_y); | ||||
|       } | ||||
|  | ||||
|       // Extend the min_x and min_y to contain all the children | ||||
|       requirement_.min_y += child->requirement().min_y; | ||||
|       requirement_.min_x = | ||||
|           std::max(requirement_.min_x, child->requirement().min_x); | ||||
|   | ||||
| @@ -30,6 +30,17 @@ Box Box::Union(Box a, Box b) { | ||||
|   }; | ||||
| } | ||||
|  | ||||
| /// Shift the box by (x,y). | ||||
| /// @param x horizontal shift. | ||||
| /// @param y vertical shift. | ||||
| /// @ingroup screen | ||||
| void Box::Shift(int x, int y) { | ||||
|   x_min += x; | ||||
|   x_max += x; | ||||
|   y_min += y; | ||||
|   y_max += y; | ||||
| } | ||||
|  | ||||
| /// @return whether (x,y) is contained inside the box. | ||||
| /// @ingroup screen | ||||
| bool Box::Contain(int x, int y) const { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Ayaan
					Ayaan