Add mouse handling + other improvements & bugfixes
This commit is contained in:
parent
50477ab304
commit
c5c23d4176
|
@ -4,3 +4,4 @@
|
||||||
*.sw*
|
*.sw*
|
||||||
mkacct/build/*
|
mkacct/build/*
|
||||||
lib/neovision/build/*
|
lib/neovision/build/*
|
||||||
|
lib/neovision/doc/*
|
||||||
|
|
|
@ -26,10 +26,12 @@ class Application: public View
|
||||||
{
|
{
|
||||||
wchar_t m_bgFillCharacter{L'░'};
|
wchar_t m_bgFillCharacter{L'░'};
|
||||||
int m_ttyFd{0};
|
int m_ttyFd{0};
|
||||||
|
InputParser m_inputParser;
|
||||||
bool m_running{false};
|
bool m_running{false};
|
||||||
std::shared_ptr<Application> m_selfPtr;
|
std::shared_ptr<Application> m_selfPtr;
|
||||||
Terminal m_terminal;
|
Terminal m_terminal;
|
||||||
std::vector<std::unique_ptr<View>> m_views;
|
|
||||||
|
void InitializeViews();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@ -44,14 +46,11 @@ public:
|
||||||
virtual ~Application();
|
virtual ~Application();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Adds a view to the application.
|
* @brief Initializes the application for use.
|
||||||
*
|
*
|
||||||
* The application will take ownership of the view and render it when
|
* You must call this before calling Run().
|
||||||
* appropriate etc,...
|
|
||||||
*
|
|
||||||
* @param view View to add.
|
|
||||||
*/
|
*/
|
||||||
virtual void Add(std::unique_ptr<View> view);
|
virtual void Initialize();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Starts the application.
|
* @brief Starts the application.
|
||||||
|
@ -73,26 +72,22 @@ public:
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Recalculates the z-order of internal views.
|
* @brief Mouse event.
|
||||||
*
|
*
|
||||||
* Called automatically by views when their z-order changes, or when new
|
* Triggers when a mouse event happens.
|
||||||
* views are added.
|
*
|
||||||
|
* @param[in] mouseData Mouse event data gets passed in here.
|
||||||
*/
|
*/
|
||||||
void CalculateZOrders();
|
virtual void OnMouse(const MouseEventData& mouseData);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initialize event.
|
* @brief Key event.
|
||||||
*
|
*
|
||||||
* Initializes all views when Run() is called. Views added after Run() is
|
* Triggers when a keyboard event happens.
|
||||||
* called get initialized as they get added.
|
|
||||||
*
|
*
|
||||||
|
* @param[in] keyData Received keyboard input.
|
||||||
*/
|
*/
|
||||||
virtual void Initialize();
|
virtual void OnKey(const std::wstring& keyData);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Application draw event.
|
|
||||||
*/
|
|
||||||
virtual void OnDraw();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Application resize event.
|
* @brief Application resize event.
|
||||||
|
|
|
@ -41,7 +41,7 @@ class Terminal {
|
||||||
TerminalEventCallback m_onResize;
|
TerminalEventCallback m_onResize;
|
||||||
TerminalEventCallback m_onTerminate;
|
TerminalEventCallback m_onTerminate;
|
||||||
struct sigaction m_resizeSignal{};
|
struct sigaction m_resizeSignal{};
|
||||||
std::chrono::seconds m_ioTimeOut{1};
|
std::chrono::milliseconds m_ioTimeOut{100};
|
||||||
int m_fd{0};
|
int m_fd{0};
|
||||||
|
|
||||||
static void sigIntHandler(int);
|
static void sigIntHandler(int);
|
||||||
|
@ -129,14 +129,14 @@ public:
|
||||||
*
|
*
|
||||||
* @return Returns the current I/O timeout.
|
* @return Returns the current I/O timeout.
|
||||||
*/
|
*/
|
||||||
std::chrono::seconds Timeout() const;
|
std::chrono::milliseconds Timeout() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set IO timeout.
|
* @brief Set IO timeout.
|
||||||
*
|
*
|
||||||
* @param[in] t New IO timeout in seconds.
|
* @param[in] t New IO timeout in seconds.
|
||||||
*/
|
*/
|
||||||
void Timeout(const std::chrono::seconds& t);
|
void Timeout(const std::chrono::milliseconds& t);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Disables buffered i/o in the terminal.
|
* @brief Disables buffered i/o in the terminal.
|
||||||
|
|
|
@ -34,11 +34,14 @@ class View
|
||||||
|
|
||||||
Vector2D m_cursorPosition;
|
Vector2D m_cursorPosition;
|
||||||
bool m_dirty{true};
|
bool m_dirty{true};
|
||||||
|
bool m_initialized{false};
|
||||||
|
std::optional<std::reference_wrapper<View>> m_parent{};
|
||||||
Vector2D m_position;
|
Vector2D m_position;
|
||||||
std::mutex m_positionMutex;
|
std::mutex m_positionMutex;
|
||||||
ViewBuffer m_previousBuffer;
|
ViewBuffer m_previousBuffer;
|
||||||
std::mutex m_previousBufferMutex;
|
std::mutex m_previousBufferMutex;
|
||||||
Vector2D m_size;
|
Vector2D m_size;
|
||||||
|
std::vector<std::unique_ptr<View>> m_views;
|
||||||
std::atomic<std::int32_t> m_zOrder{0};
|
std::atomic<std::int32_t> m_zOrder{0};
|
||||||
|
|
||||||
void DoRender(
|
void DoRender(
|
||||||
|
@ -57,6 +60,24 @@ public:
|
||||||
*/
|
*/
|
||||||
virtual ~View();
|
virtual ~View();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Adds a child view.
|
||||||
|
*
|
||||||
|
* This view will take ownership of the child and render it when
|
||||||
|
* appropriate etc,...
|
||||||
|
*
|
||||||
|
* @param view View to add.
|
||||||
|
*/
|
||||||
|
virtual void Add(std::unique_ptr<View> view);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Recalculates the z-order of child views.
|
||||||
|
*
|
||||||
|
* Called automatically by views when their z-order changes, or when new
|
||||||
|
* views are added.
|
||||||
|
*/
|
||||||
|
void CalculateZOrders();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get cursor position.
|
* @brief Get cursor position.
|
||||||
*
|
*
|
||||||
|
@ -93,6 +114,20 @@ public:
|
||||||
*/
|
*/
|
||||||
bool Dirty() const;
|
bool Dirty() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check initialized.
|
||||||
|
*
|
||||||
|
* @return Returns true if the view has been initialized.
|
||||||
|
*/
|
||||||
|
bool Initialized() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get parent.
|
||||||
|
*
|
||||||
|
* @return Returns an observing pointer to the parent view if there is one.
|
||||||
|
*/
|
||||||
|
std::optional<std::reference_wrapper<View>> Parent();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set position.
|
* @brief Set position.
|
||||||
*
|
*
|
||||||
|
@ -127,8 +162,12 @@ public:
|
||||||
* @brief Set dirty flag true.
|
* @brief Set dirty flag true.
|
||||||
*
|
*
|
||||||
* Forces the view to render next render event.
|
* Forces the view to render next render event.
|
||||||
|
*
|
||||||
|
* @param[in] propagateChildren Marks all child views as dirty as well if
|
||||||
|
* true.
|
||||||
|
* @param[in] propagateParent Marks parent as dirty as well if true.
|
||||||
*/
|
*/
|
||||||
void SetDirty();
|
void SetDirty(bool propagateChildren = true, bool propagateParent = true);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set size.
|
* @brief Set size.
|
||||||
|
@ -211,7 +250,7 @@ protected:
|
||||||
*
|
*
|
||||||
* This runs when the application initializes the view.
|
* This runs when the application initializes the view.
|
||||||
*/
|
*/
|
||||||
virtual void Initialize() = 0;
|
virtual void Initialize();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Draw event.
|
* @brief Draw event.
|
||||||
|
@ -223,7 +262,21 @@ protected:
|
||||||
*
|
*
|
||||||
* Must be implemented when creating a view.
|
* Must be implemented when creating a view.
|
||||||
*/
|
*/
|
||||||
virtual void OnDraw() = 0;
|
virtual void OnDraw();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Key event.
|
||||||
|
*
|
||||||
|
* Triggers when a keyboard event happens.
|
||||||
|
*/
|
||||||
|
virtual void OnKey(const std::wstring&){};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Mouse event.
|
||||||
|
*
|
||||||
|
* Triggers when a mouse event happens.
|
||||||
|
*/
|
||||||
|
virtual void OnMouse(const MouseEventData&){};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Write to the internal view buffer.
|
* @brief Write to the internal view buffer.
|
||||||
|
|
|
@ -15,6 +15,7 @@ class Window: public View
|
||||||
{
|
{
|
||||||
|
|
||||||
std::wstring m_title{L"Window"};
|
std::wstring m_title{L"Window"};
|
||||||
|
std::wstring m_content;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@ -58,6 +59,13 @@ protected:
|
||||||
*/
|
*/
|
||||||
virtual void OnDraw();
|
virtual void OnDraw();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Mouse event.
|
||||||
|
*
|
||||||
|
* Triggers when a mouse event happens.
|
||||||
|
*/
|
||||||
|
virtual void OnMouse(const MouseEventData&);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace neovision
|
} // namespace neovision
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "neovision/ansi.h"
|
#include "neovision/ansi.h"
|
||||||
|
@ -25,64 +26,62 @@ Application::~Application()
|
||||||
if (m_running == true) Stop();
|
if (m_running == true) Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::Add(std::unique_ptr<View> view)
|
|
||||||
{
|
|
||||||
View& viewRef = *view;
|
|
||||||
m_views.emplace_back(std::move(view));
|
|
||||||
CalculateZOrders();
|
|
||||||
if (m_running == true) viewRef.Initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::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();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::Initialize()
|
void Application::Initialize()
|
||||||
{
|
{
|
||||||
|
View::Initialize();
|
||||||
m_terminal.Initialize();
|
m_terminal.Initialize();
|
||||||
IO::Get().SetInputFunction([&](size_t b){
|
IO::Get().SetInputFunction([this](size_t b){
|
||||||
return m_terminal.Read(b);
|
return m_terminal.Read(b);
|
||||||
});
|
});
|
||||||
IO::Get().SetOutputFunction([&](const std::wstring& data){
|
IO::Get().SetOutputFunction([this](const std::wstring& data){
|
||||||
m_terminal.Write(data);
|
m_terminal.Write(data);
|
||||||
});
|
});
|
||||||
for (auto& view: m_views)
|
|
||||||
{
|
|
||||||
view->Initialize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::Run()
|
|
||||||
{
|
|
||||||
ClearScreen();
|
|
||||||
m_running = true;
|
|
||||||
m_terminal.Unbuffer();
|
m_terminal.Unbuffer();
|
||||||
Size(m_terminal.Size()- Vector2D(1, 1), m_bgFillCharacter);
|
Size(m_terminal.Size()- Vector2D(1, 1), m_bgFillCharacter);
|
||||||
m_terminal.SetOnResize([&](){
|
m_terminal.SetOnResize([&](){
|
||||||
const Vector2D newSize = m_terminal.Size() - Vector2D(1, 1);
|
const Vector2D newSize = m_terminal.Size() - Vector2D(1, 1);
|
||||||
Size(newSize, m_bgFillCharacter);
|
Size(newSize, m_bgFillCharacter);
|
||||||
for (const auto& v: m_views)
|
|
||||||
{
|
|
||||||
v->SetDirty();
|
|
||||||
}
|
|
||||||
OnResize(newSize);
|
OnResize(newSize);
|
||||||
});
|
});
|
||||||
m_terminal.SetOnTerminate([&](){
|
m_terminal.SetOnTerminate([&](){
|
||||||
Stop();
|
Stop();
|
||||||
});
|
});
|
||||||
SetMouseReporting(MouseEventMode::ButtonOnly);
|
SetMouseReporting(MouseEventMode::ButtonOnly);
|
||||||
Initialize();
|
m_inputParser.SetOnKeyEvent([this](const std::wstring& k){
|
||||||
InputParser inputParser;
|
OnKey(k);
|
||||||
|
});
|
||||||
|
m_inputParser.SetOnMouseEvent([this](const MouseEventData& m){
|
||||||
|
OnMouse(m);
|
||||||
|
});
|
||||||
|
InitializeViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::InitializeViews()
|
||||||
|
{
|
||||||
|
for (auto& view: m_views)
|
||||||
|
{
|
||||||
|
if (!view) return;
|
||||||
|
if (view->Initialized() != true) view->Initialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::Run()
|
||||||
|
{
|
||||||
|
if (!Initialized())
|
||||||
|
{
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Application::Run() called before "
|
||||||
|
"Application::Initialized() was called."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ClearScreen();
|
||||||
|
m_running = true;
|
||||||
|
InitializeViews();
|
||||||
while (m_running)
|
while (m_running)
|
||||||
{
|
{
|
||||||
m_terminal.ProcessEvents();
|
m_terminal.ProcessEvents();
|
||||||
OnDraw();
|
OnDraw();
|
||||||
inputParser.Read();
|
m_inputParser.Read();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,28 +98,33 @@ Terminal& Application::Term()
|
||||||
return m_terminal;
|
return m_terminal;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::OnDraw()
|
void Application::OnMouse(const MouseEventData& mouseData)
|
||||||
{
|
{
|
||||||
// Let all views render themselves first.
|
/* Figure out what view is clicked. Reverse iterate so the top-most window
|
||||||
for (auto& v: m_views)
|
always receives the click (the array should be sorted in z-order) */
|
||||||
|
for (auto viewIter = m_views.rbegin(); viewIter != m_views.rend();
|
||||||
|
++viewIter)
|
||||||
{
|
{
|
||||||
if (v->Dirty() == true)
|
auto& view = (*viewIter);
|
||||||
|
const std::uint32_t x1 = view->Position().X();
|
||||||
|
const std::uint32_t x2 = x1 + view->Size().X();
|
||||||
|
const std::uint32_t y1 = view->Position().Y();
|
||||||
|
const std::uint32_t y2 = y1 + view->Size().Y();
|
||||||
|
if (
|
||||||
|
(mouseData.position.X() >= x1)
|
||||||
|
&& (mouseData.position.X() <= x2)
|
||||||
|
&& (mouseData.position.Y() >= y1)
|
||||||
|
&& (mouseData.position.Y() <= y2) )
|
||||||
{
|
{
|
||||||
v->OnDraw();
|
view->OnMouse(mouseData);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Render all views (they should be sorted by z-order already) onto ourself.
|
void Application::OnKey(const std::wstring& keyData)
|
||||||
for (auto& v: m_views)
|
{
|
||||||
{
|
if (keyData.empty()) return;
|
||||||
if ((v->Dirty() == true) || (m_dirty == true))
|
|
||||||
{
|
|
||||||
v->Render(*this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we render everything to the screen.
|
|
||||||
Render();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace neovision
|
} // namespace neovision
|
||||||
|
|
|
@ -281,7 +281,6 @@ void InputParser::Read()
|
||||||
/* If we didn't get a mouse event, just fire a key event with the data we
|
/* If we didn't get a mouse event, just fire a key event with the data we
|
||||||
have. */
|
have. */
|
||||||
if (m_onKeyEvent) m_onKeyEvent(ansiParser.ProcessedCharacters());
|
if (m_onKeyEvent) m_onKeyEvent(ansiParser.ProcessedCharacters());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputParser::SetOnKeyEvent(const KeyboardEventCallback& ev)
|
void InputParser::SetOnKeyEvent(const KeyboardEventCallback& ev)
|
||||||
|
@ -430,6 +429,11 @@ void SetMouseReporting(
|
||||||
std::wstring{ansi::MouseTrackMode::VT200} +
|
std::wstring{ansi::MouseTrackMode::VT200} +
|
||||||
ansi::DecPmSuffix::DISABLE
|
ansi::DecPmSuffix::DISABLE
|
||||||
});
|
});
|
||||||
|
outStr += ansi::MakeCsiSequence({
|
||||||
|
ansi::CsiSequence::DECPM +
|
||||||
|
std::wstring{ansi::MouseTrackMode::BTN_EVENT} +
|
||||||
|
ansi::DecPmSuffix::DISABLE
|
||||||
|
});
|
||||||
outStr += ansi::MakeCsiSequence({
|
outStr += ansi::MakeCsiSequence({
|
||||||
ansi::CsiSequence::DECPM +
|
ansi::CsiSequence::DECPM +
|
||||||
std::wstring{ansi::MouseTrackMode::VT200_HIGHLIGHT} +
|
std::wstring{ansi::MouseTrackMode::VT200_HIGHLIGHT} +
|
||||||
|
|
|
@ -79,6 +79,7 @@ void Terminal::sigWinchHandler(int)
|
||||||
void Terminal::ProcessEvents()
|
void Terminal::ProcessEvents()
|
||||||
{
|
{
|
||||||
if (!m_initialized) return;
|
if (!m_initialized) return;
|
||||||
|
if (!m_fd) return;
|
||||||
winsize ws;
|
winsize ws;
|
||||||
if (ioctl(m_fd, TIOCGWINSZ, &ws) == -1)
|
if (ioctl(m_fd, TIOCGWINSZ, &ws) == -1)
|
||||||
{
|
{
|
||||||
|
@ -100,11 +101,12 @@ std::wstring Terminal::Read(size_t n)
|
||||||
while (bytesRead < n)
|
while (bytesRead < n)
|
||||||
{
|
{
|
||||||
const auto now = std::chrono::system_clock::now();
|
const auto now = std::chrono::system_clock::now();
|
||||||
const auto deltaT = std::chrono::duration_cast<std::chrono::seconds>(
|
const auto deltaT = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
now - startTime
|
now - startTime
|
||||||
);
|
);
|
||||||
if (deltaT > m_ioTimeOut) break;
|
if (deltaT > m_ioTimeOut) break;
|
||||||
char buffer[n + 1]{};
|
char buffer[n + 1];
|
||||||
|
memset(buffer, 0, n + 1);
|
||||||
const size_t br = read(m_fd, buffer, n);
|
const size_t br = read(m_fd, buffer, n);
|
||||||
if (br == -1)
|
if (br == -1)
|
||||||
{
|
{
|
||||||
|
@ -152,12 +154,12 @@ void Terminal::SetOnTerminate(TerminalEventCallback callback)
|
||||||
m_onTerminate = callback;
|
m_onTerminate = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::chrono::seconds Terminal::Timeout() const
|
std::chrono::milliseconds Terminal::Timeout() const
|
||||||
{
|
{
|
||||||
return m_ioTimeOut;
|
return m_ioTimeOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Terminal::Timeout(const std::chrono::seconds& t)
|
void Terminal::Timeout(const std::chrono::milliseconds& t)
|
||||||
{
|
{
|
||||||
m_ioTimeOut = t;
|
m_ioTimeOut = t;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,24 @@ View::~View()
|
||||||
m_previousBuffer.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
|
Vector2D View::CursorPosition() const
|
||||||
{
|
{
|
||||||
return m_cursorPosition;
|
return m_cursorPosition;
|
||||||
|
@ -33,6 +51,21 @@ bool View::Dirty() const
|
||||||
return m_dirty;
|
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)
|
void View::Position(const Vector2D& p)
|
||||||
{
|
{
|
||||||
m_position = p;
|
m_position = p;
|
||||||
|
@ -57,12 +90,6 @@ void View::Render()
|
||||||
|
|
||||||
void View::Render(View& view)
|
void View::Render(View& view)
|
||||||
{
|
{
|
||||||
{
|
|
||||||
const std::lock_guard<std::mutex> previousBufferLock(
|
|
||||||
m_previousBufferMutex
|
|
||||||
);
|
|
||||||
m_previousBuffer.clear();
|
|
||||||
}
|
|
||||||
DoRender([&](const std::wstring& output, const Vector2D& p){
|
DoRender([&](const std::wstring& output, const Vector2D& p){
|
||||||
view.WriteAt(p, output);
|
view.WriteAt(p, output);
|
||||||
});
|
});
|
||||||
|
@ -73,48 +100,53 @@ void View::DoRender(
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
const std::lock_guard<std::mutex> bufferLock(m_bufferMutex);
|
const std::lock_guard<std::mutex> bufferLock(m_bufferMutex);
|
||||||
if (!m_dirty) return;
|
/* 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)
|
for (std::uint32_t y = 0; y != m_size.Y() + 1; ++y)
|
||||||
{
|
{
|
||||||
std::wstring lineOut;
|
std::wstring lineOut;
|
||||||
const std::vector<wchar_t>& line = m_buffer[y];
|
const std::vector<wchar_t>& line = m_buffer[y];
|
||||||
size_t lastUpdatedX{0};
|
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)
|
for (std::uint32_t x = 0; x != m_size.X() + 1; ++x)
|
||||||
{
|
{
|
||||||
if (x >= line.size()) break;
|
if (x >= line.size()) break;
|
||||||
const wchar_t newChar = line[x];
|
const wchar_t newChar = line[x];
|
||||||
bool needUpdate{false};
|
const Vector2D pos = m_position + Vector2D(x, y);
|
||||||
if (m_buffer.size() == m_previousBuffer.size())
|
if (y < m_previousBuffer.size())
|
||||||
{
|
{
|
||||||
const std::vector<wchar_t>& pline = m_previousBuffer[y];
|
const std::vector<wchar_t>& pline = m_previousBuffer[y];
|
||||||
if (line.size() == pline.size())
|
if (x < pline.size())
|
||||||
{
|
{
|
||||||
const wchar_t oldChar = pline[x];
|
const wchar_t oldChar = pline[x];
|
||||||
if (newChar != oldChar) needUpdate = true;
|
if (newChar != oldChar)
|
||||||
|
{
|
||||||
|
needUpdate = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
/* If current buffer is wider than the previous buffer,
|
||||||
|
we have nothing to compare to, so just always write. */
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
/* Similarly, if the current buffer is taller than the previous
|
||||||
|
* buffer, always write. */
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
}
|
}
|
||||||
if (needUpdate)
|
// Now write output if an update is needed for this character.
|
||||||
{
|
if (needUpdate) outputFunc(std::wstring{newChar}, pos);
|
||||||
if ((x == 0) || (lastUpdatedX == (x - 1)))
|
|
||||||
{
|
|
||||||
lineOut += newChar;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const Vector2D pos = m_position + Vector2D(x, y);
|
|
||||||
outputFunc(lineOut, pos);
|
|
||||||
lineOut.clear();
|
|
||||||
}
|
|
||||||
lastUpdatedX = x;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!lineOut.empty())
|
if (!lineOut.empty())
|
||||||
{
|
{
|
||||||
|
@ -138,9 +170,49 @@ void View::DoRender(
|
||||||
m_dirty = false;
|
m_dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void View::SetDirty()
|
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;
|
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)
|
void View::Size(const Vector2D& s, wchar_t fill)
|
||||||
|
@ -165,7 +237,7 @@ void View::Size(const Vector2D& s, wchar_t fill)
|
||||||
line.resize(minColBufferSize);
|
line.resize(minColBufferSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_dirty = true;
|
SetDirty(false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector2D View::Size() const
|
Vector2D View::Size() const
|
||||||
|
@ -200,7 +272,7 @@ void View::WriteToBuffer(const Vector2D& p, const std::wstring s)
|
||||||
// a position-write with a blank string still sets position.
|
// a position-write with a blank string still sets position.
|
||||||
m_cursorPosition.X(p.X());
|
m_cursorPosition.X(p.X());
|
||||||
m_cursorPosition.Y(p.Y());
|
m_cursorPosition.Y(p.Y());
|
||||||
m_dirty = true;
|
SetDirty();
|
||||||
if (s.empty()) return;
|
if (s.empty()) return;
|
||||||
/* We're taking this one character at the time, because things get
|
/* 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
|
complicated given that there's all sorts of ansi sequences that may move
|
||||||
|
|
|
@ -13,6 +13,7 @@ Window::Window(): View()
|
||||||
|
|
||||||
void Window::Initialize()
|
void Window::Initialize()
|
||||||
{
|
{
|
||||||
|
View::Initialize();
|
||||||
/* If we have an application, we try to set our size to something reasonable
|
/* If we have an application, we try to set our size to something reasonable
|
||||||
to begin with - otherwise all windows would default to {0,0} and be
|
to begin with - otherwise all windows would default to {0,0} and be
|
||||||
invisible by default. */
|
invisible by default. */
|
||||||
|
@ -68,11 +69,19 @@ void Window::OnDraw()
|
||||||
const std::uint32_t titlePos = winMid - titleMid;
|
const std::uint32_t titlePos = winMid - titleMid;
|
||||||
WriteAt({titlePos, y1}, titleStr);
|
WriteAt({titlePos, y1}, titleStr);
|
||||||
// Contents
|
// Contents
|
||||||
for (std::uint32_t y = (y1 + 1); y < y2 ; ++y)
|
for (std::uint32_t y = y1 + 1; y < y2 ; ++y)
|
||||||
{
|
{
|
||||||
const std::wstring line(x2 - x1 - 1, L' ');
|
const std::wstring line(x2 - x1 - 1, L' ');
|
||||||
WriteAt({x1 + 1, y}, line);
|
WriteAt({x1 + 1, y}, line);
|
||||||
}
|
}
|
||||||
|
WriteAt({x1 + 1, y1 + 1}, m_content);
|
||||||
|
View::OnDraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::OnMouse(const MouseEventData&)
|
||||||
|
{
|
||||||
|
m_content = L"Click!";
|
||||||
|
SetDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::wstring Window::Title() const
|
std::wstring Window::Title() const
|
||||||
|
|
|
@ -13,6 +13,8 @@ public:
|
||||||
|
|
||||||
Application(std::vector<std::string> args);
|
Application(std::vector<std::string> args);
|
||||||
|
|
||||||
|
virtual ~Application() = default;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace pubnix
|
} // namespace pubnix
|
||||||
|
|
|
@ -17,11 +17,13 @@ int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
std::vector<std::string> args(argv + 1, argv + argc);
|
std::vector<std::string> args(argv + 1, argv + argc);
|
||||||
pubnix::Application app(args);
|
pubnix::Application app(args);
|
||||||
|
app.Initialize();
|
||||||
auto mainWindow = std::make_unique<neovision::Window>();
|
auto mainWindow = std::make_unique<neovision::Window>();
|
||||||
mainWindow->Position({10,10});
|
mainWindow->Position({10, 10});
|
||||||
|
auto otherWindow = std::make_unique<neovision::Window>();
|
||||||
|
otherWindow->Position({20, 20});
|
||||||
app.Add(std::move(mainWindow));
|
app.Add(std::move(mainWindow));
|
||||||
|
app.Add(std::move(otherWindow));
|
||||||
app.Run();
|
app.Run();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue