Skip to content

Commit 9814748

Browse files
committed
Canvas v2
1 parent 18cdc3a commit 9814748

9 files changed

Lines changed: 504 additions & 66 deletions

File tree

modules/Terminal/Area.mpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
export module CppUtils.Terminal.Area;
2+
3+
import std;
4+
import CppUtils.Container.Size;
5+
import :AreaBuffer;
6+
export import CppUtils.Terminal.CharAttributes;
7+
export import :Widget;
8+
export import :Viewport;
9+
export import :WritableAreaView;
10+
11+
export namespace CppUtils::Terminal
12+
{
13+
class Area: public AreaBuffer, public Widget
14+
{
15+
public:
16+
inline Area(const Container::Size2& size, const Viewport& viewport = {{0, 0}, {0, 0}}):
17+
AreaBuffer{size},
18+
m_viewport{viewport}
19+
{}
20+
21+
inline auto addWidget(std::unique_ptr<Widget> widget) -> void
22+
{
23+
m_widget = std::move(widget);
24+
}
25+
26+
inline auto requestFullRender() noexcept -> void
27+
{
28+
m_fullRenderRequested = true;
29+
}
30+
31+
inline auto setViewport(const Viewport& viewport) noexcept -> void
32+
{
33+
m_viewport = viewport;
34+
requestFullRender();
35+
}
36+
37+
[[nodiscard]] inline auto getViewport() const noexcept -> const auto&
38+
{
39+
return m_viewport;
40+
}
41+
42+
inline auto fill(char c) noexcept -> void
43+
{
44+
fill(CharAttributes{c});
45+
}
46+
47+
inline auto fill(const CharAttributes& c) noexcept -> void
48+
{
49+
auto view = WritableAreaView{*this, m_viewport};
50+
view.fill(c);
51+
requestFullRender();
52+
}
53+
54+
inline auto draw([[maybe_unused]] WritableAreaView& view) noexcept -> void override final
55+
{
56+
if (not m_widget)
57+
return;
58+
auto selfView = WritableAreaView{*this, m_viewport};
59+
m_widget->draw(selfView);
60+
// Draw scrollbars
61+
// Apply to view (applique la portion délimitée par viewport du buffer sur view)
62+
// view.applyArea(m_widget->getBuffer(), m_viewport);
63+
}
64+
65+
protected:
66+
bool m_fullRenderRequested = true;
67+
Viewport m_viewport;
68+
std::unique_ptr<Widget> m_widget;
69+
};
70+
}

modules/Terminal/AreaBuffer.mpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
export module CppUtils.Terminal.Area:AreaBuffer;
2+
3+
import std;
4+
import CppUtils.Container.Size;
5+
import CppUtils.Terminal.CharAttributes;
6+
7+
export namespace CppUtils::Terminal
8+
{
9+
class AreaBuffer
10+
{
11+
public:
12+
using Line = std::vector<CharAttributes>;
13+
using Buffer = std::vector<Line>;
14+
15+
inline AreaBuffer(const Container::Size2& size)
16+
{
17+
setSize(size);
18+
}
19+
20+
inline auto setSize(const Container::Size2& size) noexcept -> void
21+
{
22+
m_size = size;
23+
m_buffer = Buffer{m_size.height(), Line{m_size.width()}};
24+
}
25+
26+
[[nodiscard]] inline auto getSize() const noexcept -> const auto&
27+
{
28+
return m_size;
29+
}
30+
31+
[[nodiscard]] inline auto getChar(const Container::Size2& position) const noexcept -> const CharAttributes&
32+
{
33+
return m_buffer[position.y()][position.x()];
34+
}
35+
36+
inline auto setChar(const Container::Size2& position, const CharAttributes& c) noexcept -> void
37+
{
38+
if (position.x() >= m_size.width() or position.y() >= m_size.height())
39+
return;
40+
m_buffer[position.y()][position.x()] = c;
41+
}
42+
43+
auto getBuffer() const noexcept -> const auto&
44+
{
45+
return m_buffer;
46+
}
47+
48+
private:
49+
Buffer m_buffer;
50+
Container::Size2 m_size;
51+
};
52+
}

modules/Terminal/Canvas.mpp

Lines changed: 173 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -9,80 +9,196 @@ import CppUtils.Container.Size;
99
import CppUtils.Terminal.Utility;
1010
import CppUtils.Terminal.Size;
1111
import CppUtils.Terminal.Cursor;
12+
import CppUtils.Terminal.Area;
13+
import CppUtils.Terminal.TextStyle;
14+
import CppUtils.Terminal.TextColor;
15+
import CppUtils.Terminal.BackgroundColor;
16+
import CppUtils.Execution.Event;
1217

