Skip to content

Commit 36e16ed

Browse files
committed
Terminal/Scrollable
1 parent a6764a8 commit 36e16ed

11 files changed

Lines changed: 351 additions & 51 deletions

File tree

modules/Terminal/Area.mpp

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export namespace CppUtils::Terminal
1616
public:
1717
inline Area(const Container::Size2& size, const Viewport& viewport = {{0, 0}, {0, 0}}):
1818
DynamicAreaBuffer{size},
19-
m_viewport{viewport}
19+
m_viewport{(viewport.getSize().width() == 0 or viewport.getSize().height() == 0) ? Viewport{size} : viewport}
2020
{}
2121

2222
template<std::derived_from<Widget> T>
@@ -27,15 +27,9 @@ export namespace CppUtils::Terminal
2727
return dynamic_cast<T&>(*m_widget);
2828
}
2929

30-
inline auto requestFullRender() noexcept -> void
31-
{
32-
m_fullRenderRequested = true;
33-
}
34-
3530
inline auto setViewport(const Viewport& viewport) noexcept -> void
3631
{
3732
m_viewport = viewport;
38-
requestFullRender();
3933
}
4034

4135
[[nodiscard]] inline auto getViewport() const noexcept -> const auto&
@@ -48,11 +42,15 @@ export namespace CppUtils::Terminal
4842
return DynamicAreaBuffer::getSize();
4943
}
5044

45+
[[nodiscard]] inline auto getWritableView(std::optional<Viewport> viewport = std::nullopt) -> WritableAreaView
46+
{
47+
return WritableAreaView{*this, viewport.value_or(m_viewport)};
48+
}
49+
5150
inline auto fill(const CharAttributes& c) noexcept -> void
5251
{
53-
auto view = WritableAreaView{*this, m_viewport};
52+
auto view = getWritableView();
5453
view.fill(c);
55-
requestFullRender();
5654
}
5755

5856
inline auto fill(char c) noexcept -> void
@@ -69,7 +67,7 @@ export namespace CppUtils::Terminal
6967
{
7068
if (not m_widget)
7169
return;
72-
auto selfView = WritableAreaView{*this, m_viewport};
70+
auto selfView = getWritableView();
7371
m_widget->draw(selfView);
7472
// Draw scrollbars
7573
// Apply to view (applique la portion délimitée par viewport du buffer sur view)
@@ -78,7 +76,6 @@ export namespace CppUtils::Terminal
7876
}
7977

8078
protected:
81-
bool m_fullRenderRequested = true;
8279
Viewport m_viewport;
8380
std::unique_ptr<Widget> m_widget;
8481
};

