Initial prototype

This commit is contained in:
Arthur Sonzogni 2018-09-18 08:48:40 +02:00
commit 49aeaa49c5
28 changed files with 857 additions and 0 deletions

8
.clang-format Normal file
View File

@ -0,0 +1,8 @@
# Defines the Chromium style for automatic reformatting.
# http://clang.llvm.org/docs/ClangFormatStyleOptions.html
BasedOnStyle: Chromium
# This defaults to 'Auto'. Explicitly set it for a while, so that
# 'vector<vector<int> >' in existing files gets formatted to
# 'vector<vector<int>>'. ('Auto' means that clang-format will only use
# 'int>>' if the file already contains at least one such instance.)
Standard: Cpp11

5
CMakeLists.txt Normal file
View File

@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.0)
enable_testing()
add_subdirectory(ftxui)
add_subdirectory(examples)

1
examples/CMakeLists.txt Normal file
View File

@ -0,0 +1 @@
add_subdirectory(text)

View File

@ -0,0 +1,4 @@
add_executable(main
main.cpp
)
target_link_libraries(main PRIVATE ftxui)

38
examples/text/main.cpp Normal file
View File

@ -0,0 +1,38 @@
#include "ftxui/core/screen.hpp"
#include "ftxui/core/dom/elements.hpp"
#include <iostream>
int main(int argc, const char *argv[])
{
using namespace ftxui::dom;
auto root =
vbox(
hbox(
text(L"north-west"),
flex(),
text(L"north-east")
),
flex(),
hbox(
hbox(
flex(),
text(L"center"),
flex()
)
),
flex(),
hbox(
text(L"south-west"),
flex(),
text(L"south-east")
)
);
auto screen = ftxui::Screen::WholeTerminal();
Render(screen, root.get());
std::cout << screen.ToString();
getchar();
return 0;
}

52
ftxui/CMakeLists.txt Normal file
View File

@ -0,0 +1,52 @@
cmake_minimum_required(VERSION 3.0)
project(ftxui)
add_library(ftxui
src/ftxui/core/component.cpp
src/ftxui/core/dom/flex.cpp
src/ftxui/core/dom/hbox.cpp
src/ftxui/core/dom/node.cpp
src/ftxui/core/dom/text.cpp
src/ftxui/core/dom/vbox.cpp
src/ftxui/core/screen.cpp
src/ftxui/core/terminal.cpp
src/ftxui/util/string.cpp
)
target_include_directories(ftxui
PUBLIC include
PRIVATE src
)
target_compile_features(ftxui PUBLIC cxx_std_17)
target_compile_options(ftxui PRIVATE -Wall)
# Note: For gtest, please follow:
# https://stackoverflow.com/questions/24295876/cmake-cannot-find-a-googletest-required-library
find_package(GTest)
find_package(Threads)
if (GTEST_FOUND AND THREADS_FOUND)
function(add_new_test test_name test_files)
add_executable(${ARGV})
target_link_libraries(${test_name}
PRIVATE
ftxui
Threads::Threads
${GTEST_BOTH_LIBRARIES}
)
target_include_directories(ftxui
PRIVATE
${GTest_INCLUDE_DIRS}
ftxui
)
gtest_discover_tests(${test_name})
add_test(${test_name} ${test_name})
endfunction(add_new_test)
add_new_test(dom_tests
src/ftxui/core/dom/text_test.cpp
src/ftxui/core/dom/hbox_test.cpp
src/ftxui/core/dom/vbox_test.cpp
)
endif()

View File

@ -0,0 +1 @@
State => Components => Document => Text.

View File

@ -0,0 +1,11 @@
#ifndef FTX_UI_CORE_BOX
#define FTX_UI_CORE_BOX
struct Box {
int left;
int right;
int top;
int bottom;
};
#endif /* end of include guard: FTX_UI_CORE_BOX */

View File

@ -0,0 +1,23 @@
#ifndef FTXUI_STATE_HPP
#define FTXUI_STATE_HPP
#include "ftxui/core/requirement.hpp"
#include "ftxui/core/document.hpp"
namespace ftxui {
class Component {
public:
virtual Document Render() = 0;
// Requirement -------------------------------------------------------------
virtual void ComputeRequirement();
Requirement requirement() { return requirement_; }
private:
Requirement requirement_;
};
}; // namespace ftxui
#endif /* end of include guard: FTXUI_STATE_HPP */

View File

@ -0,0 +1,12 @@
#ifndef FTXUI_DOCUMENT_HPP
#define FTXUI_DOCUMENT_HPP
namespace ftxui {
class Document {
};
};
#endif /* end of include guard: FTXUI_DOCUMENT_HPP */