1318
export namespace CppUtils::Terminal
1419
{
15-
class Canvas final
20+
inline namespace v1
1621
{
17-
public:
18-
struct StyledSegment final
22+
class Canvas final
1923
{
20-
std::size_t begin, end;
24+
public:
25+
struct StyledSegment final
26+
{
27+
std::size_t begin, end;
28+
};
29+
30+
inline Canvas(const Container::Size2& size = getTerminalSize()):
31+
m_size{size},
32+
m_buffer{std::string(m_size.width() * m_size.height(), ' ')}
33+
{
34+
if (m_size.width() == 0 or m_size.height() == 0)
35+
return;
36+
std::print("{}", std::string(m_size.height() - 1, '\n'));
37+
update();
38+
}
39+
40+
inline ~Canvas()
41+
{
42+
std::puts("");
43+
}
44+
45+
[[nodiscard]] inline constexpr auto getSize() const noexcept -> const auto&
46+
{
47+
return m_size;
48+
}
49+
50+
inline auto fill(char c) noexcept -> void
51+
{
52+
m_buffer.assign(m_size.width() * m_size.height(), c);
53+
}
54+
55+
[[nodiscard]] inline auto getOrigin() const noexcept -> auto
56+
{
57+
return Container::Size2{0, getTerminalSize().height() - m_size.height()};
58+
}
59+
60+
inline auto print(const Container::Size2& position, std::string_view text) noexcept -> void
61+
{
62+
const auto offset = position.x() + position.y() * m_size.width();
63+
if (offset >= std::size(m_buffer))
64+
return;
65+
66+
std::copy_n(
67+
std::cbegin(text),
68+
std::min(std::size(text), std::size(m_buffer) - offset),
69+
std::begin(m_buffer) + static_cast<std::ptrdiff_t>(offset));
70+
}
71+
72+
inline auto update() -> void
73+
{
74+
{
75+
auto cursorPosition = getTerminalSize();
76+
cursorPosition.x() = 0;
77+
cursorPosition.y() -= m_size.height();
78+
setCursorPosition(cursorPosition);
79+
}
80+
const auto buffer = std::string_view{m_buffer};
81+
for (auto lineNb = 0uz; lineNb < m_size.height();)
82+
{
83+
std::fwrite(std::data(buffer.substr(lineNb * m_size.width(), m_size.width())), sizeof(decltype(m_buffer)::value_type), m_size.width(), stdout);
84+
if (++lineNb != m_size.height())
85+
std::fwrite("\n", 1, 1, stdout);
86+
}
87+
std::fflush(stdout);
88+
}
89+
90+
private:
91+
Container::Size2 m_size;
92+
std::string m_buffer;
93+
std::vector<StyledSegment> m_attributes;
2194
};
95+
}
2296

23-
inline Canvas(Container::Size2 size = getTerminalSize()):
24-
m_size{size},
25-
m_buffer{std::string(m_size.width() * m_size.height(), ' ')}
97+
namespace v2
98+
{
99+
class Canvas final: public Area
26100
{
27-
if (m_size.width() == 0 or m_size.height() == 0)
28-
return;
29-
std::print("{}", std::string(m_size.height() - 1, '\n'));
30-
update();
31-
}
101+
using Area::draw;
32102

33-
inline ~Canvas()
34-
{
35-
std::puts("");
36-
}
103+
public:
104+
inline Canvas(const Container::Size2& size = getTerminalSize(), const std::optional<Viewport>& viewportOpt = std::nullopt):
105+
Area{size, viewportOpt.value_or(Viewport{size, {0, 0}})},
106+
m_previousBuffer{size}
107+
{}
37108

38-
[[nodiscard]] inline constexpr auto getSize() const noexcept -> const auto&
39-
{
40-
return m_size;
41-
}
109+
inline auto applyDifferences() noexcept -> void
110+
{
111+
using namespace std::literals;
112+
auto terminalSize = getTerminalSize();
113+
auto stringBuffer = ""s;
114+
auto needFlush = false;
115+
auto lastAttributes = CharAttributes{};
116+
m_viewport.forEach([&](const auto& position) noexcept {
117+
const auto& currentChar = getChar(position);
118+
const auto& previousChar = m_previousBuffer.getChar(position);
42119

43-
inline auto fill(char c) noexcept -> void
44-
{
45-
m_buffer.assign(m_size.width() * m_size.height(), c);
46-
}
120+
auto needUpdateText = currentChar.character != previousChar.character;
121+
auto needUpdateTextStyle = currentChar.textStyle != lastAttributes.textStyle;
122+
auto needUpdateTextColor = currentChar.textColor != lastAttributes.textColor;
123+
auto needUpdateBackgroundColor = currentChar.backgroundColor != lastAttributes.backgroundColor;
47124

48-
[[nodiscard]] inline auto getOrigin() const noexcept -> auto
49-
{
50-
return Container::Size2{0, getTerminalSize().height() - m_size.height()};
51-
}
125+
if (needUpdateText or needUpdateTextStyle or needUpdateTextColor or needUpdateBackgroundColor)
126+
{
127+
if (std::empty(stringBuffer))
128+
setCursorPosition(Container::Size2{position.x(), terminalSize.height() - m_viewport.getSize().height() + position.y() - 1});
129+
if (needUpdateTextStyle)
130+
{
131+
stringBuffer += TextStyle::getTextStyleCode(currentChar.textStyle);
132+
lastAttributes.textStyle = currentChar.textStyle;
133+
}
134+
if (needUpdateTextColor)
135+
{
136+
stringBuffer += TextColor::getTextColorCode(currentChar.textColor);
137+
lastAttributes.textColor = currentChar.textColor;
138+
}
139+
if (needUpdateBackgroundColor)
140+
{
141+
stringBuffer += BackgroundColor::getBackgroundColorCode(currentChar.backgroundColor);
142+
lastAttributes.backgroundColor = currentChar.backgroundColor;
143+
}
144+
stringBuffer += currentChar.character;
145+
m_previousBuffer.setChar(position, currentChar);
146+
if (not std::empty(stringBuffer) and position.x() + 1 == m_viewport.getSize().width())
147+
stringBuffer += '\n';
148+
}
149+
else if (not std::empty(stringBuffer))
150+
{
151+
std::fwrite(std::data(stringBuffer), sizeof(decltype(stringBuffer)::value_type), std::size(stringBuffer), stdout);
152+
stringBuffer.clear();
153+
needFlush = true;
154+
}
155+
});
156+
if (not std::empty(stringBuffer))
157+
{
158+
std::fwrite(std::data(stringBuffer), sizeof(decltype(stringBuffer)::value_type), std::size(stringBuffer), stdout);
159+
needFlush = true;
160+
}
161+
if (needFlush)
162+
std::fflush(stdout);
163+
setCursorPosition(getTerminalSize());
164+
}
52165

53-
inline auto print(Container::Size2 position, std::string_view text) noexcept -> void
54-
{
55-
const auto offset = position.x() + position.y() * m_size.width();
56-
if (offset >= std::size(m_buffer))
57-
return;
166+
inline auto print() noexcept -> void
167+
{
168+
if (m_widget)
169+
{
170+
auto view = WritableAreaView{*this, m_viewport};
171+
m_widget->draw(view);
172+
}
173+
if (m_forceReprint)
174+
{
175+
std::print("{}", std::string(m_viewport.getSize().height(), '\n'));
176+
m_forceReprint = false;
177+
}
178+
applyDifferences();
179+
}
58180

59-
std::copy_n(
60-
std::cbegin(text),
61-
std::min(std::size(text), std::size(m_buffer) - offset),
62-
std::begin(m_buffer) + static_cast<std::ptrdiff_t>(offset));
63-
}
181+
inline auto reprint() noexcept -> void
182+
{
183+
m_forceReprint = true;
184+
print();
185+
}
64186

65-
inline auto update() -> void
66-
{
187+
inline auto wait() noexcept -> void
67188
{
68-
auto cursorPosition = getTerminalSize();
69-
cursorPosition.x() = 0;
70-
cursorPosition.y() -= m_size.height();
71-
setCursorPosition(cursorPosition);
189+
print();
190+
m_stopEvent.wait();
72191
}
73-
const auto buffer = std::string_view{m_buffer};
74-
for (auto lineNb = 0uz; lineNb < m_size.height();)
192+
193+
inline auto stop() noexcept -> void
75194
{
76-
std::fwrite(std::data(buffer.substr(lineNb * m_size.width(), m_size.width())), sizeof(decltype(m_buffer)::value_type), m_size.width(), stdout);
77-
if (++lineNb != m_size.height())
78-
std::fwrite("\n", 1, 1, stdout);
195+
m_stopEvent.notify();
79196
}
80-
std::fflush(stdout);
81-
}
82-
83-
private:
84-
Container::Size2 m_size;
85-
std::string m_buffer;
86-
std::vector<StyledSegment> m_attributes;
87-
};
197+
198+
private:
199+
bool m_forceReprint = true;
200+
AreaBuffer m_previousBuffer;
201+
Execution::Event m_stopEvent;
202+
};
203+
}
88204
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export module CppUtils.Terminal.CharAttributes;
2+
3+
import CppUtils.Terminal.TextStyle;
4+
import CppUtils.Terminal.TextColor;
5+
import CppUtils.Terminal.BackgroundColor;
6+
7+
export namespace CppUtils::Terminal
8+
{
9+
struct CharAttributes final
10+
{
11+
char character = ' ';
12+
TextStyle::TextStyleEnum textStyle = TextStyle::TextStyleEnum{0};
13+
TextColor::TextColorEnum textColor = TextColor::TextColorEnum::Default;
14+
BackgroundColor::BackgroundColorEnum backgroundColor = BackgroundColor::BackgroundColorEnum::Default;
15+
};
16+
}

0 commit comments

Comments
 (0)