#define NOMINMAX #include #include #include #include // 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( // 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(); } 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(width(), title().size() + 2); height() = std::max(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(std::move(option)); } }; // namespace ftxui // Copyright 2023 Arthur Sonzogni. All rights reserved. // Use of this source code is governed by the MIT license that can be found in // the LICENSE file.