View File

@ -0,0 +1,38 @@
#ifndef FTXUI_CORE_DOM_ELEMENTS_HPP
#define FTXUI_CORE_DOM_ELEMENTS_HPP
#include "ftxui/core/dom/node.hpp"
#include <initializer_list>
namespace ftxui {
namespace dom {
using Child = std::unique_ptr<Node>;
using Children = std::vector<std::unique_ptr<Node>>;
std::unique_ptr<Node> vbox(Children);
std::unique_ptr<Node> hbox(Children);
std::unique_ptr<Node> text(std::wstring text);
std::unique_ptr<Node> flex();
template <class... Args>
std::vector<Child> unpack(Args... args) {
std::vector<Child> vec;
(vec.push_back(std::forward<Args>(args)), ...);
return vec;
}
template <class... Args>
std::unique_ptr<Node> vbox(Args... children) {
return vbox(unpack(std::forward<Args>(children)...));
}
template <class... Args>
std::unique_ptr<Node> hbox(Args... children) {
return hbox(unpack(std::forward<Args>(children)...));
}
}; // namespace dom
}; // namespace ftxui
#endif /* end of include guard: FTXUI_CORE_DOM_ELEMENTS_HPP */

View File

@ -0,0 +1,44 @@
#ifndef CORE_DOM_NODE_HPP
#define CORE_DOM_NODE_HPP
#include <memory>
#include <vector>
#include "ftxui/core/requirement.hpp"
#include "ftxui/core/screen.hpp"
#include "ftxui/core/box.hpp"
namespace ftxui {
namespace dom {
class Node {
public:
Node();
Node(std::vector<std::unique_ptr<Node>> children);
virtual ~Node();
// Step 1: Direction parent <= children.
// Compute layout requirement. Tell parent what dimensions this
// element wants to be.
virtual void ComputeRequirement();
Requirement requirement() { return requirement_; }
// Step 2: Direction parent => children.
// Assign this element its final dimensions.
virtual void SetBox(Box box);
// Step 3: Draw this element.
virtual void Render(Screen& screen);
std::vector<std::unique_ptr<Node>> children;
protected:
Requirement requirement_;
Box box_;
};
void Render(Screen& screen, Node* node);
}; // namespace dom
}; // namespace ftxui
#endif /* end of include guard: CORE_DOM_NODE_HPP */

View File

@ -0,0 +1,28 @@
#ifndef FTXUI_LAYOUT_REQUIREMENT_HPP
#define FTXUI_LAYOUT_REQUIREMENT_HPP
namespace ftxui {
struct Requirement {
// Minimal dimensions.
struct {
int x = 0;
int y = 0;
} min;
// Maximal dimensions.
struct {
int x = -1;
int y = -1;
} max;
// Flex.
struct {
int x = 0;
int y = 0;
} flex;
};
}; // namespace ftxui
#endif /* end of include guard: FTXUI_LAYOUT_REQUIREMENT_HPP */

View File

@ -0,0 +1,28 @@
#ifndef FTXUI_CORE_SCREEN
#define FTXUI_CORE_SCREEN
#include <string>
#include <vector>
namespace ftxui {
class Screen {
public:
Screen(size_t dimx, size_t dimy);
wchar_t& at(size_t x, size_t y);
std::string ToString();
size_t dimx() { return dimx_;}
size_t dimy() { return dimy_;}
static Screen WholeTerminal();
private:
size_t dimx_;
size_t dimy_;
std::vector<std::wstring> lines_;
};
}; // namespace ftxui
#endif /* end of include guard: FTXUI_CORE_SCREEN */

View File

@ -0,0 +1,4 @@
#include <string>
std::string to_string(const std::wstring& s);
std::wstring to_wstring(const std::string& s);

View File

@ -0,0 +1,5 @@
#include "ftxui/core/component.hpp"
namespace ftxui {
void Component::ComputeRequirement() {}
} // namespace ftxui.

View File

@ -0,0 +1,23 @@
#include "ftxui/core/dom/node.hpp"
namespace ftxui {
namespace dom {
class Flex : public Node {
public:
Flex() {}
~Flex() override {}
void ComputeRequirement() {
requirement_.min.x = 0;
requirement_.min.y = 0;
requirement_.flex.x = 1;
requirement_.flex.y = 1;
}
};
std::unique_ptr<Node> flex() {
return std::make_unique<Flex>();
}
}; // namespace dom
}; // namespace ftxui

