@@ -9,80 +9,196 @@ import CppUtils.Container.Size;
99import CppUtils.Terminal.Utility;
1010import CppUtils.Terminal.Size;
1111import 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
1318export 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}
0 commit comments