modules/Terminal/Canvas.mpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export namespace CppUtils::Terminal
3737
inline auto applyDifferences() noexcept -> void
3838
{
3939
using namespace std::literals;
40+
std::fwrite(std::data(TextStyle::Ansi::EscapeCode::Reset), sizeof(char), std::size(TextStyle::Ansi::EscapeCode::Reset), stdout);
41+
4042
const auto terminalSize = getTerminalSize();
4143
const auto viewportSize = m_viewport.getSize();
4244
const auto startLine = terminalSize.height() > viewportSize.height() ? terminalSize.height() - viewportSize.height() - 1 : 0;
@@ -47,12 +49,17 @@ export namespace CppUtils::Terminal
4749
const auto& currentChar = getChar(position);
4850
const auto& previousChar = m_previousBuffer.getChar(position);
4951

50-
const auto needUpdateText = currentChar.character != previousChar.character or currentChar.displayWidth != previousChar.displayWidth;
52+
const auto cellChanged = currentChar.character != previousChar.character
53+
or currentChar.displayWidth != previousChar.displayWidth
54+
or currentChar.textStyle != previousChar.textStyle
55+
or currentChar.textColor != previousChar.textColor
56+
or currentChar.backgroundColor != previousChar.backgroundColor;
57+
5158
const auto needUpdateTextStyle = currentChar.textStyle != lastAttributes.textStyle;
5259
const auto needUpdateTextColor = currentChar.textColor != lastAttributes.textColor;
5360
const auto needUpdateBackgroundColor = currentChar.backgroundColor != lastAttributes.backgroundColor;
5461

55-
if (needUpdateText or needUpdateTextStyle or needUpdateTextColor or needUpdateBackgroundColor)
62+
if (cellChanged)
5663
{
5764
if (std::empty(stringBuffer))
5865
setCursorPosition({position.x(), startLine + position.y()});
@@ -102,7 +109,7 @@ export namespace CppUtils::Terminal
102109
auto lock = std::scoped_lock{m_printMutex};
103110
if (m_widget)
104111
{
105-
auto view = WritableAreaView{*this, m_viewport};
112+
auto view = getWritableView();
106113
m_widget->draw(view);
107114
}
108115
if (m_firstPrint)

modules/Terminal/Primitive.mpp

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ import CppUtils.Container.Size;
77

88
export namespace CppUtils::Terminal
99
{
10+
enum class PrimitiveStyle
11+
{
12+
Outline,
13+
Filled
14+
};
15+
1016
inline auto drawLine(WritableAreaView& view, const Container::Size2& start, const Container::Size2& end, const CharAttributes& c) -> void
1117
{
1218
// https://fr.wikipedia.org/wiki/Algorithme_de_trac%C3%A9_de_segment_de_Bresenham
@@ -40,7 +46,7 @@ export namespace CppUtils::Terminal
4046
}
4147
}
4248

43-
inline auto drawRectangle(WritableAreaView& view, const Container::Size2& topLeft, const Container::Size2& size, bool outline, const CharAttributes& c) -> void
49+
inline auto drawRectangle(WritableAreaView& view, const Container::Size2& topLeft, const Container::Size2& size, PrimitiveStyle style, const CharAttributes& c) -> void
4450
{
4551
if (size.width() == 0 or size.height() == 0)
4652
return;
@@ -50,7 +56,7 @@ export namespace CppUtils::Terminal
5056
const auto width = size.width();
5157
const auto height = size.height();
5258

53-
if (outline)
59+
if (style == PrimitiveStyle::Outline)
5460
{
5561
const auto topRight = Container::Size2{x + width - 1, y};
5662
const auto bottomLeft = Container::Size2{x, y + height - 1};
@@ -67,7 +73,7 @@ export namespace CppUtils::Terminal
6773
view.setChar({currentX, currentY}, c);
6874
}
6975

70-
inline auto drawCircle(WritableAreaView& view, const Container::Size2& center, std::size_t radius, bool outline, const CharAttributes& c) -> void
76+
inline auto drawCircle(WritableAreaView& view, const Container::Size2& center, std::size_t radius, PrimitiveStyle style, const CharAttributes& c) -> void
7177
{
7278
// https://fr.wikipedia.org/wiki/Algorithme_de_trac%C3%A9_d%27arc_de_cercle_de_Bresenham
7379
auto centerX = static_cast<int>(center.x());
@@ -85,7 +91,7 @@ export namespace CppUtils::Terminal
8591

8692
while (y >= x)
8793
{
88-
if (outline)
94+
if (style == PrimitiveStyle::Outline)
8995
{
9096
drawPoint(centerX + x, centerY + y);
9197
drawPoint(centerX - x, centerY + y);
@@ -121,7 +127,7 @@ export namespace CppUtils::Terminal
121127
}
122128
}
123129

124-
inline auto drawEllipse(WritableAreaView& view, const Container::Size2& p1, const Container::Size2& p2, bool outline, const CharAttributes& c) -> void
130+
inline auto drawEllipse(WritableAreaView& view, const Container::Size2& p1, const Container::Size2& p2, PrimitiveStyle style, const CharAttributes& c) -> void
125131
{
126132
// https://fr.wikipedia.org/wiki/Algorithme_de_trac%C3%A9_d%27arc_de_cercle_de_Bresenham
127133
auto x0 = static_cast<int>(p1.x());
@@ -155,7 +161,7 @@ export namespace CppUtils::Terminal
155161

156162
do
157163
{
158-
if (outline)
164+
if (style == PrimitiveStyle::Outline)
159165
{
160166
drawPoint(x1, y0);
161167
drawPoint(x0, y0);
@@ -187,7 +193,7 @@ export namespace CppUtils::Terminal
187193

188194
while (y0 - y1 < height)
189195
{
190-
if (outline)
196+
if (style == PrimitiveStyle::Outline)
191197
{
192198
drawPoint(x0 - 1, y0);
193199
drawPoint(x1 + 1, y0++);

modules/Terminal/Scrollable.mpp

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
export module CppUtils.Terminal.Scrollable;
2+
3+
import std;
4+
import CppUtils.Container.Size;
5+
export import CppUtils.Terminal.Area;
6+
import CppUtils.Terminal.CharAttributes;
7+
import CppUtils.Terminal.TextColor;
8+
import CppUtils.Terminal.BackgroundColor;
9+
10+
export namespace CppUtils::Terminal
11+
{
12+
class Scrollable: public Widget
13+
{
14+
public:
15+
enum ScrollbarMode : std::uint8_t
16+
{
17+
None = 0b00,
18+
Vertical = 0b01,
19+
Horizontal = 0b10,
20+
Both = Vertical | Horizontal
21+
};
22+
23+
inline Scrollable(const Container::Size2& viewportSize, const Container::Size2& fullContentSize):
24+
m_viewportBuffer{viewportSize},
25+
m_fullContentArea{fullContentSize}
26+
{}
27+
28+
template<std::derived_from<Widget> T>
29+
inline auto addWidget(std::unique_ptr<T> widget) -> T&
30+
{
31+
return m_fullContentArea.addWidget(std::move(widget));
32+
}
33+
34+
inline auto setScroll(const Container::Size2& position) noexcept -> void
35+
{
36+
m_scrollPosition = position;
37+
requestUpdate(std::chrono::milliseconds{10});
38+
}
39+
40+
[[nodiscard]] inline auto getScroll() const noexcept -> const Container::Size2&
41+
{
42+
return m_scrollPosition;
43+
}
44+
45+
[[nodiscard]] inline auto getContentArea() noexcept -> Area&
46+
{
47+
return m_fullContentArea;
48+
}
49+
50+
inline auto showScrollbars(std::uint8_t mode = ScrollbarMode::Both) noexcept -> void
51+
{
52+
m_scrollbarMode = mode;
53+
}
54+
55+
[[nodiscard]] inline auto getScrollbarMode() const noexcept -> std::uint8_t
56+
{
57+
return m_scrollbarMode;
58+
}
59+
60+
[[nodiscard]] inline auto getSize() const noexcept -> Container::Size2 override
61+
{
62+
return m_viewportBuffer.getSize();
63+
}
64+
65+
inline void setWidgetManager(WidgetManager& widgetManager) noexcept override
66+
{
67+
Widget::setWidgetManager(widgetManager);
68+
m_fullContentArea.setWidgetManager(widgetManager);
69+
}
70+
71+
inline auto draw(WritableAreaView& view) noexcept -> void override
72+
{
73+
{
74+
auto fullContentAreaView = m_fullContentArea.getWritableView();
75+
m_fullContentArea.draw(fullContentAreaView);
76+
}
77+
78+
const auto& contentSize = m_fullContentArea.getSize();
79+
const auto viewportSize = getSize();
80+
81+
const auto maxScrollX = (contentSize.width() > viewportSize.width()) ? contentSize.width() - viewportSize.width() : 0;
82+
const auto maxScrollY = (contentSize.height() > viewportSize.height()) ? contentSize.height() - viewportSize.height() : 0;
83+
84+
if (m_scrollPosition.x() > maxScrollX)
85+
m_scrollPosition.x() = maxScrollX;
86+
if (m_scrollPosition.y() > maxScrollY)
87+
m_scrollPosition.y() = maxScrollY;
88+
89+
Viewport{viewportSize}.forEach([&](const auto& position) {
90+
const auto sourcePosition = position + m_scrollPosition;
91+
92+
if (sourcePosition.x() < contentSize.width() and sourcePosition.y() < contentSize.height())
93+
m_viewportBuffer.setChar(position, m_fullContentArea.getChar(sourcePosition));
94+
else
95+
m_viewportBuffer.setChar(position, CharAttributes{U' '});
96+
});
97+
98+
if (m_scrollbarMode != ScrollbarMode::None)
99+
{
100+
auto internalView = WritableAreaView{m_viewportBuffer};
101+
drawScrollbars(internalView, contentSize, viewportSize, maxScrollX, maxScrollY);
102+
}
103+
104+
view.applyArea(m_viewportBuffer);
105+
drawFinished();
106+
}
107+
108+
private:
109+
inline auto drawScrollbars(WritableAreaView& view, const Container::Size2& contentSize, const Container::Size2& viewSize, std::size_t maxScrollX, std::size_t maxScrollY) -> void
110+
{
111+
const bool showVertical = (m_scrollbarMode & ScrollbarMode::Vertical) and (contentSize.height() > viewSize.height());
112+
const bool showHorizontal = (m_scrollbarMode & ScrollbarMode::Horizontal) and (contentSize.width() > viewSize.width());
113+
114+
if (showVertical)
115+
{
116+
const auto barX = viewSize.width() - 1;
117+
const auto heightForBar = showHorizontal ? viewSize.height() - 1 : viewSize.height();
118+
119+
for (auto y = 0uz; y < heightForBar; ++y)
120+
view.setChar({barX, y}, CharAttributes{U'│', TextColor::TextColorEnum::White});
121+
122+
const auto ratio = static_cast<double>(heightForBar) / static_cast<double>(contentSize.height());
123+
const auto thumbHeight = std::max(1uz, static_cast<std::size_t>(ratio * static_cast<double>(heightForBar)));
124+
const auto scrollRatio = (maxScrollY > 0) ? static_cast<double>(m_scrollPosition.y()) / static_cast<double>(maxScrollY) : 0.0;
125+
const auto thumbY = static_cast<std::size_t>(scrollRatio * static_cast<double>(heightForBar - thumbHeight));
126+
127+
for (auto i = 0uz; i < thumbHeight; ++i)
128+
view.setChar({barX, thumbY + i}, CharAttributes{U'█', TextColor::TextColorEnum::White});
129+
}
130+
131+
if (showHorizontal)
132+
{
133+
const auto barY = viewSize.height() - 1;
134+
const auto widthForBar = showVertical ? viewSize.width() - 1 : viewSize.width();
135+
136+
for (auto x = 0uz; x < widthForBar; ++x)
137+
view.setChar({x, barY}, CharAttributes{U'─', TextColor::TextColorEnum::White});
138+
139+
const auto ratio = static_cast<double>(widthForBar) / static_cast<double>(contentSize.width());
140+
const auto thumbWidth = std::max(1uz, static_cast<std::size_t>(ratio * static_cast<double>(widthForBar)));
141+
const auto scrollRatio = (maxScrollX > 0) ? static_cast<double>(m_scrollPosition.x()) / static_cast<double>(maxScrollX) : 0.0;
142+
const auto thumbX = static_cast<std::size_t>(scrollRatio * static_cast<double>(widthForBar - thumbWidth));
143+
144+
for (auto i = 0uz; i < thumbWidth; ++i)
145+
view.setChar({thumbX + i, barY}, CharAttributes{U'█', TextColor::TextColorEnum::White});
146+
}
147+
148+
if (showVertical and showHorizontal)
149+
view.setChar({viewSize.width() - 1, viewSize.height() - 1}, CharAttributes{U'┘', TextColor::TextColorEnum::White});
150+
}
151+
152+
std::uint8_t m_scrollbarMode = ScrollbarMode::Both;
153+
Container::Size2 m_scrollPosition{0, 0};
154+
DynamicAreaBuffer m_viewportBuffer;
155+
Area m_fullContentArea;
156+
};
157+
}

modules/Terminal/Terminal.mpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export import CppUtils.Terminal.Handle;
99
export import CppUtils.Terminal.Layout;
1010
export import CppUtils.Terminal.ProgressBar;
1111
export import CppUtils.Terminal.Primitive;
12+
export import CppUtils.Terminal.Scrollable;
1213
export import CppUtils.Terminal.RawTerminal;
1314
export import CppUtils.Terminal.Size;
1415
export import CppUtils.Terminal.Spinner;

modules/Terminal/TextStyle.mpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export namespace CppUtils::Terminal::TextStyle
2020

2121
namespace Ansi::EscapeCode
2222
{
23+
[[maybe_unused]] inline constexpr auto Reset = "\x1B[0m"sv;
2324
[[maybe_unused]] inline constexpr auto Bolder = "\x1B[1m"sv;
2425
[[maybe_unused]] inline constexpr auto Lighter = "\x1B[2m"sv;
2526
[[maybe_unused]] inline constexpr auto Italic = "\x1B[3m"sv;

0 commit comments

Comments
 (0)