Add "frame" : scrollable area.

This commit is contained in:
Arthur Sonzogni 2019-01-19 22:06:05 +01:00
parent cb4df0b56f
commit fddcbdea65
50 changed files with 543 additions and 243 deletions

View File

@ -14,11 +14,11 @@ A C++ library for making text based user interface.
~~~cpp
vbox(
hbox(
text(L"left") | frame,
text(L"middle") | frame | flex,
text(L"right") | frame
text(L"left") | border,
text(L"middle") | border | flex,
text(L"right") | border
),
gauge(0.5) | frame
gauge(0.5) | border
)
~~~

View File

@ -5,12 +5,14 @@ function(example name)
endfunction(example)
example(checkbox)
example(checkbox_in_frame)
example(gallery)
example(input)
example(menu)
example(menu2)
example(menu_style)
example(radiobox)
example(radiobox_in_frame)
example(tab_horizontal)
example(tab_vertical)
example(toggle)

View File

@ -22,19 +22,10 @@ class MyComponent : public Component {
box_3_.label = L"Use WebAssembly";
box_3_.state = true;
}
Element Render() {
return
window(text(L" Checkbox "),
hbox(
container_.Render()
)
);
}
};
int main(int argc, const char *argv[]) {
auto screen = ScreenInteractive::FixedSize(30,5);
auto screen = ScreenInteractive::TerminalOutput();
MyComponent component;
screen.Loop(&component);
return 0;

View File

@ -0,0 +1,42 @@
#include "ftxui/component/checkbox.hpp"
#include "ftxui/component/container.hpp"
#include "ftxui/component/input.hpp"
#include "ftxui/component/menu.hpp"
#include "ftxui/component/checkbox.hpp"
#include "ftxui/component/screen_interactive.hpp"
#include "ftxui/component/toggle.hpp"
#include "ftxui/screen/string.hpp"
using namespace ftxui;
class MyComponent : public Component {
public:
MyComponent() {
Add(&container);
checkbox.resize(30);
for(int i = 0; i<checkbox.size(); ++i) {
checkbox[i].label = (L"CheckBox " + to_wstring(i));
container.Add(&checkbox[i]);
}
}
Element Render() override {
Elements content;
for(auto& it : checkbox) {
content.push_back(it.Render());
}
return vbox(std::move(content)) | frame | size(20, 10) | border;
}
private:
std::vector<CheckBox> checkbox;
Container container = Container::Vertical();
};
int main(int argc, const char* argv[]) {
auto screen = ScreenInteractive::FitComponent();
MyComponent component;
screen.Loop(&component);
return 0;
}

View File

@ -69,7 +69,7 @@ class MyComponent : public Component {
Render(L"radiobox", radiobox),
separator(),
Render(L"input", input)
) | frame;
) | border | frame | size(10,10) | border;
}
};

View File

