Much improved ScrollableWindow and Adapter interfaces which allow for

virtual lists with extremely large item counts.
This commit is contained in:
casey 2016-05-11 23:53:35 -07:00
parent c179878e80
commit 2de2a21291
13 changed files with 277 additions and 216 deletions

View File

@ -1,24 +1,39 @@
#pragma once
#include "stdafx.h"
#include <boost/shared_ptr.hpp>
class IScrollAdapter {
public:
virtual void SetDisplaySize(size_t width, size_t height) = 0;
virtual size_t GetLineCount() = 0;
virtual size_t GetEntryCount() = 0;
virtual void DrawPage(WINDOW* window, size_t index) = 0;
struct ScrollPosition {
ScrollPosition() {
firstVisibleEntryIndex = 0;
visibleEntryCount = 0;
lineCount = 0;
totalEntries = 0;
}
class IEntry { /* can we slim this down? */
public:
virtual size_t GetIndex() = 0;
virtual void SetIndex(size_t index) = 0;
virtual size_t GetLineCount() = 0;
virtual std::string GetLine(size_t line) = 0;
virtual std::string GetValue() = 0;
virtual void SetWidth(size_t width) = 0;
virtual void SetAttrs(int64 attrs) = 0;
virtual int64 GetAttrs() = 0;
size_t firstVisibleEntryIndex;
size_t visibleEntryCount;
size_t lineCount;
size_t totalEntries;
};
class IEntry {
public:
virtual size_t GetLineCount() = 0;
virtual std::string GetLine(size_t line) = 0;
virtual std::string GetValue() = 0;
virtual void SetWidth(size_t width) = 0;
virtual void SetAttrs(int64 attrs) = 0;
virtual int64 GetAttrs() = 0;
};
typedef boost::shared_ptr<IEntry> EntryPtr;
virtual void SetDisplaySize(size_t width, size_t height) = 0;
virtual size_t GetEntryCount() = 0;
virtual EntryPtr GetEntry(size_t index) = 0;
virtual void DrawPage(WINDOW* window, size_t index, ScrollPosition *result = NULL) = 0;
};

View File

@ -126,7 +126,7 @@ int main(int argc, char* argv[])
}
focused = layout->FocusNext();
scrollable = dynamic_cast<ScrollableWindow*>(focused);
scrollable = dynamic_cast<IScrollable*>(focused);
input = dynamic_cast<IInput*>(focused);
if (input != NULL) {

View File

@ -39,8 +39,8 @@ MainLayout::~MainLayout() {
IWindow* MainLayout::FocusNext() {
IWindow* oldFocus = GetFocus();
if (++focusIndex >= (int) focusOrder.size()) {
focusIndex = 0;
if (++focusIndex >= (int) focusOrder.size()) {
focusIndex = 0;
}
return adjustFocus(oldFocus, GetFocus());
@ -48,8 +48,8 @@ IWindow* MainLayout::FocusNext() {
IWindow* MainLayout::FocusPrev() {
IWindow* oldFocus = GetFocus();
if (--focusIndex <= 0) {
focusIndex = (int) focusOrder.size() - 1;
if (--focusIndex <= 0) {
focusIndex = (int) focusOrder.size() - 1;
}
return adjustFocus(oldFocus, GetFocus());
@ -91,6 +91,6 @@ void MainLayout::Layout() {
void MainLayout::OnIdle() {
this->logs->Update();
this->transport->Repaint();
this->transport->Repaint();
this->resources->Repaint();
}

View File

@ -6,7 +6,7 @@
#include "Colors.h"
#include "MultiLineEntry.h"
typedef IScrollAdapter::IEntry IEntry;
typedef IScrollAdapter::EntryPtr EntryPtr;
OutputWindow::OutputWindow()
: ScrollableWindow()
@ -27,6 +27,6 @@ IScrollAdapter& OutputWindow::GetScrollAdapter() {
}
void OutputWindow::WriteLine(const std::string& text, int64 attrs) {
this->adapter->AddEntry(boost::shared_ptr<IEntry>(new MultiLineEntry(text, attrs)));
this->adapter->AddEntry(EntryPtr(new MultiLineEntry(text, attrs)));
this->OnAdapterChanged();
}

View File

@ -0,0 +1,125 @@
#include "stdafx.h"
#include "ScrollAdapterBase.h"
#include "MultiLineEntry.h"
typedef IScrollAdapter::EntryPtr EntryPtr;
ScrollAdapterBase::ScrollAdapterBase() {
this->height = 0;
this->width = 0;
}
ScrollAdapterBase::~ScrollAdapterBase() {
}
void ScrollAdapterBase::SetDisplaySize(size_t width, size_t height) {
this->width = width;
this->height = height;
}
size_t ScrollAdapterBase::GetLineCount() {
return -1;
}
void ScrollAdapterBase::GetVisibleItems(
size_t desired, std::deque<EntryPtr>& target, size_t& start)
{
size_t actual = desired;
/* ensure we have enough data to draw from the specified position
to the end. if we don't try to back up a bit until we can fill
the buffer */
int totalHeight = (int) this->height;
/* forward search first... */
int end = (int) GetEntryCount();
for (int i = (int) desired; i < end && totalHeight >= 0; i++) {
EntryPtr entry = this->GetEntry(i);
entry->SetWidth(this->width);
totalHeight -= entry->GetLineCount();
target.push_back(entry);
}
if (totalHeight > 0) {
target.clear();
/* oops, let's move backwards from the end */
totalHeight = this->height;
int i = GetEntryCount() - 1;
while (i >= 0 && totalHeight >= 0) {
EntryPtr entry = this->GetEntry(i);
entry->SetWidth(this->width);
int lines = entry->GetLineCount();
if (lines > totalHeight) {
break; /* this Entry won't fit. bail. */
}
totalHeight -= lines;
target.push_front(entry);
--i;
}
actual = i + 1;
}
start = actual;
}
void ScrollAdapterBase::DrawPage(WINDOW* window, size_t index, ScrollPosition *result) {
if (result != NULL) {
result->visibleEntryCount = 0;
result->firstVisibleEntryIndex = 0;
result->lineCount = 0;
result->totalEntries = 0;
}
wclear(window);
if (this->height == 0 || this->width == 0 || this->GetEntryCount() == 0) {
return;
}
if (index >= GetEntryCount()) {
index = GetEntryCount() - 1;
}
std::deque<EntryPtr> visible;
size_t topIndex; /* calculated by GetVisibleItems */
GetVisibleItems(index, visible, topIndex);
size_t drawnLines = 0;
for (size_t e = 0; e < visible.size(); e++) {
EntryPtr entry = visible.at(e);
size_t count = entry->GetLineCount();
int64 attrs = entry->GetAttrs();
if (attrs != -1) {
wattron(window, attrs);
}
for (size_t i = 0; i < count && drawnLines < this->height; i++) {
std::string line = entry->GetLine(i);
size_t len = u8len(line);
/* don't add a newline if we're going to hit the end of the line, the
newline will be added automatically. */
wprintw(window, "%s%s", line.c_str(), len >= this->width ? "" : "\n");
++drawnLines;
}
if (attrs != -1) {
wattroff(window, attrs);
}
}
if (result != NULL) {
result->visibleEntryCount = visible.size();
result->firstVisibleEntryIndex = topIndex;
result->lineCount = drawnLines;
result->totalEntries = GetEntryCount();
}
}

View File

@ -0,0 +1,27 @@
#pragma once
#include "curses_config.h"
#include "IScrollAdapter.h"
#include <deque>
class ScrollAdapterBase : public IScrollAdapter {
public:
ScrollAdapterBase();
virtual ~ScrollAdapterBase();
virtual void SetDisplaySize(size_t width, size_t height);
virtual size_t GetLineCount();
virtual void DrawPage(WINDOW* window, size_t index, ScrollPosition *result = NULL);
virtual size_t GetEntryCount() = 0;
virtual EntryPtr GetEntry(size_t index) = 0;
protected:
void GetVisibleItems(size_t desired, std::deque<EntryPtr>& target, size_t& start);
size_t GetWidth() { return this->width; }
size_t GetHeight() { return this->height; }
private:
size_t width, height;
};

View File

@ -9,8 +9,6 @@
ScrollableWindow::ScrollableWindow()
: Window() {
scrollPosition = 0;
scrolledToBottom = true;
}
ScrollableWindow::~ScrollableWindow() {
@ -23,11 +21,15 @@ void ScrollableWindow::SetSize(int width, int height) {
void ScrollableWindow::OnAdapterChanged() {
IScrollAdapter *adapter = &GetScrollAdapter();
if (scrolledToBottom) {
if (IsLastItemVisible()) {
this->ScrollToBottom();
}
else {
GetScrollAdapter().DrawPage(this->GetContent(), this->scrollPosition);
GetScrollAdapter().DrawPage(
this->GetContent(),
this->scrollPosition.firstVisibleEntryIndex,
&this->scrollPosition);
this->Repaint();
}
}
@ -37,75 +39,77 @@ void ScrollableWindow::Create() {
this->OnAdapterChanged();
}
size_t ScrollableWindow::GetFirstVisible() {
return scrollPosition;
}
size_t ScrollableWindow::GetLastVisible() {
size_t total = GetScrollAdapter().GetLineCount();
return min(scrollPosition + this->GetContentHeight(), total);
}
void ScrollableWindow::ScrollToTop() {
GetScrollAdapter().DrawPage(this->GetContent(), 0);
this->scrollPosition = 0;
GetScrollAdapter().DrawPage(this->GetContent(), 0, &scrollPosition);
this->Repaint();
this->CheckScrolledToBottom();
}
void ScrollableWindow::ScrollToBottom() {
IScrollAdapter *adapter = &GetScrollAdapter();
GetScrollAdapter().DrawPage(
this->GetContent(),
GetScrollAdapter().GetEntryCount(),
&scrollPosition);
int total = (int) adapter->GetLineCount();
int height = this->GetContentHeight();
int actual = total - height;
actual = (actual < 0) ? 0 : actual;
adapter->DrawPage(this->GetContent(), actual);
this->scrollPosition = actual;
this->Repaint();
this->CheckScrolledToBottom();
}
void ScrollableWindow::ScrollUp(int delta) {
int actual = (int) this->scrollPosition - delta;
actual = (actual < 0) ? 0 : actual;
if (this->scrollPosition.firstVisibleEntryIndex > 0) {
GetScrollAdapter().DrawPage(
this->GetContent(),
this->scrollPosition.firstVisibleEntryIndex - delta,
&scrollPosition);
GetScrollAdapter().DrawPage(this->GetContent(), actual);
this->scrollPosition = (size_t) actual;
this->Repaint();
this->CheckScrolledToBottom();
this->Repaint();
}
}
void ScrollableWindow::ScrollDown(int delta) {
IScrollAdapter *adapter = &GetScrollAdapter();
GetScrollAdapter().DrawPage(
this->GetContent(),
this->scrollPosition.firstVisibleEntryIndex + delta,
&scrollPosition);
int total = adapter->GetLineCount();
int height = this->GetContentHeight();
int optimal = total - height;
int max = max(0, optimal);
int actual = (int) this->scrollPosition + delta;
actual = (actual > max) ? max : actual;
adapter->DrawPage(this->GetContent(), actual);
this->scrollPosition = (size_t) actual;
this->Repaint();
this->CheckScrolledToBottom();
}
size_t ScrollableWindow::GetPreviousPageEntryIndex() {
IScrollAdapter* adapter = &GetScrollAdapter();
int remaining = this->GetContentHeight();
int width = this->GetContentWidth();
int i = this->scrollPosition.firstVisibleEntryIndex;
while (i >= 0) {
IScrollAdapter::EntryPtr entry = adapter->GetEntry(i);
entry->SetWidth(width);
int count = entry->GetLineCount();
if (count > remaining) {
break;
}
i--;
remaining -= count;
}
return max(0, i);
}
void ScrollableWindow::PageUp() {
ScrollUp(this->GetContentHeight() - 1);
ScrollUp(
this->scrollPosition.firstVisibleEntryIndex -
GetPreviousPageEntryIndex());
}
void ScrollableWindow::PageDown() {
ScrollDown(this->GetContentHeight() - 1);
ScrollDown(this->scrollPosition.visibleEntryCount - 1);
}
void ScrollableWindow::CheckScrolledToBottom() {
bool ScrollableWindow::IsLastItemVisible() {
size_t lastIndex = this->scrollPosition.totalEntries;
lastIndex = (lastIndex == 0) ? lastIndex : lastIndex - 1;
size_t firstVisible = this->scrollPosition.firstVisibleEntryIndex;
size_t lastVisible = firstVisible + this->scrollPosition.visibleEntryCount;
return lastIndex >= firstVisible && lastIndex <= lastVisible;
}

View File

@ -25,12 +25,9 @@ class ScrollableWindow : public IScrollable, public Window {
virtual IScrollAdapter& GetScrollAdapter() = 0;
void OnAdapterChanged();
size_t GetFirstVisible();
size_t GetLastVisible();
private:
void CheckScrolledToBottom();
bool IsLastItemVisible();
size_t GetPreviousPageEntryIndex();
size_t scrollPosition;
bool scrolledToBottom;
IScrollAdapter::ScrollPosition scrollPosition;
};

View File

@ -8,9 +8,9 @@
#define MAX_ENTRY_COUNT 0xffffffff
SimpleScrollAdapter::SimpleScrollAdapter() {
this->lineCount = 0;
typedef IScrollAdapter::EntryPtr EntryPtr;
SimpleScrollAdapter::SimpleScrollAdapter() {
/* the adapters can have a maximum size. as we remove elements from
the back, we don't want to re-index everything. instead, we'll use
this offset for future calculations when searching for items. */
@ -22,18 +22,6 @@ SimpleScrollAdapter::~SimpleScrollAdapter() {
}
void SimpleScrollAdapter::SetDisplaySize(size_t width, size_t height) {
if (height != this->height || width != this->width) {
this->height = height;
this->width = width;
Reindex();
}
}
size_t SimpleScrollAdapter::GetLineCount() {
return this->lineCount;
}
size_t SimpleScrollAdapter::GetEntryCount() {
return this->entries.size();
}
@ -42,112 +30,15 @@ void SimpleScrollAdapter::SetMaxEntries(size_t maxEntries) {
this->maxEntries = maxEntries;
}
void SimpleScrollAdapter::DrawPage(WINDOW* window, size_t lineNumber) {
wclear(window);
if (this->lineCount <= 0) {
return;
}
if (lineNumber >= this->lineCount) {
lineNumber = this->lineCount - 1;
}
if (lineNumber < 0) {
lineNumber = 0;
}
/* binary search to find where we need to start */
size_t offset = this->FindEntryIndex(lineNumber);
Iterator it = this->entries.begin() + offset;
/* if found, the iterator will be pointing at the first visible
element. */
Iterator end = this->entries.end();
size_t remaining = this->height;
size_t w = this->width;
size_t c = lineNumber - ((*it)->GetIndex() - removedOffset);
do {
size_t count = (*it)->GetLineCount();
int64 attrs = (*it)->GetAttrs();
if (attrs != -1) {
wattron(window, attrs);
}
for (size_t i = c; i < count && remaining != 0; i++) {
std::string line = (*it)->GetLine(i).c_str();
size_t len = u8len(line);
/* don't add a newline if we're going to hit the end of the line, the
newline will be added automatically. */
wprintw(window, "%s%s", line.c_str(), len >= this->width ? "" : "\n");
--remaining;
}
if (attrs != -1) {
wattroff(window, attrs);
}
++it;
c = 0;
} while (it != end && remaining != 0);
EntryPtr SimpleScrollAdapter::GetEntry(size_t index) {
return this->entries.at(index);
}
void SimpleScrollAdapter::AddEntry(boost::shared_ptr<IEntry> entry) {
entry->SetWidth(this->width);
entry->SetIndex(this->lineCount + this->removedOffset);
entry->SetWidth(this->GetWidth());
entries.push_back(entry);
this->lineCount += entry->GetLineCount();
while (entries.size() > this->maxEntries) {
boost::shared_ptr<IEntry> e = entries.front();
size_t lineCount = e->GetLineCount();
this->removedOffset += lineCount;
this->lineCount -= lineCount;
entries.pop_front();
}
}
size_t SimpleScrollAdapter::FindEntryIndex(size_t lineNumber) {
if (lineCount == -1) {
Reindex();
}
size_t min = 0, max = this->entries.size() - 1;
while (true) {
size_t guess = (min + max) / 2;
IEntry* entry = this->entries.at(guess).get();
size_t first = entry->GetIndex() - this->removedOffset;
size_t last = first + entry->GetLineCount();
if (lineNumber >= first && lineNumber <= last) {
return guess;
}
else if (lineNumber > first) { /* guess too low */
min = guess + 1;
}
else if (lineNumber < last) { /* guess too high */
max = guess - 1;
}
}
}
void SimpleScrollAdapter::Reindex() {
int index = 0;
for (Iterator it = this->entries.begin(); it != this->entries.end(); it++) {
(*it)->SetIndex(index);
(*it)->SetWidth(this->width);
index += (*it)->GetLineCount();
}
this->removedOffset = 0;
this->lineCount = index;
}
}

View File

@ -1,31 +1,25 @@
#pragma once
#include "curses_config.h"
#include "IScrollAdapter.h"
#include "ScrollAdapterBase.h"
#include <deque>
class SimpleScrollAdapter : public IScrollAdapter {
class SimpleScrollAdapter : public ScrollAdapterBase {
public:
SimpleScrollAdapter();
virtual ~SimpleScrollAdapter();
virtual void SetDisplaySize(size_t width, size_t height);
virtual size_t GetLineCount();
virtual size_t GetEntryCount();
virtual void DrawPage(WINDOW* window, size_t index);
virtual void AddEntry(boost::shared_ptr<IEntry> entry);
virtual void AddEntry(EntryPtr entry);
virtual void SetMaxEntries(const size_t size = 500);
virtual size_t GetEntryCount();
virtual EntryPtr GetEntry(size_t index);
private:
typedef std::deque<boost::shared_ptr<IEntry>> EntryList;
typedef std::deque<EntryPtr> EntryList;
typedef EntryList::iterator Iterator;
void Reindex();
size_t FindEntryIndex(size_t index);
EntryList entries;
size_t lineCount, width, height;
size_t removedOffset;
size_t maxEntries;
};

View File

@ -36,11 +36,11 @@ int Window::GetHeight() const {
}
int Window::GetContentHeight() const {
return this->height - 2;
return this->height ? this->height - 2 : 0;
}
int Window::GetContentWidth() const {
return this->width - 2;
return this->width ? this->width - 2 : 0;
}
int Window::GetX() const {

View File

@ -117,9 +117,11 @@
<ItemGroup>
<ClCompile Include="CategoryListQuery.cpp" />
<ClCompile Include="CategoryListView.cpp" />
<ClCompile Include="ScrollAdapterBase.cpp" />
<ClCompile Include="LibraryLayout.cpp" />
<ClCompile Include="MainLayout.cpp" />
<ClCompile Include="MultiLineEntry.cpp" />
<ClCompile Include="ScrollableWindow.cpp" />
<ClCompile Include="SingleLineEntry.cpp" />
<ClCompile Include="Window.cpp" />
<ClCompile Include="Colors.cpp" />
@ -130,7 +132,6 @@
<ClCompile Include="OutputWindow.cpp" />
<ClCompile Include="ResourcesWindow.cpp" />
<ClCompile Include="Screen.cpp" />
<ClCompile Include="ScrollableWindow.cpp" />
<ClCompile Include="SimpleScrollAdapter.cpp" />
<ClCompile Include="SystemInfo.cpp" />
<ClCompile Include="Main.cpp" />
@ -143,10 +144,12 @@
<ItemGroup>
<ClInclude Include="CategoryListQuery.h" />
<ClInclude Include="CategoryListView.h" />
<ClInclude Include="ScrollAdapterBase.h" />
<ClInclude Include="IScrollable.h" />
<ClInclude Include="LibraryLayout.h" />
<ClInclude Include="MainLayout.h" />
<ClInclude Include="MultiLineEntry.h" />
<ClInclude Include="ScrollableWindow.h" />
<ClInclude Include="SingleLineEntry.h" />
<ClInclude Include="Window.h" />
<ClInclude Include="Colors.h" />
@ -158,7 +161,6 @@
<ClInclude Include="OutputWindow.h" />
<ClInclude Include="ResourcesWindow.h" />
<ClInclude Include="Screen.h" />
<ClInclude Include="ScrollableWindow.h" />
<ClInclude Include="SimpleScrollAdapter.h" />
<ClInclude Include="SystemInfo.h" />
<ClInclude Include="stdafx.h" />

View File

@ -24,9 +24,6 @@
<ClCompile Include="Screen.cpp">
<Filter>curses</Filter>
</ClCompile>
<ClCompile Include="ScrollableWindow.cpp">
<Filter>curses</Filter>
</ClCompile>
<ClCompile Include="SimpleScrollAdapter.cpp">
<Filter>curses</Filter>
</ClCompile>
@ -60,6 +57,12 @@
<ClCompile Include="MultiLineEntry.cpp">
<Filter>curses</Filter>
</ClCompile>
<ClCompile Include="ScrollAdapterBase.cpp">
<Filter>curses</Filter>
</ClCompile>
<ClCompile Include="ScrollableWindow.cpp">
<Filter>curses</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="stdafx.h" />
@ -84,9 +87,6 @@
<ClInclude Include="Screen.h">
<Filter>curses</Filter>
</ClInclude>
<ClInclude Include="ScrollableWindow.h">
<Filter>curses</Filter>
</ClInclude>
<ClInclude Include="SimpleScrollAdapter.h">
<Filter>curses</Filter>
</ClInclude>
@ -126,6 +126,12 @@
<ClInclude Include="MultiLineEntry.h">
<Filter>curses</Filter>
</ClInclude>
<ClInclude Include="ScrollAdapterBase.h">
<Filter>curses</Filter>
</ClInclude>
<ClInclude Include="ScrollableWindow.h">
<Filter>curses</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="curses">