Add mouse handling + other improvements & bugfixes
This commit is contained in:
parent
50477ab304
commit
c5c23d4176
|
@ -4,3 +4,4 @@
|
|||
*.sw*
|
||||
mkacct/build/*
|
||||
lib/neovision/build/*
|
||||
lib/neovision/doc/*
|
||||
|
|
|
@ -26,10 +26,12 @@ class Application: public View
|
|||
{
|
||||
wchar_t m_bgFillCharacter{L'░'};
|
||||
int m_ttyFd{0};
|
||||
InputParser m_inputParser;
|
||||
bool m_running{false};
|
||||
std::shared_ptr<Application> m_selfPtr;
|
||||
Terminal m_terminal;
|
||||
std::vector<std::unique_ptr<View>> m_views;
|
||||
|
||||
void InitializeViews();
|
||||
|
||||
public:
|
||||
|
||||
|
@ -44,14 +46,11 @@ public:
|
|||
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
|
||||
* appropriate etc,...
|
||||
*
|
||||
* @param view View to add.
|
||||
* You must call this before calling Run().
|
||||
*/
|
||||
virtual void Add(std::unique_ptr<View> view);
|
||||
virtual void Initialize();
|
||||
|
||||
/**
|
||||
* @brief Starts the application.
|
||||
|
@ -73,26 +72,22 @@ public:
|
|||
protected:
|
||||
|
||||
/**
|
||||
* @brief Recalculates the z-order of internal views.
|
||||
* @brief Mouse event.
|
||||
*
|
||||
* Called automatically by views when their z-order changes, or when new
|
||||
* views are added.
|
||||
* Triggers when a mouse event happens.
|
||||
*
|
||||
* @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
|
||||
* called get initialized as they get added.
|
||||
* Triggers when a keyboard event happens.
|
||||
*
|
||||
* @param[in] keyData Received keyboard input.
|
||||
*/
|
||||
virtual void Initialize();
|
||||
|
||||
/**
|
||||
* @brief Application draw event.
|
||||
*/
|
||||
virtual void OnDraw();
|
||||
virtual void OnKey(const std::wstring& keyData);
|
||||
|
||||
/**
|
||||
* @brief Application resize event.
|
||||
|
|
|
@ -41,7 +41,7 @@ class Terminal {
|
|||
TerminalEventCallback m_onResize;
|
||||
TerminalEventCallback m_onTerminate;
|
||||
struct sigaction m_resizeSignal{};
|
||||
std::chrono::seconds m_ioTimeOut{1};
|
||||
std::chrono::milliseconds m_ioTimeOut{100};
|
||||
int m_fd{0};
|
||||
|
||||
static void sigIntHandler(int);
|
||||
|
@ -129,14 +129,14 @@ public:
|
|||
*
|
||||
* @return Returns the current I/O timeout.
|
||||
*/
|
||||
std::chrono::seconds Timeout() const;
|
||||
std::chrono::milliseconds Timeout() const;
|
||||
|
||||
/**
|
||||
* @brief Set IO timeout.
|
||||
*
|
||||
* @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.
|
||||
|
|
|
@ -34,11 +34,14 @@ class View
|
|||
|
||||
Vector2D m_cursorPosition;
|
||||
bool m_dirty{true};
|
||||
bool m_initialized{false};
|
||||
std::optional<std::reference_wrapper<View>> m_parent{};
|
||||
Vector2D m_position;
|
||||
std::mutex m_positionMutex;
|
||||
ViewBuffer m_previousBuffer;
|
||||
std::mutex m_previousBufferMutex;
|
||||
Vector2D m_size;
|
||||
std::vector<std::unique_ptr<View>> m_views;
|
||||
std::atomic<std::int32_t> m_zOrder{0};
|
||||
|
||||
void DoRender(
|
||||
|
@ -57,6 +60,24 @@ public:
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
@ -93,6 +114,20 @@ public:
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
@ -127,8 +162,12 @@ public:
|
|||
* @brief Set dirty flag true.
|
||||
*
|
||||
* 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.
|
||||
|
@ -211,7 +250,7 @@ protected:
|
|||
*
|
||||
* This runs when the application initializes the view.
|
||||
*/
|
||||
virtual void Initialize() = 0;
|
||||
virtual void Initialize();
|
||||
|
||||
/**
|
||||
* @brief Draw event.
|
||||
|
@ -223,7 +262,21 @@ protected:
|
|||
*
|
||||
* 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.
|
||||
|
|
|
@ -15,6 +15,7 @@ class Window: public View
|
|||
{
|
||||
|
||||
std::wstring m_title{L"Window"};
|
||||
std::wstring m_content;
|
||||
|
||||
public:
|
||||
|
||||
|
@ -58,6 +59,13 @@ protected:
|
|||
*/
|
||||
virtual void OnDraw();
|
||||
|
||||
/**
|
||||
* @brief Mouse event.
|
||||
*
|
||||
* Triggers when a mouse event happens.
|
||||
*/
|
||||
virtual void OnMouse(const MouseEventData&);
|
||||
|
||||
};
|
||||
|
||||
} // namespace neovision
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "neovision/ansi.h"
|
||||
|
@ -25,64 +26,62 @@ Application::~Application()
|
|||
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()
|
||||
{
|
||||
View::Initialize();
|
||||
m_terminal.Initialize();
|
||||
IO::Get().SetInputFunction([&](size_t b){
|
||||
IO::Get().SetInputFunction([this](size_t 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);
|
||||
});
|
||||
for (auto& view: m_views)
|
||||
{
|
||||
view->Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::Run()
|
||||
{
|
||||
ClearScreen();
|
||||
m_running = true;
|
||||
m_terminal.Unbuffer();
|
||||
Size(m_terminal.Size()- Vector2D(1, 1), m_bgFillCharacter);
|
||||
m_terminal.SetOnResize([&](){
|
||||
const Vector2D newSize = m_terminal.Size() - Vector2D(1, 1);
|
||||
Size(newSize, m_bgFillCharacter);
|
||||
for (const auto& v: m_views)
|
||||
{
|
||||
v->SetDirty();
|
||||
}
|
||||
OnResize(newSize);
|
||||
});
|
||||
m_terminal.SetOnTerminate([&](){
|
||||
Stop();
|
||||
});
|
||||
SetMouseReporting(MouseEventMode::ButtonOnly);
|
||||
Initialize();
|
||||
InputParser inputParser;
|
||||
m_inputParser.SetOnKeyEvent([this](const std::wstring& k){
|
||||
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)
|
||||
{
|
||||
m_terminal.ProcessEvents();
|
||||
OnDraw();
|
||||
inputParser.Read();
|
||||
m_inputParser.Read();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,28 +98,33 @@ Terminal& Application::Term()
|
|||
return m_terminal;
|
||||
}
|
||||
|
||||
void Application::OnDraw()
|
||||
void Application::OnMouse(const MouseEventData& mouseData)
|
||||
{
|
||||
// Let all views render themselves first.
|
||||
for (auto& v: m_views)
|
||||
/* Figure out what view is clicked. Reverse iterate so the top-most window
|
||||
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.
|
||||
for (auto& v: m_views)
|
||||
{
|
||||
if ((v->Dirty() == true) || (m_dirty == true))
|
||||
{
|
||||
v->Render(*this);
|
||||
}
|
||||
}
|
||||
|
||||
// Now we render everything to the screen.
|
||||
Render();
|
||||
void Application::OnKey(const std::wstring& keyData)
|
||||
{
|
||||
if (keyData.empty()) return;
|
||||
}
|
||||
|
||||
} // 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
|
||||
have. */
|
||||
if (m_onKeyEvent) m_onKeyEvent(ansiParser.ProcessedCharacters());
|
||||
|
||||
}
|
||||
|
||||
void InputParser::SetOnKeyEvent(const KeyboardEventCallback& ev)
|
||||
|
@ -430,6 +429,11 @@ void SetMouseReporting(
|
|||
std::wstring{ansi::MouseTrackMode::VT200} +
|
||||
ansi::DecPmSuffix::DISABLE
|
||||
});
|
||||
outStr += ansi::MakeCsiSequence({
|
||||
ansi::CsiSequence::DECPM +
|
||||
std::wstring{ansi::MouseTrackMode::BTN_EVENT} +
|
||||
ansi::DecPmSuffix::DISABLE
|
||||
});
|
||||
outStr += ansi::MakeCsiSequence({
|
||||
ansi::CsiSequence::DECPM +
|
||||
std::wstring{ansi::MouseTrackMode::VT200_HIGHLIGHT} +
|
||||
|
|
|
@ -79,6 +79,7 @@ void Terminal::sigWinchHandler(int)
|
|||
void Terminal::ProcessEvents()
|
||||
{
|
||||
if (!m_initialized) return;
|
||||
if (!m_fd) return;
|
||||
winsize ws;
|
||||
if (ioctl(m_fd, TIOCGWINSZ, &ws) == -1)
|
||||
{
|
||||
|
@ -100,11 +101,12 @@ std::wstring Terminal::Read(size_t n)
|
|||
while (bytesRead < n)
|
||||
{
|
||||
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
|
||||
);
|
||||
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);
|
||||
if (br == -1)
|
||||
{
|
||||
|
@ -152,12 +154,12 @@ void Terminal::SetOnTerminate(TerminalEventCallback callback)
|
|||
m_onTerminate = callback;
|
||||
}
|
||||
|
||||
std::chrono::seconds Terminal::Timeout() const
|
||||
std::chrono::milliseconds Terminal::Timeout() const
|
||||
{
|
||||
return m_ioTimeOut;
|
||||
}
|
||||
|
||||
void Terminal::Timeout(const std::chrono::seconds& t)
|
||||
void Terminal::Timeout(const std::chrono::milliseconds& t)
|
||||
{
|
||||
m_ioTimeOut = t;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,24 @@ View::~View()
|
|||
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;
|
||||
|
@ -33,6 +51,21 @@ 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;
|
||||
|
@ -57,12 +90,6 @@ void View::Render()
|
|||
|
||||
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){
|
||||
view.WriteAt(p, output);
|
||||
});
|
||||
|
@ -73,48 +100,53 @@ void View::DoRender(
|
|||
)
|
||||
{
|
||||
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)
|
||||
{
|
||||
std::wstring lineOut;
|
||||
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)
|
||||
{
|
||||
if (x >= line.size()) break;
|
||||
const wchar_t newChar = line[x];
|
||||
bool needUpdate{false};
|
||||
if (m_buffer.size() == m_previousBuffer.size())
|
||||
const Vector2D pos = m_position + Vector2D(x, y);
|
||||
if (y < m_previousBuffer.size())
|
||||
{
|
||||
const std::vector<wchar_t>& pline = m_previousBuffer[y];
|
||||
if (line.size() == pline.size())
|
||||
if (x < pline.size())
|
||||
{
|
||||
const wchar_t oldChar = pline[x];
|
||||
if (newChar != oldChar) needUpdate = true;
|
||||
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;
|
||||
}
|
||||
if (needUpdate)
|
||||
{
|
||||
if ((x == 0) || (lastUpdatedX == (x - 1)))
|
||||
{
|
||||
lineOut += newChar;
|
||||
}
|
||||
else
|
||||
{
|
||||
const Vector2D pos = m_position + Vector2D(x, y);
|
||||
outputFunc(lineOut, pos);
|
||||
lineOut.clear();
|
||||
}
|
||||
lastUpdatedX = x;
|
||||
}
|
||||
// Now write output if an update is needed for this character.
|
||||
if (needUpdate) outputFunc(std::wstring{newChar}, pos);
|
||||
}
|
||||
if (!lineOut.empty())
|
||||
{
|
||||
|
@ -138,9 +170,49 @@ void View::DoRender(
|
|||
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;
|
||||
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)
|
||||
|
@ -165,7 +237,7 @@ void View::Size(const Vector2D& s, wchar_t fill)
|
|||
line.resize(minColBufferSize);
|
||||
}
|
||||
}
|
||||
m_dirty = true;
|
||||
SetDirty(false, false);
|
||||
}
|
||||
|
||||
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.
|
||||
m_cursorPosition.X(p.X());
|
||||
m_cursorPosition.Y(p.Y());
|
||||
m_dirty = true;
|
||||
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
|
||||
|
|
|
@ -13,6 +13,7 @@ Window::Window(): View()
|
|||
|
||||
void Window::Initialize()
|
||||
{
|
||||
View::Initialize();
|
||||
/* 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
|
||||
invisible by default. */
|
||||
|
@ -68,11 +69,19 @@ void Window::OnDraw()
|
|||
const std::uint32_t titlePos = winMid - titleMid;
|
||||
WriteAt({titlePos, y1}, titleStr);
|
||||
// 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' ');
|
||||
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
|
||||
|
|
|
@ -13,6 +13,8 @@ public:
|
|||
|
||||
Application(std::vector<std::string> args);
|
||||
|
||||
virtual ~Application() = default;
|
||||
|
||||
};
|
||||
|
||||
} // namespace pubnix
|
||||
|
|
|
@ -17,11 +17,13 @@ int main(int argc, char* argv[])
|
|||
{
|
||||
std::vector<std::string> args(argv + 1, argv + argc);
|
||||
pubnix::Application app(args);
|
||||
|
||||
app.Initialize();
|
||||
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(otherWindow));
|
||||
app.Run();
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue