320 lines
8.4 KiB
C++
320 lines
8.4 KiB
C++
#include <codecvt>
|
|
#include <locale>
|
|
#include <string>
|
|
|
|
#include "neovision/ansi.h"
|
|
#include "neovision/application.h"
|
|
#include "neovision/crt.h"
|
|
|
|
#include "neovision/view.h"
|
|
|
|
namespace neovision {
|
|
|
|
View::~View()
|
|
{
|
|
const std::lock_guard<std::mutex> bufferLock(m_bufferMutex);
|
|
const std::lock_guard<std::mutex> previousBufferLock(m_previousBufferMutex);
|
|
m_buffer.clear();
|
|
m_previousBuffer.clear();
|
|
}
|
|
|
|
void View::Add(std::unique_ptr<View> view)
|
|
{
|
|
View& viewRef = *view;
|
|
viewRef.m_parent = *this;
|
|
m_views.emplace_back(std::move(view));
|
|
CalculateZOrders();
|
|
if (m_initialized == true) viewRef.Initialize();
|
|
}
|
|
|
|
void View::CalculateZOrders()
|
|
{
|
|
std::sort(m_views.begin(), m_views.end(),
|
|
[](const std::unique_ptr<View>& a, const std::unique_ptr<View>& b){
|
|
return a->Zorder() < b->Zorder();
|
|
}
|
|
);
|
|
}
|
|
|
|
Vector2D View::CursorPosition() const
|
|
{
|
|
return m_cursorPosition;
|
|
}
|
|
|
|
void View::CursorPosition(const Vector2D& pos)
|
|
{
|
|
m_cursorPosition = pos;
|
|
}
|
|
|
|
bool View::Dirty() const
|
|
{
|
|
return m_dirty;
|
|
}
|
|
|
|
void View::Initialize()
|
|
{
|
|
m_initialized = true;
|
|
}
|
|
|
|
bool View::Initialized() const
|
|
{
|
|
return m_initialized;
|
|
}
|
|
|
|
std::optional<std::reference_wrapper<View>> View::Parent()
|
|
{
|
|
return m_parent;
|
|
}
|
|
|
|
void View::Position(const Vector2D& p)
|
|
{
|
|
m_position = p;
|
|
}
|
|
|
|
Vector2D View::Position() const
|
|
{
|
|
return m_position;
|
|
}
|
|
|
|
void View::Render()
|
|
{
|
|
DoRender([&](const std::wstring& output, const Vector2D& pos){
|
|
if (m_cursorPosition != pos)
|
|
{
|
|
m_cursorPosition = pos;
|
|
SetCursorPos(pos);
|
|
}
|
|
IO::Get().Write(output);
|
|
});
|
|
}
|
|
|
|
void View::Render(View& view)
|
|
{
|
|
DoRender([&](const std::wstring& output, const Vector2D& p){
|
|
view.WriteAt(p, output);
|
|
});
|
|
}
|
|
|
|
void View::DoRender(
|
|
std::function<void(const std::wstring&, const Vector2D&)> outputFunc
|
|
)
|
|
{
|
|
const std::lock_guard<std::mutex> bufferLock(m_bufferMutex);
|
|
/* We try to be clever and only re-draw the areas that need updating.
|
|
Therefore, calling this when not dirty is fine, no unnecesary output
|
|
shoud happen. However, if the parent is dirty, we force a complete redraw
|
|
by clearing our previous buffer, because the real output is the parent's
|
|
buffer and we have no way of knowing the state of it's buffer. But that's
|
|
OK because when the parent get's to it's render phase, IT will do the
|
|
clever comparing thing as well, and again only output to the screen what
|
|
needs changing. */
|
|
if (m_parent && (m_parent->get().Dirty() == true)) m_previousBuffer.clear();
|
|
for (std::uint32_t y = 0; y != m_size.Y() + 1; ++y)
|
|
{
|
|
std::wstring lineOut;
|
|
const std::vector<wchar_t>& line = m_buffer[y];
|
|
bool needUpdate{false};
|
|
/* Compare current buffer with previous buffer and only write the
|
|
differences to output. */
|
|
for (std::uint32_t x = 0; x != m_size.X() + 1; ++x)
|
|
{
|
|
if (x >= line.size()) break;
|
|
const wchar_t newChar = line[x];
|
|
const Vector2D pos = m_position + Vector2D(x, y);
|
|
if (y < m_previousBuffer.size())
|
|
{
|
|
const std::vector<wchar_t>& pline = m_previousBuffer[y];
|
|
if (x < pline.size())
|
|
{
|
|
const wchar_t oldChar = pline[x];
|
|
if (newChar != oldChar)
|
|
{
|
|
needUpdate = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* If current buffer is wider than the previous buffer,
|
|
we have nothing to compare to, so just always write. */
|
|
needUpdate = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Similarly, if the current buffer is taller than the previous
|
|
* buffer, always write. */
|
|
needUpdate = true;
|
|
}
|
|
// Now write output if an update is needed for this character.
|
|
if (needUpdate) outputFunc(std::wstring{newChar}, pos);
|
|
}
|
|
if (!lineOut.empty())
|
|
{
|
|
const Vector2D pos = m_position + Vector2D(0, y);
|
|
outputFunc(lineOut, pos);
|
|
}
|
|
}
|
|
{
|
|
const std::lock_guard<std::mutex> previousBufferLock(m_previousBufferMutex);
|
|
m_previousBuffer.clear();
|
|
for (const auto& l: m_buffer)
|
|
{
|
|
std::vector<wchar_t> newLine;
|
|
for (const wchar_t c: l)
|
|
{
|
|
newLine.emplace_back(c);
|
|
}
|
|
m_previousBuffer.emplace_back(newLine);
|
|
}
|
|
}
|
|
m_dirty = false;
|
|
}
|
|
|
|
void View::OnDraw()
|
|
{
|
|
// Let all child views draw themselves first.
|
|
for (auto& v: m_views)
|
|
{
|
|
if (!v) continue;
|
|
if (v->Initialized() != true) v->Initialize();
|
|
if (v->Dirty() == true)
|
|
{
|
|
v->OnDraw();
|
|
}
|
|
}
|
|
|
|
/* Render all child views (they should be sorted by z-order already)
|
|
onto ourself. */
|
|
for (auto& v: m_views)
|
|
{
|
|
if ((v->Dirty() == true) || (m_dirty == true))
|
|
{
|
|
v->Render(*this);
|
|
}
|
|
}
|
|
// If we don't have a parent, render to screen.
|
|
if (!m_parent) Render();
|
|
}
|
|
|
|
void View::SetDirty(bool propagateChildren, bool propagateParent)
|
|
{
|
|
m_dirty = true;
|
|
if (propagateChildren == true)
|
|
{
|
|
for (const auto& v: m_views)
|
|
{
|
|
v->SetDirty(true, false);
|
|
}
|
|
}
|
|
if (m_parent)
|
|
{
|
|
if (propagateParent == true)
|
|
{
|
|
m_parent->get().SetDirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
void View::Size(const Vector2D& s, wchar_t fill)
|
|
{
|
|
const std::lock_guard<std::mutex> lock(m_bufferMutex);
|
|
m_size = s;
|
|
const uint32_t sY = s.Y();
|
|
const uint32_t sX = s.X();
|
|
const uint32_t curRowBufferSize = m_buffer.size();
|
|
const uint32_t minRowBufferSize = sY + 1;
|
|
const uint32_t minColBufferSize = sX + 1;
|
|
if (curRowBufferSize != minRowBufferSize) m_buffer.resize(minRowBufferSize);
|
|
for (std::vector<wchar_t>& line: m_buffer)
|
|
{
|
|
const uint32_t curColBufferSize = line.size();
|
|
if (curColBufferSize < minColBufferSize)
|
|
{
|
|
line.resize(minColBufferSize, fill);
|
|
}
|
|
else if (curColBufferSize > minColBufferSize)
|
|
{
|
|
line.resize(minColBufferSize);
|
|
}
|
|
}
|
|
SetDirty(false, false);
|
|
}
|
|
|
|
Vector2D View::Size() const
|
|
{
|
|
return m_size;
|
|
}
|
|
|
|
void View::Write(const std::wstring& s)
|
|
{
|
|
if (s.empty() || m_buffer.empty()) return;
|
|
WriteToBuffer(m_cursorPosition, s);
|
|
}
|
|
|
|
void View::WriteAt(const Vector2D& p, const std::wstring& s)
|
|
{
|
|
if (s.empty()) return;
|
|
WriteToBuffer(p, s);
|
|
}
|
|
|
|
void View::WriteLn(const std::wstring& s)
|
|
{
|
|
if (s.empty() || m_buffer.empty()) return;
|
|
WriteToBuffer(m_cursorPosition, s + L"\n");
|
|
}
|
|
|
|
void View::WriteToBuffer(const Vector2D& p, const std::wstring s)
|
|
{
|
|
const std::lock_guard<std::mutex> lock(m_bufferMutex);
|
|
if (m_buffer.empty()) return;
|
|
if (p.X() > m_size.X()) return;
|
|
if (p.Y() > m_size.Y()) return;
|
|
// a position-write with a blank string still sets position.
|
|
m_cursorPosition.X(p.X());
|
|
m_cursorPosition.Y(p.Y());
|
|
SetDirty();
|
|
if (s.empty()) return;
|
|
/* We're taking this one character at the time, because things get
|
|
complicated given that there's all sorts of ansi sequences that may move
|
|
our cursor around. */
|
|
for (const wchar_t c: s)
|
|
{
|
|
if (c == L'\n')
|
|
{
|
|
if ((m_cursorPosition.Y() + 1) > m_size.Y()) break; // no room.
|
|
m_cursorPosition.Y(m_cursorPosition.Y() + 1);
|
|
m_cursorPosition.X(0);
|
|
}
|
|
if (c == 0)
|
|
{
|
|
m_cursorPosition.X(m_cursorPosition.X() + 1);
|
|
continue;
|
|
}
|
|
/**
|
|
* @todo parse all possible ansi escape codes and their effects on the
|
|
* cursor's position....yeah,... that'll be fun.
|
|
*/
|
|
const uint32_t x = m_cursorPosition.X();
|
|
const uint32_t y = m_cursorPosition.Y();
|
|
if (y > m_buffer.size() - 1) break;
|
|
if (x > m_buffer[y].size() - 1) break;
|
|
m_buffer[y][x] = c;
|
|
m_cursorPosition.X(x + 1);
|
|
}
|
|
}
|
|
|
|
std::int32_t View::Zorder() const
|
|
{
|
|
return m_zOrder;
|
|
}
|
|
|
|
void View::Zorder(std::int32_t z)
|
|
{
|
|
m_zOrder = z;
|
|
const std::shared_ptr<Application> app = TheApp.lock();
|
|
if (app == nullptr) return; // No application instantiated yet.
|
|
app->CalculateZOrders();
|
|
}
|
|
|
|
} // namespace neovision
|