diff --git a/CMakeLists.txt b/CMakeLists.txt index ffa8ca1..1ec81e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,6 +101,7 @@ add_library(component STATIC src/ftxui/component/radiobox.cpp src/ftxui/component/radiobox.cpp src/ftxui/component/renderer.cpp + src/ftxui/component/resizable_split.cpp src/ftxui/component/screen_interactive.cpp src/ftxui/component/slider.cpp src/ftxui/component/terminal_input_parser.cpp diff --git a/examples/component/CMakeLists.txt b/examples/component/CMakeLists.txt index 3f4e5f1..66d9711 100644 --- a/examples/component/CMakeLists.txt +++ b/examples/component/CMakeLists.txt @@ -21,3 +21,4 @@ example(slider_rgb) example(tab_horizontal) example(tab_vertical) example(toggle) +example(resizable_split) diff --git a/examples/component/resizable_split.cpp b/examples/component/resizable_split.cpp new file mode 100644 index 0000000..7a72adf --- /dev/null +++ b/examples/component/resizable_split.cpp @@ -0,0 +1,34 @@ +#include "ftxui/component/component.hpp" // for Slider +#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive + +using namespace ftxui; + +int main(int argc, const char* argv[]) { + auto screen = ScreenInteractive::Fullscreen(); + + auto middle = Renderer([] { return text(L"middle") | center; }); + auto left = Renderer([] { return text(L"Left") | center; }); + auto right = Renderer([] { return text(L"right") | center; }); + auto top = Renderer([] { return text(L"top") | center; }); + auto bottom = Renderer([] { return text(L"bottom") | center; }); + + int left_size = 20; + int right_size = 20; + int top_size = 10; + int bottom_size = 10; + + auto container = middle; + container = ResizableSplit::Left(left, container, &left_size); + container = ResizableSplit::Right(right, container, &right_size); + container = ResizableSplit::Top(top, container, &top_size); + container = ResizableSplit::Bottom(bottom, container, &bottom_size); + + auto renderer = + Renderer(container, [&] { return container->Render() | border; }); + + screen.Loop(renderer); +} + +// 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. diff --git a/include/ftxui/component/component.hpp b/include/ftxui/component/component.hpp index 298d7be..bc32123 100644 --- a/include/ftxui/component/component.hpp +++ b/include/ftxui/component/component.hpp @@ -41,8 +41,16 @@ namespace Container { Component Vertical(Components children); Component Horizontal(Components children); Component Tab(Components children, int* selector); + } // namespace Container +namespace ResizableSplit { +Component Left(Component main, Component back, int* main_size); +Component Right(Component main, Component back, int* main_size); +Component Top(Component main, Component back, int* main_size); +Component Bottom(Component main, Component back, int* main_size); +} // namespace ResizableSplit + } // namespace ftxui #endif /* end of include guard: FTXUI_COMPONENT_HPP */ diff --git a/src/ftxui/component/resizable_split.cpp b/src/ftxui/component/resizable_split.cpp new file mode 100644 index 0000000..f16c33d --- /dev/null +++ b/src/ftxui/component/resizable_split.cpp @@ -0,0 +1,257 @@ +#include "ftxui/component/captured_mouse.hpp" // for CapturedMouse +#include "ftxui/component/component.hpp" +#include "ftxui/component/component_base.hpp" +#include "ftxui/component/event.hpp" +#include "ftxui/dom/elements.hpp" + +namespace ftxui { +namespace { + +class ResizableSplitLeft : public ComponentBase { + public: + ResizableSplitLeft(Component main, Component child, int* main_size) + : main_(main), child_(child), main_size_(main_size) { + Add(Container::Horizontal({ + main, + child, + })); + } + + bool OnEvent(Event event) final { + if (event.is_mouse()) + return OnMouseEvent(std::move(event)); + return ComponentBase::OnEvent(std::move(event)); + } + + bool OnMouseEvent(Event event) { + if (captured_mouse_ && event.mouse().motion == Mouse::Released) { + captured_mouse_.reset(); + return true; + } + + if (event.mouse().button == Mouse::Left && + event.mouse().motion == Mouse::Pressed && + separator_box_.Contain(event.mouse().x, event.mouse().y) && + !captured_mouse_) { + captured_mouse_ = CaptureMouse(event); + return true; + } + + if (captured_mouse_) { + *main_size_ = event.mouse().x - global_box_.x_min; + return true; + } + + return ComponentBase::OnEvent(event); + } + + Element Render() final { + return hbox({ + main_->Render() | size(WIDTH, EQUAL, *main_size_), + separator() | reflect(separator_box_), + child_->Render() | xflex, + }) | + reflect(global_box_); + }; + + private: + Component main_; + Component child_; + int* const main_size_; + CapturedMouse captured_mouse_; + Box separator_box_; + Box global_box_; +}; + +class ResizableSplitRight: public ComponentBase { + public: + ResizableSplitRight(Component main, Component child, int* main_size) + : main_(main), child_(child), main_size_(main_size) { + Add(Container::Horizontal({ + child, + main, + })); + } + + bool OnEvent(Event event) final { + if (event.is_mouse()) + return OnMouseEvent(std::move(event)); + return ComponentBase::OnEvent(std::move(event)); + } + + bool OnMouseEvent(Event event) { + if (captured_mouse_ && event.mouse().motion == Mouse::Released) { + captured_mouse_.reset(); + return true; + } + + if (event.mouse().button == Mouse::Left && + event.mouse().motion == Mouse::Pressed && + separator_box_.Contain(event.mouse().x, event.mouse().y) && + !captured_mouse_) { + captured_mouse_ = CaptureMouse(event); + return true; + } + + if (captured_mouse_) { + *main_size_ = global_box_.x_max - event.mouse().x; + return true; + } + + return ComponentBase::OnEvent(event); + } + + Element Render() final { + return hbox({ + child_->Render() | xflex, + separator() | reflect(separator_box_), + main_->Render() | size(WIDTH, EQUAL, *main_size_), + }) | + reflect(global_box_); + }; + + private: + Component main_; + Component child_; + int* const main_size_; + CapturedMouse captured_mouse_; + Box separator_box_; + Box global_box_; +}; + +class ResizableSplitTop: public ComponentBase { + public: + ResizableSplitTop(Component main, Component child, int* main_size) + : main_(main), child_(child), main_size_(main_size) { + Add(Container::Vertical({ + main, + child, + })); + } + + bool OnEvent(Event event) final { + if (event.is_mouse()) + return OnMouseEvent(std::move(event)); + return ComponentBase::OnEvent(std::move(event)); + } + + bool OnMouseEvent(Event event) { + if (captured_mouse_ && event.mouse().motion == Mouse::Released) { + captured_mouse_.reset(); + return true; + } + + if (event.mouse().button == Mouse::Left && + event.mouse().motion == Mouse::Pressed && + separator_box_.Contain(event.mouse().x, event.mouse().y) && + !captured_mouse_) { + captured_mouse_ = CaptureMouse(event); + return true; + } + + if (captured_mouse_) { + *main_size_ = event.mouse().y - global_box_.y_min; + return true; + } + + return ComponentBase::OnEvent(event); + } + + Element Render() final { + return vbox({ + main_->Render() | size(HEIGHT, EQUAL, *main_size_), + separator() | reflect(separator_box_), + child_->Render() | yflex, + }) | + reflect(global_box_); + }; + + private: + Component main_; + Component child_; + int* const main_size_; + CapturedMouse captured_mouse_; + Box separator_box_; + Box global_box_; +}; + +class ResizableSplitBottom: public ComponentBase { + public: + ResizableSplitBottom(Component main, Component child, int* main_size) + : main_(main), child_(child), main_size_(main_size) { + Add(Container::Vertical({ + child, + main, + })); + } + + bool OnEvent(Event event) final { + if (event.is_mouse()) + return OnMouseEvent(std::move(event)); + return ComponentBase::OnEvent(std::move(event)); + } + + bool OnMouseEvent(Event event) { + if (captured_mouse_ && event.mouse().motion == Mouse::Released) { + captured_mouse_.reset(); + return true; + } + + if (event.mouse().button == Mouse::Left && + event.mouse().motion == Mouse::Pressed && + separator_box_.Contain(event.mouse().x, event.mouse().y) && + !captured_mouse_) { + captured_mouse_ = CaptureMouse(event); + return true; + } + + if (captured_mouse_) { + *main_size_ = global_box_.y_max - event.mouse().y; + return true; + } + + return ComponentBase::OnEvent(event); + } + + Element Render() final { + return vbox({ + child_->Render() | yflex, + separator() | reflect(separator_box_), + main_->Render() | size(HEIGHT, EQUAL, *main_size_), + }) | + reflect(global_box_); + }; + + private: + Component main_; + Component child_; + int* const main_size_; + CapturedMouse captured_mouse_; + Box separator_box_; + Box global_box_; +}; + +} // namespace + +namespace ResizableSplit { +Component Left(Component main, Component back, int* main_size) { + return Make(std::move(main), std::move(back), main_size); +} +Component Right(Component main, Component back, int* main_size) { + return Make(std::move(main), std::move(back), main_size); +} +Component Top(Component main, Component back, int* main_size) { + return Make(std::move(main), std::move(back), main_size); +} +Component Bottom(Component main, Component back, int* main_size) { + return Make(std::move(main), std::move(back), main_size); +} +//Component Top(Component main, Component back, int main_size) { + //return Make(std::move(main), std::move(back), main_size); +//} +//Component Bottom(Component main, Component back, int main_size) { + //return Make(std::move(main), std::move(back), + //main_size); +//} +} // namespace ResizableSplit +} // namespace ftxui