@ -30,7 +30,7 @@ class MyComponent : public Component {
Element Render() override {
return
frame(
border(
vbox(
hbox(text(L" input_1 : "), input_1.Render()),
hbox(text(L" input_2 : "), input_2.Render()),

View File

@ -34,7 +34,7 @@ class MyComponent : public Component {
Element Render() override {
int sum = left_menu.selected * 10 + right_menu.selected;
return
frame(
border(
vbox(
// -------- Top panel --------------
hbox(

View File

@ -26,21 +26,21 @@ class MyComponent : public Component {
}
menu_2.selected_style = color(Color::Blue);
menu_2.active_style = bold | color(Color::Blue);
menu_2.focused_style = bold | color(Color::Blue);
menu_3.selected_style = color(Color::Blue);
menu_3.active_style = bgcolor(Color::Blue);
menu_3.focused_style = bgcolor(Color::Blue);
menu_4.selected_style = bgcolor(Color::Blue);
menu_4.active_style = bgcolor(Color::BlueLight);
menu_4.focused_style = bgcolor(Color::BlueLight);
menu_5.normal_style = bgcolor(Color::Blue);
menu_5.selected_style = bgcolor(Color::Yellow);
menu_5.active_style = bgcolor(Color::Red);
menu_5.focused_style = bgcolor(Color::Red);
menu_6.normal_style = dim | color(Color::Blue);
menu_6.selected_style = color(Color::Blue);
menu_6.active_style = bold | color(Color::Blue);
menu_6.focused_style = bold | color(Color::Blue);
}
std::function<void()> on_enter = [](){};
@ -62,7 +62,7 @@ class MyComponent : public Component {
menu_4.Render() | flex, separator(),
menu_5.Render() | flex, separator(),
menu_6.Render() | flex
) | frame;
) | border;
}
};

View File

@ -0,0 +1,34 @@
#include "ftxui/component/checkbox.hpp"
#include "ftxui/component/container.hpp"
#include "ftxui/component/input.hpp"
#include "ftxui/component/menu.hpp"
#include "ftxui/component/radiobox.hpp"
#include "ftxui/component/screen_interactive.hpp"
#include "ftxui/component/toggle.hpp"
#include "ftxui/screen/string.hpp"
using namespace ftxui;
class MyComponent : public Component {
RadioBox radiobox;
public:
MyComponent() {
for(int i = 0; i<30; ++i) {
radiobox.entries.push_back(L"RadioBox " + to_wstring(i));
}
Add(&radiobox);
}
Element Render() override {
return radiobox.Render() | frame | size(20,10) | border;
}
};
int main(int argc, const char* argv[]) {
auto screen = ScreenInteractive::FitComponent();
MyComponent component;
screen.Loop(&component);
return 0;
}

View File

@ -50,7 +50,7 @@ class MyComponent : public Component {
toggle_.Render(),
separator(),
tab_container_.Render()
) | frame;
) | border;
}
private:

View File

@ -4,7 +4,7 @@ function(example name)
endfunction(example)
example(dbox)
example(frame)
example(border)
example(gauge)
example(package_manager)
example(separator)

View File

@ -15,13 +15,11 @@ int main(int argc, const char *argv[])
text(L"Line 1"),
text(L"Line 2"),
text(L"Line 3"),
frame(
vbox(
text(L"Line 4"),
text(L"Line 5"),
text(L"Line 6")
)
),
) | border,
hbox(
window(text(L"frame 2"),
vbox(

View File

@ -7,20 +7,14 @@ int main(int argc, const char *argv[])
using namespace ftxui;
auto document =
dbox(
frame(
vbox(
text(L"line_1"),
text(L"line_2"),
text(L"line_3"),
text(L"line_4"),
text(L"line_5")
)
),
center(
frame(
text(L"overlay")
)
)
) | border,
text(L"overlay") | border | center
);
auto screen = Screen::TerminalOutput(document);
Render(screen, document.get());

View File

@ -24,7 +24,7 @@ int main(int argc, const char *argv[])
)
);
}
auto document = hbox(vbox(std::move(entries)) | frame, filler());
auto document = hbox(vbox(std::move(entries)) | border, filler());
auto screen = Screen::TerminalOutput(document);
Render(screen, document.get());
std::cout << reset_position << screen.ToString() << std::flush;

View File

@ -1,14 +1,16 @@
cmake_minimum_required(VERSION 3.0)
add_library(screen
src/ftxui/screen/box.cpp
src/ftxui/screen/screen.cpp
src/ftxui/screen/terminal.cpp
src/ftxui/screen/string.cpp
src/ftxui/screen/terminal.cpp
)
add_library(dom
src/ftxui/dom/blink.cpp
src/ftxui/dom/bold.cpp
src/ftxui/dom/border.cpp
src/ftxui/dom/color.cpp
src/ftxui/dom/composite_decorator.cpp
src/ftxui/dom/dbox.cpp

View File

@ -13,7 +13,7 @@ class Container : public Component {
public:
static Container Vertical();
static Container Horizontal();
static Container Tab(size_t* selector);
static Container Tab(int* selector);
~Container() override = default;
@ -36,8 +36,8 @@ class Container : public Component {
Element TabRender();
RenderHandler render_handler_;
size_t selected_ = 0;
size_t* selector_ = &selected_;
int selected_ = 0;
int* selector_ = &selected_;
};
} // namespace ftxui

View File

@ -15,9 +15,9 @@ class Menu : public Component {
// State.
std::vector<std::wstring> entries = {};
size_t selected = 0;
int selected = 0;
Decorator active_style = inverted;
Decorator focused_style = inverted;
Decorator selected_style = bold;
Decorator normal_style = nothing;

View File

@ -13,9 +13,13 @@ class Toggle : public Component {
~Toggle() override = default;
// State.
size_t selected = 0;
int selected = 0;
std::vector<std::wstring> entries = {L"On", L"Off"};
Decorator focused_style = inverted;
Decorator selected_style = bold;
Decorator normal_style = dim;
// Callback.
std::function<void()> on_change = [](){};

View File

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

View File

@ -12,21 +12,11 @@ using Element = std::unique_ptr<Node>;
using Elements = std::vector<Element>;
using Decorator = std::function<Element(Element)>;
// --- Layout ----
Element vbox(Elements);
Element hbox(Elements);
Element dbox(Elements);
// -- Flexibility --
Element filler();
Element flex(Element);
Decorator size(size_t width, size_t height);
// --- Widget --
// --- Widget ---
Element text(std::wstring text);
Element separator();
Element gauge(float ratio);
Element frame(Element);
Element border(Element);
Element window(Element title, Element content);
Element spinner(int charset_index, size_t image_index);
@ -36,19 +26,37 @@ Element dim(Element);
Element inverted(Element);
Element underlined(Element);
Element blink(Element);
Decorator color(Color);
Decorator bgcolor(Color);
Element color(Color, Element);
Element bgcolor(Color, Element);
// --- Util ---
// --- Layout ---
// Horizontal, Vertical or stacked set of elements.
Element vbox(Elements);
Element hbox(Elements);
Element dbox(Elements);
// -- Flexibility ---
// Define how to share the remaining space when not all of it is used inside a
// container.
Element filler();
Element flex(Element);
Decorator size(size_t width, size_t height);
// --- Frame ---
// A frame is a scrollable area. The internal area is potentially larger than
// the external one. The internal area is scrolled in order to make visible the
// focused element.
Element frame(Element);
Element focus(Element);
Element select(Element);
// --- Util --------------------------------------------------------------------
Element hcenter(Element);
Element vcenter(Element);
Element center(Element);
Element align_right(Element);
// --- Util ---
Element nothing(Element element);
// Pipe elements into decorator togethers.

View File

@ -4,8 +4,8 @@
#include <memory>
#include <vector>
#include "ftxui/dom/box.hpp"
#include "ftxui/dom/requirement.hpp"
#include "ftxui/screen/box.hpp"
#include "ftxui/screen/screen.hpp"
namespace ftxui {

View File

@ -1,6 +1,8 @@
#ifndef FTXUI_DOM_REQUIREMENT_HPP
#define FTXUI_DOM_REQUIREMENT_HPP
#include "ftxui/screen/box.hpp"
namespace ftxui {
struct Requirement {
@ -9,6 +11,14 @@ struct Requirement {
// How much flexibility is given to the component.
struct { int x = 0; int y = 0; } flex;
// Frame.
enum Selection {
NORMAL = 0,
SELECTED = 1,
FOCUSED = 2,
} selection = NORMAL;
Box selected_box;
};
}; // namespace ftxui

View File

@ -0,0 +1,17 @@
#ifndef FTXUI_SCREEN_BOX_HPP
#define FTXUI_SCREEN_BOX_HPP
namespace ftxui {
struct Box {
int x_min;
int x_max;
int y_min;
int y_max;
static Box Intersection(Box a, Box b);
};
}; // namespace ftxui
#endif /* end of include guard: FTXUI_SCREEN_BOX_HPP */

View File

@ -6,6 +6,7 @@
#include <memory>
#include "ftxui/screen/color.hpp"
#include "ftxui/screen/box.hpp"
namespace ftxui {
class Node;
@ -51,6 +52,7 @@ class Screen {
void Clear();
void ApplyShader();
Box stencil;
protected:
size_t dimx_;

View File

@ -0,0 +1,22 @@
#ifndef FTXUI_UTIL_AUTORESET_HPP
#define FTXUI_UTIL_AUTORESET_HPP
namespace ftxui {
template<typename T>
class AutoReset {
public:
AutoReset(T* variable, T new_value)
: variable_(variable),
previous_value_(std::move(*variable)) {
*variable_ = std::move(new_value);
}
~AutoReset() { *variable_ = std::move(previous_value_); }
private:
T* variable_;
T previous_value_;
};
} // namespace ftxui
#endif /* end of include guard: FTXUI_UTIL_AUTORESET_HPP */

View File

@ -4,8 +4,11 @@
namespace ftxui {
Element CheckBox::Render() {
auto style = Focused() ? focused_style : unfocused_style;
return hbox(text(state ? checked : unchecked), text(label) | style);
bool is_focused = Focused();
auto style = is_focused ? focused_style : unfocused_style;
auto focus_management = is_focused ? focus : state ? select : nothing;
return hbox(text(state ? checked : unchecked),
text(label) | style | focus_management);
}
bool CheckBox::OnEvent(Event event) {

View File

@ -19,7 +19,7 @@ Container Container::Vertical() {
}
// static
Container Container::Tab(size_t* selector) {
Container Container::Tab(int* selector) {
Container container;
container.event_handler_ = &Container::TabEvent;
container.render_handler_ = &Container::TabRender;
@ -53,7 +53,7 @@ bool Container::VerticalEvent(Event event) {
// Left pressed ?
if (event == Event::ArrowDown || event == Event::Character('j')) {
if (selected_ != children_.size() - 1) {
if (selected_ != int(children_.size()) - 1) {
selected_++;
return true;
}
@ -74,7 +74,7 @@ bool Container::HorizontalEvent(Event event) {
// Left pressed ?
if (event == Event::ArrowRight || event == Event::Character('l')) {
if (selected_ != children_.size() - 1) {
if (selected_ != int(children_.size()) - 1) {
selected_++;
return true;
}

View File

@ -26,12 +26,15 @@ Element Input::Render() {
std::wstring part_after_cursor = cursor_position < (int)content.size() - 1
? content.substr(cursor_position + 1)
: L"";
auto focused =
is_focused ? focus : select;
return
hbox(
text(part_before_cursor),
text(part_at_cursor) | underlined,
text(part_at_cursor) | underlined | focused,
text(part_after_cursor)
) | flex | inverted;
) | flex | inverted | frame;
}
bool Input::OnEvent(Event event) {
std::wstring c;

View File

@ -6,17 +6,14 @@ namespace ftxui {
Element Menu::Render() {
std::vector<Element> elements;
bool focused = Focused();
bool is_focused = Focused();
for (size_t i = 0; i < entries.size(); ++i) {
if (selected == i) {
if (focused)
elements.push_back(active_style(text(L"> " + entries[i])));
else
elements.push_back(selected_style(text(L"> " + entries[i])));
}
else {
elements.push_back(normal_style(text(L" " + entries[i])));
}
auto style = (selected != int(i))
? normal_style
: is_focused ? focused_style : selected_style;
auto focused = (selected != int(i)) ? nothing : is_focused ? focus : select;
auto icon = (selected != int(i)) ? L" " : L"> ";
elements.push_back(text(icon + entries[i]) | style | focused);
}
return vbox(std::move(elements));
}
@ -25,12 +22,12 @@ bool Menu::OnEvent(Event event) {
if (!Focused())
return false;
size_t new_selected = selected;
int new_selected = selected;
if (event == Event::ArrowUp || event == Event::Character('k'))
new_selected--;
if (event == Event::ArrowDown || event == Event::Character('j'))
new_selected++;
new_selected = std::max(size_t(0), std::min(entries.size()-size_t(1), new_selected));
new_selected = std::max(0, std::min(int(entries.size()) - 1, new_selected));
if (selected != new_selected) {
selected = new_selected;

View File

@ -9,8 +9,12 @@ Element RadioBox::Render() {
for (size_t i = 0; i < entries.size(); ++i) {
auto style =
(focused == int(i) && is_focused) ? focused_style : unfocused_style;
auto focus_management =
(focused != int(i)) ? nothing : is_focused ? focus : select;
const std::wstring& symbol = selected == int(i) ? checked : unchecked;
elements.push_back(hbox(text(symbol), text(entries[i]) | style));
elements.push_back(hbox(text(symbol), text(entries[i]) | style) |
focus_management);
}
return vbox(std::move(elements));
}
@ -30,7 +34,7 @@ bool RadioBox::OnEvent(Event event) {
return true;
}
if (event == Event::Character(' ')) {
if (event == Event::Character(' ') || event==Event::Return) {
selected = focused;
on_change();
}

View File

@ -3,7 +3,7 @@
namespace ftxui {
Element Toggle::Render() {
auto highlight = Focused() ? inverted : bold;
bool is_focused = Focused();
Elements children;
for(size_t i = 0; i<entries.size(); ++i) {
@ -12,8 +12,11 @@ Element Toggle::Render() {
children.push_back(separator());
// Entry.
auto style = i == selected ? highlight : dim;
children.push_back(style(text(entries[i])));
auto style = (selected != int(i))
? normal_style
: is_focused ? focused_style : selected_style;
auto focused = (selected != int(i)) ? nothing : is_focused ? focus : select;
children.push_back(text(entries[i]) | style | focused);
}
return hbox(std::move(children));
}
@ -26,7 +29,7 @@ bool Toggle::OnEvent(Event event) {
return true;
}
if (selected < entries.size() - 1 &&
if (selected < int(entries.size()) - 1 &&
(event == Event::ArrowRight || event == Event::Character('l'))) {
selected++;
on_change();

View File

@ -10,8 +10,8 @@ class Blink : public NodeDecorator {
void Render(Screen& screen) override {
Node::Render(screen);
for (int y = box_.top; y <= box_.bottom; ++y) {
for (int x = box_.left; x <= box_.right; ++x) {
for (int y = box_.y_min; y <= box_.y_max; ++y) {
for (int x = box_.x_min; x <= box_.x_max; ++x) {
screen.PixelAt(x, y).blink = true;
}
}

View File

@ -9,8 +9,8 @@ class Bold : public NodeDecorator {
~Bold() override {}
void Render(Screen& screen) override {
for (int y = box_.top; y <= box_.bottom; ++y) {
for (int x = box_.left; x <= box_.right; ++x) {
for (int y = box_.y_min; y <= box_.y_max; ++y) {
for (int x = box_.x_min; x <= box_.x_max; ++x) {
screen.PixelAt(x,y).bold = true;
}
}

View File

@ -0,0 +1,88 @@
#include "ftxui/dom/node.hpp"
#include "ftxui/dom/elements.hpp"
namespace ftxui {
using namespace ftxui;
static wchar_t charset[] = L"┌┐└┘─│┬┴┤├";
class Border : public Node {
public:
Border(Elements children) : Node(std::move(children)) {}
~Border() override {}
void ComputeRequirement() override {
Node::ComputeRequirement();
requirement_ = children[0]->requirement();
requirement_.min.x += 2;
requirement_.min.y += 2;
if (children.size() == 2) {
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++;
}
void SetBox(Box box) override {
Node::SetBox(box);
if (children.size() == 2) {
Box title_box;
title_box.x_min = box.x_min + 1;
title_box.x_max = box.x_max - 1;
title_box.y_min = box.y_min;
title_box.y_max = box.y_min;
children[1]->SetBox(title_box);
}
box.x_min++;
box.x_max--;
box.y_min++;
box.y_max--;
children[0]->SetBox(box);
}
void Render(Screen& screen) override {
// Draw content.
children[0]->Render(screen);
// Draw the border.
if (box_.x_min >= box_.x_max || box_.y_min >= box_.y_max)
return;
screen.at(box_.x_min, box_.y_min) = charset[0];
screen.at(box_.x_max, box_.y_min) = charset[1];
screen.at(box_.x_min, box_.y_max) = charset[2];
screen.at(box_.x_max, box_.y_max) = charset[3];
for(float x = box_.x_min + 1; x<box_.x_max; ++x) {
screen.at(x, box_.y_min) = charset[4];
screen.at(x, box_.y_max) = charset[4];
}
for(float y = box_.y_min + 1; y<box_.y_max; ++y) {
screen.at(box_.x_min, y) = charset[5];
screen.at(box_.x_max,y) = charset[5];
}
// Draw title.
if (children.size() == 2)
children[1]->Render(screen);
}
};
std::unique_ptr<Node> border(Element child) {
return std::make_unique<Border>(unpack(std::move(child)));
}
std::unique_ptr<Node> window(Element title, Element content) {
return std::make_unique<Border>(unpack(std::move(content), std::move(title)));
}
Decorator boxed() {
return [](Element child) {
return border(std::move(child));
};
}
}; // namespace ftxui

View File

@ -9,8 +9,8 @@ class BgColor : public NodeDecorator {
: NodeDecorator(std::move(children)), color_(color) {}
void Render(Screen& screen) override {
for (int y = box_.top; y <= box_.bottom; ++y) {
for (int x = box_.left; x <= box_.right; ++x) {
for (int y = box_.y_min; y <= box_.y_max; ++y) {
for (int x = box_.x_min; x <= box_.x_max; ++x) {
screen.PixelAt(x, y).background_color = color_;
}
}
@ -27,8 +27,8 @@ class FgColor : public NodeDecorator {
~FgColor() override {}
void Render(Screen& screen) override {
for (int y = box_.top; y <= box_.bottom; ++y) {
for (int x = box_.left; x <= box_.right; ++x) {
for (int y = box_.y_min; y <= box_.y_max; ++y) {
for (int x = box_.x_min; x <= box_.x_max; ++x) {
screen.PixelAt(x, y).foreground_color = color_;
}
}

View File

@ -17,6 +17,11 @@ class DBox : public Node {
child->ComputeRequirement();
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;
}
}
}

View File

@ -12,8 +12,8 @@ class Dim : public NodeDecorator {
void Render(Screen& screen) override {
Node::Render(screen);
for (int y = box_.top; y <= box_.bottom; ++y) {
for (int x = box_.left; x <= box_.right; ++x) {
for (int y = box_.y_min; y <= box_.y_max; ++y) {
for (int x = box_.x_min; x <= box_.x_max; ++x) {
screen.PixelAt(x,y).dim = true;
}
}

View File

@ -1,69 +1,116 @@
#include "ftxui/dom/node.hpp"
#include "ftxui/dom/elements.hpp"
#include "ftxui/dom/node.hpp"
#include "ftxui/util/autoreset.hpp"
namespace ftxui {
using namespace ftxui;
static wchar_t charset[] = L"┌┐└┘─│┬┴┤├";
// -----------------------------------------------------------------------------
class Frame : public Node {
class Select : public Node {
public:
Frame(Elements children) : Node(std::move(children)) {}
~Frame() override {}
Select(std::vector<std::unique_ptr<Node>> children)
: Node(std::move(children)) {}
void ComputeRequirement() override {
Node::ComputeRequirement();
requirement_ = children[0]->requirement();
requirement_.min.x += 2;
requirement_.min.y += 2;
if (children.size() == 2) {
requirement_.min.x =
std::max(requirement_.min.x, children[1]->requirement().min.x + 2);
auto& selected_box = requirement_.selected_box;
selected_box.x_min = 0;
selected_box.y_min = 0;
selected_box.x_max = requirement_.min.x;
selected_box.y_max = requirement_.min.y;
requirement_.selection = Requirement::SELECTED;
};
void SetBox(Box box) override {
box_ = box;
children[0]->SetBox(box);
}
};
std::unique_ptr<Node> select(Element child) {
return std::make_unique<Select>(unpack(std::move(child)));
}
// -----------------------------------------------------------------------------
class Focus : public Select {
public:
Focus(std::vector<std::unique_ptr<Node>> children)
: Select(std::move(children)) {}
void ComputeRequirement() override {
Select::ComputeRequirement();
requirement_.selection = Requirement::FOCUSED;
};
};
std::unique_ptr<Node> focus(Element child) {
return std::make_unique<Focus>(unpack(std::move(child)));
}
// -----------------------------------------------------------------------------
class Frame : public Node {
public:
Frame(std::vector<std::unique_ptr<Node>> children)
: Node(std::move(children)) {}
void ComputeRequirement() override {
Node::ComputeRequirement();
requirement_ = children[0]->requirement();
}
void SetBox(Box box) override {
Node::SetBox(box);
if (children.size() == 2) {
Box title_box;
title_box.left = box.left + 1;
title_box.right = box.right - 1;
title_box.top = box.top;
title_box.bottom = box.top;
children[1]->SetBox(title_box);
}
box.left++;
box.right--;
box.top++;
box.bottom--;
children[0]->SetBox(box);
int external_dimx = box.x_max - box.x_min;
int external_dimy = box.y_max - box.y_min;
int internal_dimx = std::max(requirement_.min.x, external_dimx);
int internal_dimy = std::max(requirement_.min.y, external_dimy);
auto& selected_box = requirement_.selected_box;
int focused_dimx = selected_box.x_max - selected_box.x_min;
int focused_dimy = selected_box.y_max - selected_box.y_min;
int dx = selected_box.x_min - external_dimx / 2 + focused_dimx / 2;
int dy = selected_box.y_min - external_dimy / 2 + focused_dimy / 2;
dx = std::max(0, std::min(internal_dimx - external_dimx - 1, dx));
dy = std::max(0, std::min(internal_dimy - external_dimy - 1, dy));
Box children_box = box;
children_box.x_min = box.x_min - dx;
children_box.y_min = box.y_min - dy;
children_box.x_max = box.x_min + internal_dimx - dx;
children_box.y_max = box.y_min + internal_dimy - dy;
children[0]->SetBox(children_box);
// int dx = box.x_max - box.x_min;
// int dy = box.y_max - box.y_min;
// int cdx = std::min(children[0].requirement().min.x
// Box children_box;
// children_box.x_min =
// if (box.x_max - box.x_min >= children[0].requirement().min.x && //
// box.y_max - box.y_min >= children[0].requirement().min.y) {
// children_[0]->SetBox(box);
// dx = 0;
// dy = 0;
// return;
//}
// Box children_box;
// children_box.x_min = box.x_min;
// children_box.y_min = box.x_min;
}
void Render(Screen& screen) override {
// Draw content.
AutoReset<Box> stencil(&screen.stencil,
Box::Intersection(box_, screen.stencil));
children[0]->Render(screen);
// Draw the frame.
if (box_.left >= box_.right || box_.top >= box_.bottom)
return;
screen.at(box_.left, box_.top) = charset[0];
screen.at(box_.right, box_.top) = charset[1];
screen.at(box_.left, box_.bottom) = charset[2];
screen.at(box_.right, box_.bottom) = charset[3];
for(float x = box_.left + 1; x<box_.right; ++x) {
screen.at(x, box_.top) = charset[4];
screen.at(x, box_.bottom) = charset[4];
}
for(float y = box_.top + 1; y<box_.bottom; ++y) {
screen.at(box_.left, y) = charset[5];
screen.at(box_.right,y) = charset[5];
}
// Draw title.
if (children.size() == 2)
children[1]->Render(screen);
}
};
@ -71,14 +118,4 @@ std::unique_ptr<Node> frame(Element child) {
return std::make_unique<Frame>(unpack(std::move(child)));
}
std::unique_ptr<Node> window(Element title, Element content) {
return std::make_unique<Frame>(unpack(std::move(content), std::move(title)));
}
Decorator boxed() {
return [](Element child) {
return frame(std::move(child));
};
}
}; // namespace ftxui

View File

@ -18,14 +18,14 @@ class Gauge : public Node {
}
void Render(Screen& screen) override {
float y = box_.top;
float limit = box_.left + progress_ * (box_.right - box_.left + 1);
float y = box_.y_min;
float limit = box_.x_min + progress_ * (box_.x_max - box_.x_min + 1);
int limit_int = limit;
int x = box_.left;
int x = box_.x_min;
while (x < limit_int)
screen.at(x++, y) = charset[9];
screen.at(x++, y) = charset[int(9*(limit-limit_int))];
while (x <= box_.right)
while (x <= box_.x_max)
screen.at(x++, y) = charset[0];
}
private:

View File

@ -15,6 +15,12 @@ class HBox : public Node {
requirement_.flex.y = 0;
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;
}
requirement_.min.x += child->requirement().min.x;
requirement_.min.y =
std::max(requirement_.min.y, child->requirement().min.y);
@ -28,33 +34,33 @@ class HBox : public Node {
for (auto& child : children)
flex_sum += child->requirement().flex.x;
int space = box.right - box.left + 1;
int space = box.x_max - box.x_min + 1;
int extra_space = space - requirement_.min.x;
int remaining_flex = flex_sum;
int remaining_extra_space = extra_space;
int x = box.left;
int x = box.x_min;
for (auto& child : children) {
if (x > box.right)
if (x > box.x_max)
break;
Box child_box = box;
child_box.left = x;
child_box.x_min = x;
child_box.right = x + child->requirement().min.x - 1;
child_box.x_max = 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.x_max += added_space;
}
child_box.right = std::min(child_box.right, box.right);
child_box.x_max = std::min(child_box.x_max, box.x_max);
child->SetBox(child_box);
x = child_box.right + 1;
x = child_box.x_max + 1;
}
}
};

View File

@ -12,8 +12,8 @@ class Inverted : public NodeDecorator {
void Render(Screen& screen) override {
Node::Render(screen);
for (int y = box_.top; y <= box_.bottom; ++y) {
for (int x = box_.left; x <= box_.right; ++x) {
for (int y = box_.y_min; y <= box_.y_max; ++y) {
for (int x = box_.x_min; x <= box_.x_max; ++x) {
screen.PixelAt(x,y).inverted = true;
}
}

View File

@ -28,13 +28,14 @@ void Render(Screen& screen, Node* node) {
node->ComputeRequirement();
Box box;
box.left = 0;
box.top = 0;
box.right = screen.dimx() - 1;
box.bottom = screen.dimy() - 1;
box.x_min = 0;
box.y_min = 0;
box.x_max = screen.dimx() - 1;
box.y_max = screen.dimy() - 1;
// Step 2: Assign a dimension to the element.
node->SetBox(box);
screen.stencil = box;
// Step 3: Draw the element.
node->Render(screen);

View File

@ -14,8 +14,8 @@ class Separator : public Node {
}
void Render(Screen& screen) override {
bool is_column = (box_.right == box_.left);
bool is_line = (box_.top == box_.bottom);
bool is_column = (box_.x_max == box_.x_min);
bool is_line = (box_.y_min == box_.y_max);
wchar_t c = U'+';
if (is_line && !is_column)
@ -23,8 +23,8 @@ class Separator : public Node {
else
c = U'';
for (int y = box_.top; y <= box_.bottom; ++y) {
for (int x = box_.left; x <= box_.right; ++x) {
for (int y = box_.y_min; y <= box_.y_max; ++y) {
for (int x = box_.x_min; x <= box_.x_max; ++x) {
screen.at(x, y) = c;
}
}

View File

@ -10,6 +10,7 @@ class Size : public Node {
~Size() override {}
void ComputeRequirement() override {
Node::ComputeRequirement();
requirement_ = children[0]->requirement();
requirement_.min.x = width_;
requirement_.min.y = height_;
requirement_.flex.x = 0;

View File

@ -15,12 +15,12 @@ class Text : public Node {
}
void Render(Screen& screen) override {
int x = box_.left;
int y = box_.top;
if (y > box_.bottom)
int x = box_.x_min;
int y = box_.y_min;
if (y > box_.y_max)
return;
for (wchar_t c : text_) {
if (x > box_.right)
if (x > box_.x_max)
return;
screen.at(x++, y) = c;
}

View File

@ -12,8 +12,8 @@ class Underlined : public NodeDecorator {
void Render(Screen& screen) override {
Node::Render(screen);
for (int y = box_.top; y <= box_.bottom; ++y) {
for (int x = box_.left; x <= box_.right; ++x) {
for (int y = box_.y_min; y <= box_.y_max; ++y) {
for (int x = box_.x_min; x <= box_.x_max; ++x) {
screen.PixelAt(x, y).underlined = true;
}
}

View File

@ -15,6 +15,12 @@ class VBox : public Node {
requirement_.flex.y = 1;
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;
}
requirement_.min.y += child->requirement().min.y;
requirement_.min.x =
std::max(requirement_.min.x, child->requirement().min.x);
@ -28,33 +34,33 @@ class VBox : public Node {
for (auto& child : children)
flex_sum += child->requirement().flex.y;
int space = box.bottom - box.top + 1;
int space = box.y_max - box.y_min + 1;
int extra_space = space - requirement_.min.y;
int remaining_flex = flex_sum;
int remaining_extra_space = extra_space;
int y = box.top;
int y = box.y_min;
for (auto& child : children) {
if (y > box.bottom)
if (y > box.y_max)
break;
Box child_box = box;
child_box.top = y;
child_box.y_min = y;
child_box.bottom = y + child->requirement().min.y - 1;
child_box.y_max = 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.y_max += added_space;
}
child_box.bottom = std::min(child_box.bottom, box.bottom);
child_box.y_max = std::min(child_box.y_max, box.y_max);
child->SetBox(child_box);
y = child_box.bottom + 1;
y = child_box.y_max + 1;
}
}
};

View File

@ -0,0 +1,14 @@
#include "ftxui/screen/box.hpp"
#include <cmath>
namespace ftxui {
// static
Box Box::Intersection(Box a, Box b) {
return Box{
std::max(a.x_min, b.x_min),
std::min(a.x_max, b.x_max),
std::max(a.y_min, b.y_min),
std::min(a.y_max, b.y_max),
};
}
} // namespace ftxui

View File

@ -7,6 +7,7 @@
namespace ftxui {
namespace {
static const wchar_t* BOLD_SET = L"\e[1m";
static const wchar_t* BOLD_RESET = L"\e[22m"; // Can't use 21 here.
@ -26,8 +27,20 @@ static const char* MOVE_LEFT = "\r";
static const char* MOVE_UP = "\e[1A";
static const char* CLEAR_LINE = "\e[2K";
bool In(const Box& stencil, int x, int y) {
return stencil.x_min <= x && x <= stencil.x_max && //
stencil.y_min <= y && y <= stencil.y_max;
}
Pixel dev_null_pixel;
} // namespace
Screen::Screen(size_t dimx, size_t dimy)
: dimx_(dimx), dimy_(dimy), pixels_(dimy, std::vector<Pixel>(dimx)) {}
: stencil({0, int(dimx) - 1, 0, int(dimy) - 1}),
dimx_(dimx),
dimy_(dimy),
pixels_(dimy, std::vector<Pixel>(dimx)) {}
void UpdatePixelStyle(std::wstringstream& ss, Pixel& previous, Pixel& next) {
if (next.bold != previous.bold)
@ -76,11 +89,11 @@ std::string Screen::ToString() {
}
wchar_t& Screen::at(size_t x, size_t y) {
return pixels_[y][x].character;
return PixelAt(x,y).character;
}
Pixel& Screen::PixelAt(size_t x, size_t y) {
return pixels_[y][x];
return In(stencil, x, y) ? pixels_[y][x] : dev_null_pixel;
}
// static

View File

@ -5,7 +5,7 @@
+ [Examples](#examples)
* [Widget.](#widget)
+ [text](#text)
+ [frame](#frame)
+ [border](#border)
+ [separator](#separator)
+ [gauge](#gauge)
* [Decorator](#decorator)
@ -27,21 +27,11 @@ All the dom element are declared in one header:
It declares the following set of elements:
~~~cpp
// --- Layout ----
Element vbox(Elements);
Element hbox(Elements);
Element dbox(Elements);
// -- Flexibility --
Element filler();
Element flex(Element);
Decorator size(size_t width, size_t height);
// --- Widget --
// --- Widget ---
Element text(std::wstring text);
Element separator();
Element gauge(float ratio);
Element frame(Element);
Element border(Element);
Element window(Element title, Element content);
Element spinner(int charset_index, size_t image_index);
@ -51,19 +41,37 @@ Element dim(Element);
Element inverted(Element);
Element underlined(Element);
Element blink(Element);
Decorator color(Color);
Decorator bgcolor(Color);
Element color(Color, Element);
Element bgcolor(Color, Element);
// --- Util ---
// --- Layout ---
// Horizontal, Vertical or stacked set of elements.
Element vbox(Elements);
Element hbox(Elements);
Element dbox(Elements);
// -- Flexibility ---
// Define how to share the remaining space when not all of it is used inside a
// container.
Element filler();
Element flex(Element);
Decorator size(size_t width, size_t height);
// --- Frame ---
// A frame is a scrollable area. The internal area is potentially larger than
// the external one. The internal area is scrolled in order to make visible the
// focused element.
Element frame(Element);
Element focus(Element);
Element select(Element);
// --- Util --------------------------------------------------------------------
Element hcenter(Element);
Element vcenter(Element);
Element center(Element);
Element align_right(Element);
// --- Util ---
Element nothing(Element element);
~~~
@ -98,9 +106,9 @@ container.
#### Examples
~~~cpp
hbox(
text(L"left") | frame ,
text(L"middle") | frame | flex,
text(L"right") | frame
text(L"left") | border ,
text(L"middle") | border | flex,
text(L"right") | border
);
~~~
~~~bash
@ -111,9 +119,9 @@ container.
~~~cpp
hbox(
text(L"left") | frame ,
text(L"middle") | frame | flex,
text(L"right") | frame | flex
text(L"left") | border ,
text(L"middle") | border | flex,
text(L"right") | border | flex
);
~~~
~~~bash
@ -134,10 +142,10 @@ The most simple widget. It display a text.
I am a piece of text.
~~~
#### frame
#### border
Add a border arround an element
~~~cpp
frame(text(L"The element"))
border(text(L"The element"))
~~~
~~~bash
@ -152,7 +160,7 @@ Display a vertical or horizontal line to visually split the content of a
container in two.
~~~cpp
frame(hbox(
border(hbox(
vbox(
text(L"left top"),
text(L"left bottom")
@ -176,7 +184,7 @@ frame(hbox(
A gauge. It can be used to represent a progress bar.
~~~c+
frame(gauge(0.5))
border(gauge(0.5))
~~~
~~~bash