view and stuff

This commit is contained in:
John Sennesael 2021-08-17 19:36:56 -05:00
parent eb4c9cdb85
commit 72b9df3052
14 changed files with 817 additions and 280 deletions

View File

@ -11,8 +11,10 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
add_library(neovisionObj OBJECT
"src/ansi.cpp"
"src/application.cpp"
"src/crt.cpp"
"src/term.cpp"
"src/view.cpp"
)
target_include_directories(neovisionObj

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,60 @@
#pragma once
#include <memory>
#include "neovision/view.h"
#include "neovision/crt.h"
#include "neovision/term.h"
namespace neovision {
/**
* Main application class.
*
* Create a class derrived from this one to serve as your main application
* class.
*
*/
class Application: public View
{
wchar_t m_bgFillCharacter{L''};
int m_ttyFd{0};
bool m_running{false};
std::shared_ptr<Application> m_selfPtr;
Terminal m_terminal;
public:
Application();
virtual ~Application();
virtual void Run();
virtual void Stop();
/**
* @brief Get terminal.
*
* @return Returns a reference to the terminal instance.
*/
Terminal& Term();
protected:
virtual void OnDraw();
virtual void OnResize(const Vector2D&){};
};
/**
* @brief Global Application pointer.
*
* This gets assigned as soon as the Application class is instantiated.
*
* It's primary reason to exist is that some low-level ioctl callback functions
* need to have access to the application, and the api does not allow for
* passing parameters to the callback functions, which means, unfortunately,
* that we have to rely on a global. That said- it's available for other use.
*/
extern std::weak_ptr<Application> TheApp;
} // namespace neovision

View File

@ -33,26 +33,37 @@ enum class StandardColor {
};
/**
* @brief Screen position coordinates.
* @brief 2d vector holding a x and y values.
*
* Holds the column (X) and row (Y) of a screen position.
* Typically holds the columns (X) and rows (Y) of a screen position or size.
*/
class Position {
class Vector2D {
std::int32_t m_x{};
std::int32_t m_y{};
public:
/**
* Default constructor.
*/
Vector2D() = default;
/**
* Constructor with initial values.
*/
Position(std::int32_t x, std::int32_t y);
Vector2D(std::int32_t x, std::int32_t y);
/**
* Constructor with unsigned initial values.
*/
Position(std::uint32_t x, std::uint32_t y);
Vector2D(std::uint32_t x, std::uint32_t y);
/**
* @brief Get value as string.
* @return Returns the x and y coordinates in the form of "x,y"
*/
std::string Str() const;
/**
* @brief Get X.
@ -78,11 +89,11 @@ public:
*/
void Y(std::int32_t n);
Position operator+(const Position& other) const;
Position operator+=(const Position& other);
void operator=(const Position& other);
bool operator==(const Position& other) const;
bool operator!=(const Position& other) const;
Vector2D operator+(const Vector2D& other) const;
Vector2D operator+=(const Vector2D& other);
void operator=(const Vector2D& other);
bool operator==(const Vector2D& other) const;
bool operator!=(const Vector2D& other) const;
};
@ -105,8 +116,8 @@ class IO {
std::mutex m_inputMutex;
std::mutex m_outputMutex;
std::reference_wrapper<std::istream> m_inputStream;
std::reference_wrapper<std::ostream> m_outputStream;
std::reference_wrapper<std::wistream> m_inputStream;
std::reference_wrapper<std::wostream> m_outputStream;
public:
@ -144,7 +155,7 @@ public:
*
* @return Returns a reference to the current input stream.
*/
std::reference_wrapper<std::istream> InputStream();
std::reference_wrapper<std::wistream> InputStream();
/**
* Lock input stream.
@ -172,7 +183,7 @@ public:
*
* @return Returns a reference to the current output stream.
*/
std::reference_wrapper<std::ostream> OutputStream();
std::reference_wrapper<std::wostream> OutputStream();
/**
* @brief Reads from the current input stream.
@ -182,7 +193,7 @@ public:
* @param bytes Number of bytes to read off of the stream.
* @return Returns the data read from the stream.
*/
std::string Read(std::size_t bytes);
std::wstring Read(std::size_t bytes);
/**
* @brief Reads from the current input stream.
@ -193,7 +204,7 @@ public:
* @param c Character to read up to.
* @return Returns the data read from the stream.
*/
std::string ReadUntil(char c);
std::wstring ReadUntil(wchar_t c);
/**
* @brief Sets the current input stream.
@ -203,7 +214,7 @@ public:
*
* @param stream The new input stream to be used for text output.
*/
void SetInputStream(std::istream& stream);
void SetInputStream(std::wistream& stream);
/**
* @brief Sets the current output stream.
@ -213,7 +224,7 @@ public:
*
* @param stream The new output stream to be used for text output.
*/
void SetOutputStream(std::ostream& stream);
void SetOutputStream(std::wostream& stream);
/**
* Unlock input stream.
@ -233,14 +244,13 @@ public:
void UnlockOutputStream();
/**
* @brief Write output.
* @brief Write output (wide version).
*
* Writes given string to the configured output stream.
*
* @param str String to write to output stream.
*/
void Write(const std::string& str);
void Write(const std::wstring& str);
};
// Functions -------------------------------------------------------------------
@ -266,7 +276,7 @@ void ClearScreen();
*
* @return Returns the current cursor's screen coordinates.
*/
Position GetCursorPos();
Vector2D GetCursorPos();
/**
* @brief Sets the cursor position.
@ -279,7 +289,7 @@ Position GetCursorPos();
* @param relative (optional, default false) Moves cursor relative to current
* position if true. Otherwise, absolute coordinates are used.
*/
void SetCursorPos(const Position& pos, bool relative = false);
void SetCursorPos(const Vector2D& pos, bool relative = false);
/**
* @brief Sets background color (16 color version).
@ -337,7 +347,7 @@ void SetForegroundColor(std::uint8_t r, std::uint8_t g, std::uint8_t b);
* @param c Color to convert.
* @return Color as ansi background color SGR code.
*/
std::string ToAnsiBgColor(StandardColor c);
std::wstring ToAnsiBgColor(StandardColor c);
/**
* Convert Color enum to ANSI foreground color code.
@ -345,6 +355,6 @@ std::string ToAnsiBgColor(StandardColor c);
* @param c Color to convert.
* @return Color as ansi foreground color SGR code.
*/
std::string ToAnsiFgColor(StandardColor c);
std::wstring ToAnsiFgColor(StandardColor c);
} // namespace neovision

View File

@ -1,34 +1,64 @@
#pragma once
#include <signal.h>
#include <termios.h>
#include <unordered_map>
namespace neovision {
class Application;
class Vector2D;
/**
* @brief Terminal event callback.
*
* This function signature is used for registering terminal event callbacks.
* The application is passed as a convenience, as anything that needs to be
* accessible within the function can be wired into the application class.
*/
typedef std::function<void(Application& app)> TerminalEventCallback;
/**
* Class for terminal i/o operations.
*
* Any terminal setting changes made during the lifetime of the class will be
* restored to their previous state when the instance is destroyed.
*
* While you can use this class on it's own, it is sort-of assumed that the
* Terminal is instantiated by the Application class.
* Specifically, the callback functions rely on the 'TheApp' pointer being
* set in order to dispatch their events.
*
* @todo this is going to require platform-specific split implementations.
*/
class Terminal {
std::unordered_map<int, termios> m_savedTerminalSettings{};
TerminalEventCallback m_onResize;
struct sigaction m_resizeSignal{};
static void sigWinchHandler(int);
public:
/**
* Constructor.
*/
Terminal() = default;
Terminal();
/**
* Destructor.
* @brief Destructor.
*/
~Terminal();
/**
* @brief Processes terminal events.
*
* Checks for ioctl signals, triggers callbacks as-needed.
*
* @param fd File descriptor on which to check for signals.
*/
void ProcessEvents(int fd);
/**
* @brief Restores terminal settings back to their original state.
*
@ -37,6 +67,24 @@ public:
*/
void Restore(int fd);
/**
* @brief Sets the on-resize callback function.
*
* This function gets executed when the terminal gets resized.
*
* @param callback Function to be executed when the terminal gets resized.
*/
void SetOnResize(TerminalEventCallback callback);
/**
* @brief Get the terminal size.
*
* @param fd File descriptor of stream to get terminal size for.
* @return Returns the terminal size as a Position with X representing the
* number of columns, and Y the number of rows.
*/
Vector2D Size(int fd);
/**
* @brief Disables buffered i/o in the terminal.
*

View File

@ -0,0 +1,155 @@
#pragma once
#include <vector>
#include "neovision/crt.h"
namespace neovision {
class Application;
/**
* @class Represents a renderable view area.
*
* This is the base class for anything within neovision that can be drawn onto
* the screen.
*
* Each view holds it's own buffer - all writes to a view go to it's internal
* buffer. The buffer is only drawn to the screen when the Draw event is called
* (typically by the Application instance, which is also a view in itself).
*
*/
class View
{
typedef std::vector<std::vector<wchar_t>> ViewBuffer;
ViewBuffer m_buffer;
Vector2D m_cursorPosition;
Vector2D m_position;
ViewBuffer m_previousBuffer;
Vector2D m_size;
public:
/**
* @brief Default constructor.
*/
View() = default;
/**
* @brief Destructor.
*/
virtual ~View() = default;
/**
* @brief Get cursor position.
*
* Gets the current cursor position within the view area.
*
* @note Coordinates are relative to the view area, not the screen. In fact,
* a view's cursor has no relation to the terminal's cursor at all. It just
* determines where text will go when the Write-family functions are called
* without an explicit position.
*
* @return Returns the current cursor position.
*/
Vector2D CursorPosition() const;
/**
* @brief Set cursor position.
*
* Sets the current position witin the view area.
*
* @note As with the CursorPosition() getter, coordinates are relative, and
* there is no relation to the terminal cursor.
*
* @param pos New cursor position.
*/
void CursorPosition(const Vector2D& pos);
/**
* @brief Set position.
*
* @param p New area position.
*/
void Position(const Vector2D& p);
/**
* @brief Get position.
*
* @return Returns the area's position.
*/
Vector2D Position() const;
/**
* @brief Render buffer.
*
* Writes the buffer to the current IO stream.
*/
void Render();
/**
* @brief Set size.
*
* @param s New area size.
* @param fill Fill character for newly created blank space (if growing).
* (optional - defaults to spaces.)
*/
void Size(const Vector2D& s, wchar_t fill = L' ');
/**
* @brief Get size.
*
* @return Returns the area size.
*/
Vector2D Size() const;
/**
* @brief Write text.
*
* Writes a wide string to the view buffer at the current cursor
* position.
*
* @param s String to write.
*/
void Write(const std::wstring& s);
/**
* @brief Write text at specific position.
*
* Writes a wide string to the view buffer at the specified position. The
* cursor position will be updated to wherever the last character of the
* string ends up.
*
* @param p Position to write string at.
* @param s String to write.
*/
void WriteAt(const Vector2D& p, const std::wstring& s);
/**
* @brief Write text line.
*
* Writes a wide string to the view buffer at the current cursor position
* and moves to the cursor to the beginning of the next line.
*
* @param s String to write.
*/
void WriteLn(const std::wstring& s);
protected:
/**
* @brief Write to the internal view buffer.
*
* All the various Write() functions ultimately call this.
* Override this if you want to make a custom view writer.
*
* @param p Position in buffer to write to.
* @param s String to be written into the buffer.
*/
virtual void WriteToBuffer(const Vector2D& p, const std::wstring s);
};
} // namespace neovision

View File

@ -6,54 +6,47 @@
namespace neovision {
namespace ansi {
std::string MakeEscapeSequence(const std::string& sequence)
std::wstring MakeEscapeSequence(const std::wstring& sequence)
{
return ControlCharacter::ESC + sequence;
}
std::string MakeCssiSequence(const char sequence)
std::wstring MakeCssiSequence(const wchar_t sequence)
{
return MakeEscapeSequence(EscapeSequence::CSSI + sequence);
}
/*
std::string MakeCsiSequence(const std::string& sequence)
std::wstring MakeCsiSequence(const std::vector<std::wstring>& sequenceItems)
{
return MakeEscapeSequence(EscapeSequence::CSI + sequence);
}
*/
std::string MakeCsiSequence(const std::vector<std::string>& sequenceItems)
{
std::string result;
std::wstring result;
if (!sequenceItems.empty())
{
for (const std::string& item: sequenceItems)
for (const std::wstring& item: sequenceItems)
{
result += item + ";";
result += item + L";";
}
result.pop_back();
}
return MakeEscapeSequence(EscapeSequence::CSI + result);
}
std::string MakeDecPmSequence(const char sequence, const char suffix)
std::wstring MakeDecPmSequence(const std::wstring& sequence, const std::wstring& suffix)
{
return MakeCsiSequence({
std::string{CsiSequence::DECPM},
std::string{sequence},
std::string{suffix}
std::wstring{CsiSequence::DECPM},
sequence,
suffix
});
}
std::string MakeSgrSequence(const std::vector<std::string>& sequenceItems)
std::wstring MakeSgrSequence(const std::vector<std::wstring>& sequenceItems)
{
std::string result;
std::wstring result;
if (!sequenceItems.empty())
{
for (const std::string& item: sequenceItems)
for (const std::wstring& item: sequenceItems)
{
result += item + ";";
result += item + L";";
}
result.pop_back();
}

View File

@ -0,0 +1,77 @@
#include <unistd.h>
#include "neovision/ansi.h"
#include "neovision/application.h"
namespace neovision {
std::weak_ptr<Application> TheApp;
/* There's a bit of evil voodoo going on where with the TheApp pointer.
Technically we don't "own" the TheApp pointer, the caller who instantiates
us does - yet we set the TheApp pointer (a weak (observing) pointer) -
in order to not force the user to do it. In order to do this, we create a
shared 'this' ptr with a custom no-op Deleter (to avoid a double-free, since
we get deleted already by the caller). */
Application::Application(): View(), m_selfPtr(this, [](Application*){})
{
TheApp = m_selfPtr;
}
Application::~Application()
{
if (m_running) Stop();
}
void Application::Run()
{
/*
IO::Get().Write(ansi::MakeDecPmSequence(ansi::DecPmSequence::DECAWM,
ansi::DecPmSuffix::DISABLE
) + ansi::MakeDecPmSequence(ansi::DecPmSequence::DECTECM,
ansi::DecPmSuffix::DISABLE
));
*/
ClearScreen();
m_terminal.Unbuffer(STDIN_FILENO);
m_running = true;
Size(m_terminal.Size(STDIN_FILENO), m_bgFillCharacter);
m_terminal.SetOnResize([&](Application&){
const Vector2D newSize = m_terminal.Size(STDIN_FILENO);
ClearScreen();
Size(newSize, m_bgFillCharacter);
OnResize(newSize);
});
while (m_running)
{
m_terminal.ProcessEvents(STDIN_FILENO);
OnDraw();
}
}
void Application::Stop()
{
/*
IO::Get().Write(ansi::MakeDecPmSequence(ansi::DecPmSequence::DECAWM,
ansi::DecPmSuffix::ENABLE
) + ansi::MakeDecPmSequence(ansi::DecPmSequence::DECTECM,
ansi::DecPmSuffix::ENABLE
));
*/
IO::Get().Write(ansi::MakeEscapeSequence(ansi::EscapeSequence::RIS));
m_running = false;
}
Terminal& Application::Term()
{
return m_terminal;
}
void Application::OnDraw()
{
Render();
sleep(1);
}
} // namespace neovision

View File

@ -2,6 +2,7 @@
#include <cstdio>
#include <functional>
#include <iostream>
#include <locale>
#include <memory>
#include <mutex>
#include <regex>
@ -15,68 +16,79 @@ namespace neovision {
// Position struct -------------------------------------------------------------
Position::Position(std::int32_t x, std::int32_t y): m_x{x}, m_y{y}
Vector2D::Vector2D(std::int32_t x, std::int32_t y): m_x{x}, m_y{y}
{
}
Position::Position(std::uint32_t x, std::uint32_t y)
Vector2D::Vector2D(std::uint32_t x, std::uint32_t y)
{
m_x = x;
m_y = y;
}
std::int32_t Position::X() const
std::string Vector2D::Str() const
{
return std::to_string(m_x) + "," + std::to_string(m_y);
}
std::int32_t Vector2D::X() const
{
return m_x;
}
void Position::X(std::int32_t n)
void Vector2D::X(std::int32_t n)
{
m_x = n;
}
std::int32_t Position::Y() const
std::int32_t Vector2D::Y() const
{
return m_y;
}
void Position::Y(std::int32_t n)
void Vector2D::Y(std::int32_t n)
{
m_y = n;
}
Position Position::operator+(const Position& other) const
Vector2D Vector2D::operator+(const Vector2D& other) const
{
return Position(m_x + other.m_x, m_y + other.m_y);
return Vector2D(m_x + other.m_x, m_y + other.m_y);
}
Position Position::operator+=(const Position& other)
Vector2D Vector2D::operator+=(const Vector2D& other)
{
m_x += other.m_x;
m_y += other.m_y;
return *this;
}
void Position::operator=(const Position& other)
void Vector2D::operator=(const Vector2D& other)
{
m_x = other.m_x;
m_y = other.m_y;
}
bool Position::operator==(const Position& other) const
bool Vector2D::operator==(const Vector2D& other) const
{
return (m_x == other.m_x) && (m_y == other.m_y);
}
bool Position::operator!=(const Position& other) const
bool Vector2D::operator!=(const Vector2D& other) const
{
return !((m_x == other.m_x) && (m_y == other.m_y));
}
// IO class --------------------------------------------------------------------
IO::IO(): m_inputStream{std::ref(std::cin)}, m_outputStream{std::ref(std::cout)}
IO::IO():
m_inputStream{std::ref(std::wcin)}, m_outputStream{std::ref(std::wcout)}
{
m_inputStream.get().setf(std::ios::unitbuf);
m_outputStream.get().setf(std::ios::unitbuf);
/// @todo - this should probably be aware of the system locale.
std::locale::global(std::locale("en_US.utf8"));
std::wcout.imbue(std::locale());
}
IO& IO::Get()
@ -85,7 +97,7 @@ IO& IO::Get()
return instance;
}
std::reference_wrapper<std::istream> IO::InputStream()
std::reference_wrapper<std::wistream> IO::InputStream()
{
return m_inputStream;
}
@ -100,26 +112,26 @@ void IO::LockOutputStream()
m_inputMutex.unlock();
}
std::reference_wrapper<std::ostream> IO::OutputStream()
std::reference_wrapper<std::wostream> IO::OutputStream()
{
return m_outputStream;
}
std::string IO::Read(std::size_t bytes)
std::wstring IO::Read(std::size_t bytes)
{
const std::lock_guard<std::mutex> lock(m_inputMutex);
std::vector<char> buffer(bytes);
char charBuffer[bytes];
std::vector<wchar_t> buffer(bytes);
wchar_t charBuffer[bytes];
m_inputStream.get().rdbuf()->pubsetbuf(charBuffer, bytes);
m_inputStream.get().read(&buffer[0], bytes);
return std::string(buffer.begin(), buffer.end());
return std::wstring(buffer.begin(), buffer.end());
}
std::string IO::ReadUntil(char c)
std::wstring IO::ReadUntil(wchar_t c)
{
const std::lock_guard<std::mutex> lock(m_inputMutex);
char character;
std::string buffer;
wchar_t character;
std::wstring buffer;
while (m_inputStream.get().get(character))
{
buffer += character;
@ -128,16 +140,18 @@ std::string IO::ReadUntil(char c)
return buffer;
}
void IO::SetInputStream(std::istream& stream)
void IO::SetInputStream(std::wistream& stream)
{
const std::lock_guard<std::mutex> lock(m_inputMutex);
m_inputStream = std::ref(stream);
m_inputStream.get().setf(std::ios::unitbuf);
}
void IO::SetOutputStream(std::ostream& stream)
void IO::SetOutputStream(std::wostream& stream)
{
const std::lock_guard<std::mutex> lock(m_outputMutex);
m_outputStream = std::ref(stream);
m_outputStream.get().setf(std::ios::unitbuf);
}
void IO::UnlockInputStream()
@ -150,7 +164,7 @@ void IO::UnlockOutputStream()
m_outputMutex.unlock();
}
void IO::Write(const std::string& str)
void IO::Write(const std::wstring& str)
{
const std::lock_guard<std::mutex> lock(m_outputMutex);
m_outputStream.get() << str;
@ -160,18 +174,18 @@ void IO::Write(const std::string& str)
void ClearScreen() {
IO::Get().Write(ansi::MakeCsiSequence({
std::string{"2"}, std::string{ansi::CsiSequence::ED}
std::wstring{L"2"}, std::wstring{ansi::CsiSequence::ED}
}));
}
Position GetCursorPos()
Vector2D GetCursorPos()
{
static const std::regex getCursorPosRegex{"^\x1b\\[(\\d+);(\\d+)R$"};
static const std::wregex getCursorPosRegex{L"^\x1b\\[(\\d+);(\\d+)R$"};
IO::Get().Write(ansi::MakeCsiSequence({
std::string{"6"}, std::string{ansi::CsiSequence::DSR}
std::wstring{L"6"}, std::wstring{ansi::CsiSequence::DSR}
}));
std::string response = IO::Get().ReadUntil('R');
std::smatch responseMatches;
std::wstring response = IO::Get().ReadUntil(L'R');
std::wsmatch responseMatches;
if (!std::regex_match(
response, responseMatches, getCursorPosRegex
))
@ -191,9 +205,9 @@ Position GetCursorPos()
return {x, y};
}
void SetCursorPos(const Position& pos, bool relative)
void SetCursorPos(const Vector2D& pos, bool relative)
{
Position newPos(pos);
Vector2D newPos(pos);
if (relative)
{
newPos += GetCursorPos();
@ -201,8 +215,8 @@ void SetCursorPos(const Position& pos, bool relative)
}
IO::Get().Write(
ansi::MakeCsiSequence({
std::to_string(newPos.Y()),
std::to_string(newPos.X()),
std::to_wstring(newPos.Y()),
std::to_wstring(newPos.X()),
ansi::CsiSequence::HVP
})
);
@ -220,10 +234,10 @@ void SetBackgroundColor(std::uint8_t r, std::uint8_t g, std::uint8_t b)
IO::Get().Write(
ansi::MakeSgrSequence({
ansi::SgrSequence::SET_BG_COLOR,
"2",
std::to_string(r),
std::to_string(g),
std::to_string(b)
L"2",
std::to_wstring(r),
std::to_wstring(g),
std::to_wstring(b)
})
);
}
@ -240,15 +254,15 @@ void SetForegroundColor(std::uint8_t r, std::uint8_t g, std::uint8_t b)
IO::Get().Write(
ansi::MakeSgrSequence({
ansi::SgrSequence::SET_FG_COLOR,
"2",
std::to_string(r),
std::to_string(g),
std::to_string(b)
L"2",
std::to_wstring(r),
std::to_wstring(g),
std::to_wstring(b)
})
);
}
std::string ToAnsiBgColor(StandardColor c)
std::wstring ToAnsiBgColor(StandardColor c)
{
switch(c)
{
@ -287,7 +301,7 @@ std::string ToAnsiBgColor(StandardColor c)
}
}
std::string ToAnsiFgColor(StandardColor c)
std::wstring ToAnsiFgColor(StandardColor c)
{
switch(c)
{

View File

@ -1,10 +1,29 @@
#include <cstring>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#include "neovision/application.h"
#include "neovision/crt.h"
#include "neovision/term.h"
namespace neovision {
Terminal::Terminal()
{
// Register signal handlers.
sigemptyset(&m_resizeSignal.sa_mask);
m_resizeSignal.sa_flags = SA_RESTART;
m_resizeSignal.sa_handler = sigWinchHandler;
if (sigaction(SIGWINCH, &m_resizeSignal, NULL) == -1)
{
throw std::runtime_error(
"Could not register terminal resize signal handler."
);
}
}
Terminal::~Terminal()
{
// Restore terminal settings for each file descriptor on exit.
@ -14,6 +33,31 @@ Terminal::~Terminal()
}
}
void Terminal::sigWinchHandler(int)
{
/* This is a c-callback invoked when the SIGWINCH signal is received.
We call the actual c++ callback from here if one is registred. */
const std::shared_ptr<Application> app = TheApp.lock();
if (app == nullptr) return; // No application instantiated yet.
const Terminal& term = app->Term();
if (term.m_onResize)
{
term.m_onResize(*app);
}
}
void Terminal::ProcessEvents(int fd)
{
winsize ws;
if (ioctl(fd, TIOCGWINSZ, &ws) == -1)
{
throw std::runtime_error(
std::string{"Error while checking for terminal resize event: "} +
std::strerror(errno) + " (" + std::to_string(errno) + ")"
);
}
}
void Terminal::Restore(int fd)
{
if (m_savedTerminalSettings.find(fd) == m_savedTerminalSettings.end())
@ -24,6 +68,18 @@ void Terminal::Restore(int fd)
tcsetattr(fd, TCSANOW, &m_savedTerminalSettings[fd]);
}
Vector2D Terminal::Size(int fd)
{
winsize w{};
ioctl(fd, TIOCGWINSZ, &w);
return {w.ws_col, w.ws_row};
}
void Terminal::SetOnResize(TerminalEventCallback callback)
{
m_onResize = callback;
}
void Terminal::Unbuffer(int fd)
{
termios old_tio{};

145
lib/neovision/src/view.cpp Normal file
View File

@ -0,0 +1,145 @@
#include <codecvt>
#include <locale>
#include <string>
#include "neovision/ansi.h"
#include "neovision/application.h"
#include "neovision/crt.h"
#include "neovision/view.h"
namespace neovision {
Vector2D View::CursorPosition() const
{
return m_cursorPosition;
}
void View::CursorPosition(const Vector2D& pos)
{
m_cursorPosition = pos;
}
void View::Position(const Vector2D& p)
{
m_position = p;
}
Vector2D View::Position() const
{
return m_position;
}
void View::Render()
{
SetCursorPos(m_position);
for (std::uint32_t y = 0; y != m_buffer.size(); ++y)
{
std::wstring lineOut;
const std::vector<wchar_t>& line = m_buffer[y];
size_t lastUpdatedX{0};
for (std::uint32_t x = 0; x < line.size(); ++x)
{
const wchar_t newChar = line[x];
bool needUpdate{false};
if (y < m_previousBuffer.size())
{
const std::vector<wchar_t>& pline = m_previousBuffer[y];
if (pline.size() >= x)
{
const wchar_t oldChar = pline[x];
if (newChar != oldChar) needUpdate = true;
}
}
else
{
needUpdate = true;
}
if (needUpdate)
{
if ((x == 0) || (lastUpdatedX == (x - 1)))
{
lineOut += newChar;
}
else
{
const Vector2D newPos = m_position + Vector2D(x, y);
SetCursorPos(newPos);
IO::Get().Write(lineOut);
lineOut.clear();
}
lastUpdatedX = x;
}
}
if (!lineOut.empty())
{
IO::Get().Write(lineOut);
}
}
m_previousBuffer = m_buffer;
}
void View::Size(const Vector2D& s, wchar_t fill)
{
m_size = s;
m_buffer.resize(s.Y());
for (std::vector<wchar_t>& line: m_buffer)
{
line.resize(s.X(), fill);
}
m_previousBuffer.clear();
}
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)
{
if (m_buffer.empty()) return;
// First set cursor position, bounded by our size.
m_cursorPosition.X( p.X() > m_size.X() ? m_size.X() : p.X() );
m_cursorPosition.Y( p.Y() > m_size.Y() ? m_size.Y() : p.Y() );
// a position-write with a blank string still sets position.
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);
}
/**
* @todo parse all possible ansi escape codes and their effects on the
* cursor's position....yeah,... that'll be fun.
*/
if ((m_cursorPosition.X() + 1) > m_size.X()) break; // no room.
m_cursorPosition.X(m_cursorPosition.X() + 1);
m_buffer[m_cursorPosition.Y()][m_cursorPosition.X()] = c;
}
}
} // namespace neovision

View File

@ -3,9 +3,11 @@
#include <string>
#include <vector>
#include "neovision/application.h"
namespace pubnix {
class Application
class Application: public neovision::Application
{
public:

View File

@ -5,9 +5,10 @@
namespace pubnix {
Application::Application(std::vector<std::string> args)
Application::Application(std::vector<std::string> args):
neovision::Application()
{
(void)args;
}
} // namespace pubnix

View File

@ -1,4 +1,4 @@
#include <unistd.h>
#include <string>
#include <vector>
@ -16,24 +16,8 @@ using namespace neovision;
int main(int argc, char* argv[])
{
Terminal term;
term.Unbuffer(STDIN_FILENO);
ClearScreen();
for (int y = 0; y != 21 ; ++y)
{
for (int x = 0 ; x != 80 ; ++x)
{
SetCursorPos({x, y});
SetBackgroundColor( x, 0, 0 );
SetForegroundColor( 0, y*5, y*5 );
IO::Get().Write("x");
}
}
std::vector<std::string> args(argv + 1, argv + argc);
pubnix::Application app(args);
app.Run();
return 0;
}