View File

@ -0,0 +1,68 @@
#include "ftxui/core/dom/node.hpp"
#include "ftxui/core/dom/elements.hpp"
namespace ftxui {
namespace dom {
class HBox : public Node {
public:
HBox(Children children) : Node(std::move(children)) {}
~HBox() {}
void ComputeRequirement() override {
requirement_.min.x = 0;
requirement_.min.y = 0;
requirement_.flex.x = 1;
requirement_.flex.y = 0;
for (auto& child : children) {
child->ComputeRequirement();
requirement_.min.x += child->requirement().min.x;
requirement_.min.y =
std::max(requirement_.min.y, child->requirement().min.y);
}
}
void SetBox(Box box) override {
Node::SetBox(box);
int flex_sum = 0;
for (auto& child : children)
flex_sum += child->requirement().flex.x;
int space = box.right - box.left + 1;
int extra_space = space - requirement_.min.x;
int remaining_flex = flex_sum;
int remaining_extra_space = extra_space;
int x = box.left;
for (auto& child : children) {
if (x > box.right)
break;
Box child_box = box;
child_box.left = x;
child_box.right = x + child->requirement().min.x - 1;
if (child->requirement().flex.x && remaining_extra_space > 0) {
int added_space = remaining_extra_space * child->requirement().flex.x /
remaining_flex;
remaining_extra_space -= added_space;
remaining_flex -= child->requirement().flex.x;
child_box.right += added_space;
}
child_box.right = std::min(child_box.right, box.right);
child->SetBox(child_box);
x = child_box.right + 1;
}
}
};
std::unique_ptr<Node> hbox(Children children) {
return std::make_unique<HBox>(std::move(children));
}
}; // namespace dom
}; // namespace ftxui

View File

@ -0,0 +1,123 @@
#include "ftxui/core/dom/elements.hpp"
#include "ftxui/core/screen.hpp"
#include "gtest/gtest.h"
namespace ftxui {
namespace dom {
TEST(HBoxTest, ScreenSmaller1) {
auto root = hbox(
text(L"text_1"),
text(L"text_2")
);
Screen screen(11,1);
Render(screen, root.get());
EXPECT_EQ("text_1text_", screen.ToString());
}
TEST(HBoxTest, ScreenSmaller2) {
auto root = hbox(
text(L"text_1"),
text(L"text_2")
);
Screen screen(10,1);
Render(screen, root.get());
EXPECT_EQ("text_1text", screen.ToString());
}
TEST(HBoxTest, ScreenFit) {
auto root = hbox(
text(L"text_1"),
text(L"text_2")
);
Screen screen(12,1);
Render(screen, root.get());
EXPECT_EQ("text_1text_2", screen.ToString());
}
TEST(HBoxTest, ScreenBigger1) {
auto root = hbox(
text(L"text_1"),
text(L"text_2")
);
Screen screen(13,1);
Render(screen, root.get());
EXPECT_EQ("text_1text_2 ", screen.ToString());
}
TEST(HBoxTest, ScreenBigger2) {
auto root = hbox(
text(L"text_1"),
text(L"text_2")
);
Screen screen(14,1);
Render(screen, root.get());
EXPECT_EQ("text_1text_2 ", screen.ToString());
}
TEST(HBoxTest, ScreenSmaller1Flex) {
auto root = hbox(
text(L"text_1"),
flex(),
text(L"text_2")
);
Screen screen(11,1);
Render(screen, root.get());
EXPECT_EQ("text_1text_", screen.ToString());
}
TEST(HBoxTest, ScreenSmaller2Flex) {
auto root = hbox(
text(L"text_1"),
flex(),
text(L"text_2")
);
Screen screen(10,1);
Render(screen, root.get());
EXPECT_EQ("text_1text", screen.ToString());
}
TEST(HBoxTest, ScreenFitFlex) {
auto root = hbox(
text(L"text_1"),
flex(),
text(L"text_2")
);
Screen screen(12,1);
Render(screen, root.get());
EXPECT_EQ("text_1text_2", screen.ToString());
}
TEST(HBoxTest, ScreenBigger1Flex) {
auto root = hbox(
text(L"text_1"),
flex(),
text(L"text_2")
);
Screen screen(13,1);
Render(screen, root.get());
EXPECT_EQ("text_1 text_2", screen.ToString());
}
TEST(HBoxTest, ScreenBigger2Flex) {
auto root = hbox(
text(L"text_1"),
flex(),
text(L"text_2")
);
Screen screen(14,1);
Render(screen, root.get());
EXPECT_EQ("text_1 text_2", screen.ToString());
}
}; // namespace dom
}; // namespace ftxui

View File

@ -0,0 +1,38 @@
#include "ftxui/core/dom/node.hpp"
namespace ftxui {
namespace dom {
Node::Node() {}
Node::Node(std::vector<std::unique_ptr<Node>> children)
: children(std::move(children)) {}
Node::~Node() {}
void Node::ComputeRequirement() {}
void Node::SetBox(Box box) {
box_ = box;
}
void Node::Render(Screen& screen) {
for(auto& child : children)
child->Render(screen);
}
void Render(Screen& screen, Node* node) {
// Step 1: Find what dimension this elements wants to be.
node->ComputeRequirement();
Box box;
box.left = 0;
box.top = 0;
box.right = screen.dimx() - 1;
box.bottom = screen.dimy() - 1;
// Step 2: Assign a dimension to the element.
node->SetBox(box);
// Step 3: Draw the element.
node->Render(screen);
}
}; // namespace dom
}; // namespace ftxui

View File

@ -0,0 +1,37 @@
#include "ftxui/core/dom/node.hpp"
namespace ftxui {
namespace dom {
class Text : public Node {
public:
Text(std::wstring text) : Node(), text_(text) {}
~Text() {}
void ComputeRequirement() override {
requirement_.min.x = text_.size();
requirement_.min.y = 1;
}
void Render(Screen& screen) override {
int x = box_.left;
int y = box_.top;
if (y > box_.bottom)
return;
for (wchar_t c : text_) {
if (x > box_.right)
return;
screen.at(x++, y) = c;
}
}
private:
std::wstring text_;
};
std::unique_ptr<Node> text(std::wstring text) {
return std::make_unique<Text>(text);
}
}; // namespace dom
}; // namespace ftxui

View File

@ -0,0 +1,49 @@
#include "ftxui/core/dom/elements.hpp"
#include "ftxui/core/screen.hpp"
#include "gtest/gtest.h"
namespace ftxui {
namespace dom {
TEST(TextTest, ScreenHeightSmaller) {
auto element = text(L"test");
Screen screen(2, 0);
Render(screen, element.get());
EXPECT_EQ("", screen.ToString());
}
TEST(TextTest, ScreenSmaller) {
auto element = text(L"test");
Screen screen(2, 1);
Render(screen, element.get());
EXPECT_EQ("te", screen.ToString());
}
TEST(TextTest, ScreenFit) {
auto element = text(L"test");
Screen screen(4, 1);
Render(screen, element.get());
EXPECT_EQ("test", screen.ToString());
}
TEST(TextTest, ScreenBigger) {
auto element = text(L"test");
Screen screen(6, 1);
Render(screen, element.get());
EXPECT_EQ("test ", screen.ToString());
}
TEST(TextTest, ScreenBigger2) {
auto element = text(L"test");
Screen screen(6, 2);
Render(screen, element.get());
EXPECT_EQ("test \n ", screen.ToString());
}
}; // namespace dom
}; // namespace ftxui

View File

@ -0,0 +1,68 @@
#include "ftxui/core/dom/node.hpp"
#include "ftxui/core/dom/elements.hpp"
namespace ftxui {
namespace dom {
class VBox : public Node {
public:
VBox(Children children) : Node(std::move(children)) {}
~VBox() {}
void ComputeRequirement() {
requirement_.min.x = 0;
requirement_.min.y = 0;
requirement_.flex.x = 0;
requirement_.flex.y = 1;
for (auto& child : children) {
child->ComputeRequirement();
requirement_.min.y += child->requirement().min.y;
requirement_.min.x =
std::max(requirement_.min.x, child->requirement().min.x);
}
}
void SetBox(Box box) {
Node::SetBox(box);
int flex_sum = 0;
for (auto& child : children)
flex_sum += child->requirement().flex.y;
int space = box.bottom - box.top + 1;
int extra_space = space - requirement_.min.y;
int remaining_flex = flex_sum;
int remaining_extra_space = extra_space;
int y = box.left;
for (auto& child : children) {
if (y > box.right)
break;
Box child_box = box;
child_box.top = y;
child_box.bottom = y + child->requirement().min.y - 1;
if (child->requirement().flex.y && remaining_extra_space > 0) {
int added_space = remaining_extra_space * child->requirement().flex.y /
remaining_flex;
remaining_extra_space -= added_space;
remaining_flex -= child->requirement().flex.y;
child_box.bottom += added_space;
}
child_box.bottom = std::min(child_box.bottom, box.bottom);
child->SetBox(child_box);
y = child_box.bottom + 1;
}
}
};
std::unique_ptr<Node> vbox(Children children) {
return std::make_unique<VBox>(std::move(children));
}
}; // namespace dom
}; // namespace ftxui

View File

@ -0,0 +1,71 @@
#include "ftxui/core/dom/elements.hpp"
#include "ftxui/core/screen.hpp"
#include "gtest/gtest.h"
namespace ftxui {
namespace dom {
TEST(VBoxTest, ScreenSmaller1) {
auto root = vbox(text(L"text_1"), text(L"text_2"));
Screen screen(6, 1);
Render(screen, root.get());
EXPECT_EQ("text_1", screen.ToString());
}
TEST(VBoxTest, ScreenFit) {
auto root = vbox(text(L"text_1"), text(L"text_2"));
Screen screen(6, 2);
Render(screen, root.get());
EXPECT_EQ("text_1\ntext_2", screen.ToString());
}
TEST(VBoxTest, ScreenBigger1) {
auto root = vbox(text(L"text_1"), text(L"text_2"));
Screen screen(6, 3);
Render(screen, root.get());
EXPECT_EQ("text_1\ntext_2\n ", screen.ToString());
}
TEST(VBoxTest, ScreenBigger2) {
auto root = vbox(text(L"text_1"), text(L"text_2"));
Screen screen(6, 4);
Render(screen, root.get());
EXPECT_EQ("text_1\ntext_2\n \n ", screen.ToString());
}
TEST(VBoxTest, ScreenSmaller1Flex) {
auto root = vbox(text(L"text_1"), flex(), text(L"text_2"));
Screen screen(6, 1);
Render(screen, root.get());
EXPECT_EQ("text_1", screen.ToString());
}
TEST(VBoxTest, ScreenFitFlex) {
auto root = vbox(text(L"text_1"), flex(), text(L"text_2"));
Screen screen(6, 2);
Render(screen, root.get());
EXPECT_EQ("text_1\ntext_2", screen.ToString());
}
TEST(VBoxTest, ScreenBigger1Flex) {
auto root = vbox(text(L"text_1"), flex(), text(L"text_2"));
Screen screen(6, 3);
Render(screen, root.get());
EXPECT_EQ("text_1\n \ntext_2", screen.ToString());
}
TEST(VBoxTest, ScreenBigger2Flex) {
auto root = vbox(text(L"text_1"), flex(), text(L"text_2"));
Screen screen(6, 4);
Render(screen, root.get());
EXPECT_EQ("text_1\n \n \ntext_2", screen.ToString());
}
}; // namespace dom
}; // namespace ftxui

View File

@ -0,0 +1,31 @@
#include "ftxui/core/screen.hpp"
#include "ftxui/core/terminal.hpp"
#include "ftxui/util/string.hpp"
#include <sstream>
namespace ftxui {
Screen::Screen(size_t dimx, size_t dimy)
: dimx_(dimx), dimy_(dimy), lines_(dimy, std::wstring(dimx, U' ')) {}
std::string Screen::ToString() {
std::stringstream ss;
for (size_t y = 0; y < dimy_; ++y) {
ss << to_string(lines_[y]);
if (y + 1 < dimy_)
ss << '\n';
}
return ss.str();
}
wchar_t& Screen::at(size_t x, size_t y) {
return lines_[y][x];
}
Screen Screen::WholeTerminal() {
Terminal::Dimensions size = Terminal::Size();
return Screen(size.dimx, size.dimy);
}
}; // namespace ftxui

View File

@ -0,0 +1,15 @@
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#include "ftxui/core/terminal.hpp"
namespace ftxui {
Terminal::Dimensions Terminal::Size() {
winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
return Dimensions{w.ws_col, w.ws_row};
}
} // namespace ftxui

View File

@ -0,0 +1,18 @@
#ifndef FTXUI_CORE_TERMINAL_HPP
#define FTXUI_CORE_TERMINAL_HPP
namespace ftxui {
class Terminal {
public:
struct Dimensions {
int dimx;
int dimy;
};
static Dimensions Size();
};
} // namespace ftxui
#endif /* end of include guard: FTXUI_CORE_TERMINAL_HPP */

View File

@ -0,0 +1,14 @@
#include "ftxui/util/string.hpp"
#include <codecvt>
#include <locale>
std::string to_string(const std::wstring& s) {
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
return converter.to_bytes(s);
}
std::wstring to_wstring(const std::string& s) {
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
return converter.from_bytes(s);
}