mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-19 00:37:25 +01:00
Escher: Rect-based dirty tracking
Change-Id: I798dd04f4b36042429105e2ccdf7cd5df554bedf
This commit is contained in:
@@ -39,7 +39,7 @@ void MyFunCell::setMessage(const char * message) {
|
||||
|
||||
void MyFunCell::setFocused(bool focused) {
|
||||
m_focused = focused;
|
||||
markAsNeedingRedraw();
|
||||
markRectAsDirty(bounds());
|
||||
}
|
||||
|
||||
void MyFunCell::setEven(bool even) {
|
||||
@@ -150,49 +150,96 @@ KDCoordinate ListController::cellHeight() {
|
||||
return 40;
|
||||
}
|
||||
|
||||
class GraphView : public ChildlessView {
|
||||
class CursorView : public ChildlessView {
|
||||
public:
|
||||
using ChildlessView::ChildlessView;
|
||||
void drawRect(KDRect rect) const override;
|
||||
};
|
||||
|
||||
void CursorView::drawRect(KDRect rect) const {
|
||||
KDFillRect(rect, KDColorRed);
|
||||
}
|
||||
|
||||
class GraphView : public View {
|
||||
public:
|
||||
GraphView();
|
||||
void drawRect(KDRect rect) const override;
|
||||
void moveCursorRight();
|
||||
private:
|
||||
int numberOfSubviews() const override;
|
||||
View * subviewAtIndex(int index) override;
|
||||
void layoutSubviews() override;
|
||||
|
||||
CursorView m_cursorView;
|
||||
KDPoint m_cursorPosition;
|
||||
};
|
||||
|
||||
GraphView::GraphView() :
|
||||
View(),
|
||||
m_cursorView(CursorView()),
|
||||
m_cursorPosition(KDPointZero)
|
||||
{
|
||||
}
|
||||
|
||||
int GraphView::numberOfSubviews() const {
|
||||
return 1;
|
||||
};
|
||||
|
||||
View * GraphView::subviewAtIndex(int index) {
|
||||
return &m_cursorView;
|
||||
}
|
||||
|
||||
void GraphView::moveCursorRight() {
|
||||
m_cursorPosition.x = m_cursorPosition.x + 2;
|
||||
layoutSubviews();
|
||||
}
|
||||
|
||||
void GraphView::layoutSubviews() {
|
||||
KDRect cursorFrame;
|
||||
cursorFrame.origin = m_cursorPosition;
|
||||
cursorFrame.width = 10;
|
||||
cursorFrame.height = 10;
|
||||
m_cursorView.setFrame(cursorFrame);
|
||||
}
|
||||
|
||||
void GraphView::drawRect(KDRect rect) const {
|
||||
KDFillRect(rect, KDColorWhite);
|
||||
KDCoordinate x_grid_step = m_frame.width/10;
|
||||
KDCoordinate y_grid_step = m_frame.height/10;
|
||||
KDColor gridColor = KDColorGray(0xEE);
|
||||
for (KDCoordinate x=rect.x; x<rect.width; x += x_grid_step) {
|
||||
for (KDCoordinate x=m_frame.x; x<m_frame.width; x += x_grid_step) {
|
||||
KDRect verticalGridRect;
|
||||
verticalGridRect.x = x;
|
||||
verticalGridRect.y = rect.y;
|
||||
verticalGridRect.y = m_frame.y;
|
||||
verticalGridRect.width = 1;
|
||||
verticalGridRect.height = rect.height;
|
||||
verticalGridRect.height = m_frame.height;
|
||||
KDFillRect(verticalGridRect, gridColor);
|
||||
}
|
||||
for (KDCoordinate y=rect.y; y<rect.height; y += y_grid_step) {
|
||||
for (KDCoordinate y=m_frame.y; y<m_frame.height; y += y_grid_step) {
|
||||
KDRect horizontalGridRect;
|
||||
horizontalGridRect.x = rect.x;
|
||||
horizontalGridRect.x = m_frame.x;
|
||||
horizontalGridRect.y = y;
|
||||
horizontalGridRect.width = rect.width;
|
||||
horizontalGridRect.width = m_frame.width;
|
||||
horizontalGridRect.height = 1;
|
||||
KDFillRect(horizontalGridRect, gridColor);
|
||||
}
|
||||
|
||||
for (int i=rect.x; i<rect.width; i++) {
|
||||
for (int i=m_frame.x; i<m_frame.width; i++) {
|
||||
KDPoint p;
|
||||
p.x = i;
|
||||
p.y = (i*i)/rect.height;
|
||||
p.y = (i*i)/m_frame.height;
|
||||
KDSetPixel(p, KDColorRGB(0x7F, 0, 0));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class DemoViewController : public ViewController {
|
||||
class GraphController : public ViewController {
|
||||
public:
|
||||
DemoViewController(KDColor c);
|
||||
GraphController(KDColor c);
|
||||
View * view() override;
|
||||
const char * title() const override;
|
||||
void setFocused(bool focused) override;
|
||||
bool handleEvent(ion_event_t event) override;
|
||||
private:
|
||||
#if 0
|
||||
//SolidColorView m_view;
|
||||
@@ -204,7 +251,7 @@ private:
|
||||
#endif
|
||||
};
|
||||
|
||||
DemoViewController::DemoViewController(KDColor c) :
|
||||
GraphController::GraphController(KDColor c) :
|
||||
ViewController(),
|
||||
//m_view(TextField(buffer, k_bufferSize))
|
||||
//m_view(SolidColorView(c))
|
||||
@@ -213,15 +260,15 @@ DemoViewController::DemoViewController(KDColor c) :
|
||||
//m_view.setParentResponder(this);
|
||||
}
|
||||
|
||||
View * DemoViewController::view() {
|
||||
View * GraphController::view() {
|
||||
return &m_view;
|
||||
}
|
||||
|
||||
const char * DemoViewController::title() const {
|
||||
return "HELLO";
|
||||
const char * GraphController::title() const {
|
||||
return "Graph";
|
||||
}
|
||||
|
||||
void DemoViewController::setFocused(bool focused) {
|
||||
void GraphController::setFocused(bool focused) {
|
||||
/*
|
||||
if (focused) {
|
||||
App::runningApp()->focus(&m_view);
|
||||
@@ -229,22 +276,32 @@ void DemoViewController::setFocused(bool focused) {
|
||||
*/
|
||||
}
|
||||
|
||||
bool GraphController::handleEvent(ion_event_t event) {
|
||||
switch (event) {
|
||||
case ENTER:
|
||||
m_view.moveCursorRight();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class MyTestApp : public App {
|
||||
public:
|
||||
MyTestApp();
|
||||
protected:
|
||||
ViewController * rootViewController() override;
|
||||
private:
|
||||
DemoViewController m_demoViewController;
|
||||
GraphController m_graphViewController;
|
||||
ListController m_listViewController;
|
||||
TabViewController m_tabViewController;
|
||||
};
|
||||
|
||||
MyTestApp::MyTestApp() :
|
||||
App(),
|
||||
m_demoViewController(DemoViewController(KDColorWhite)),
|
||||
m_graphViewController(GraphController(KDColorWhite)),
|
||||
m_listViewController(ListController()),
|
||||
m_tabViewController(&m_demoViewController, &m_listViewController)
|
||||
m_tabViewController(&m_graphViewController, &m_listViewController)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,6 @@ private:
|
||||
public:
|
||||
ContentView();
|
||||
|
||||
int numberOfSubviews() const override;
|
||||
void layoutSubviews() override;
|
||||
|
||||
void setActiveView(View * view);
|
||||
TabView m_tabView;
|
||||
protected:
|
||||
@@ -31,7 +28,10 @@ private:
|
||||
const char * className() const override;
|
||||
#endif
|
||||
private:
|
||||
int numberOfSubviews() const override;
|
||||
View * subviewAtIndex(int index) override;
|
||||
void layoutSubviews() override;
|
||||
|
||||
View * m_activeView;
|
||||
};
|
||||
|
||||
|
||||
@@ -39,7 +39,17 @@ public:
|
||||
friend std::ostream &operator<<(std::ostream &os, View &view);
|
||||
#endif
|
||||
protected:
|
||||
void markAsNeedingRedraw();
|
||||
/* The whole point of the dirty-tracking mechanism is to identify which
|
||||
* pixels have to be redrawn. So in the end it doesn't really need to be bound
|
||||
* to a view, it's really absolute pixels that count.
|
||||
*
|
||||
* That being said, what are the case of dirtyness that we know of?
|
||||
* - Scrolling -> well, everything has to be redrawn anyway
|
||||
* - Moving a cursor -> In that case, there's really a much more efficient way
|
||||
* - ... and that's all I can think of.
|
||||
*/
|
||||
void markRectAsDirty(KDRect rect);
|
||||
//void markAsNeedingRedraw();
|
||||
#if ESCHER_VIEW_LOGGING
|
||||
virtual const char * className() const;
|
||||
virtual void logAttributes(std::ostream &os) const;
|
||||
@@ -55,7 +65,7 @@ private:
|
||||
KDRect absoluteVisibleFrame() const;
|
||||
|
||||
View * m_superview;
|
||||
bool m_needsRedraw;
|
||||
KDRect m_dirtyRect;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -34,12 +34,12 @@ void ScrollViewIndicator::drawRect(KDRect rect) const {
|
||||
|
||||
void ScrollViewIndicator::setStart(float start) {
|
||||
m_start = start;
|
||||
markAsNeedingRedraw();
|
||||
markRectAsDirty(bounds());
|
||||
}
|
||||
|
||||
void ScrollViewIndicator::setEnd(float end) {
|
||||
m_end = end;
|
||||
markAsNeedingRedraw();
|
||||
markRectAsDirty(bounds());
|
||||
}
|
||||
|
||||
#if ESCHER_VIEW_LOGGING
|
||||
|
||||
@@ -21,7 +21,7 @@ void TabView::addTabNamed(const char * name) {
|
||||
m_cells[tabIndex].setName(name);
|
||||
m_numberOfTabs++;
|
||||
//setSubview(&m_cells[tabIndex], tabIndex);
|
||||
markAsNeedingRedraw();
|
||||
markRectAsDirty(bounds());
|
||||
}
|
||||
|
||||
void TabView::setActiveIndex(int index) {
|
||||
|
||||
@@ -12,12 +12,12 @@ TabViewCell::TabViewCell() :
|
||||
|
||||
void TabViewCell::setName(const char * name) {
|
||||
m_name = name;
|
||||
markAsNeedingRedraw();
|
||||
markRectAsDirty(bounds());
|
||||
}
|
||||
|
||||
void TabViewCell::setActive(bool active) {
|
||||
m_active = active;
|
||||
markAsNeedingRedraw();
|
||||
markRectAsDirty(bounds());
|
||||
}
|
||||
|
||||
void TabViewCell::drawRect(KDRect rect) const {
|
||||
|
||||
@@ -13,7 +13,7 @@ TabViewController::ContentView::ContentView() :
|
||||
void TabViewController::ContentView::setActiveView(View * view) {
|
||||
m_activeView = view;
|
||||
layoutSubviews();
|
||||
markAsNeedingRedraw();
|
||||
markRectAsDirty(bounds());
|
||||
}
|
||||
|
||||
void TabViewController::ContentView::layoutSubviews() {
|
||||
|
||||
@@ -33,7 +33,7 @@ bool TextField::handleEvent(ion_event_t event) {
|
||||
}
|
||||
if (m_currentTextLength-1 < m_textBufferSize) {
|
||||
m_textBuffer[m_currentTextLength++] = event;
|
||||
markAsNeedingRedraw();
|
||||
markRectAsDirty(bounds()); // TODO: Could be optimized
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ extern "C" {
|
||||
View::View() :
|
||||
m_superview(nullptr),
|
||||
m_frame(KDRectZero),
|
||||
m_needsRedraw(true)
|
||||
m_dirtyRect(KDRectZero)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -23,38 +23,26 @@ const Window * View::window() const {
|
||||
}
|
||||
}
|
||||
|
||||
void View::markAsNeedingRedraw() {
|
||||
// Let's mark ourself as needing redraw
|
||||
m_needsRedraw = true;
|
||||
|
||||
/* And let's mark our parents as needing redraw too. The alternative would be
|
||||
* to have a recursive getter, which would be more resilient to superview
|
||||
* modification, but much slower too. As long as we don't change the view
|
||||
* hierarchy, this way is easier. */
|
||||
if (m_superview) {
|
||||
m_superview->markAsNeedingRedraw();
|
||||
}
|
||||
void View::markRectAsDirty(KDRect rect) {
|
||||
m_dirtyRect = KDRectUnion(m_dirtyRect, rect);
|
||||
}
|
||||
|
||||
void View::redraw(KDRect rect) {
|
||||
/* CAUTION: do NOT call redraw directly.
|
||||
* This may seem to work, but will not. Namely, it won't clip.
|
||||
* Example : our superview is smaller than we are. If we redraw ourself, we
|
||||
* will overflow our superview. */
|
||||
|
||||
if (window() == nullptr || !m_needsRedraw) {
|
||||
if (window() == nullptr) {
|
||||
/* That view (and all of its subviews) is offscreen. That means so are all
|
||||
* of its subviews. So there's no point in drawing them. */
|
||||
return;
|
||||
}
|
||||
|
||||
// First, let's draw our own content by calling drawRect
|
||||
KDPoint absOrigin = absoluteOrigin();
|
||||
KDRect absRect = KDRectTranslate(rect, absOrigin);
|
||||
|
||||
KDRect absClippingRect = KDRectIntersection(absoluteVisibleFrame(), absRect);
|
||||
|
||||
KDSetDrawingArea(absOrigin, absoluteVisibleFrame());
|
||||
|
||||
this->drawRect(rect);
|
||||
KDRect rectNeedingRedraw = KDRectIntersection(rect, m_dirtyRect);
|
||||
if (rectNeedingRedraw.width > 0 && rectNeedingRedraw.height > 0) {
|
||||
KDPoint absOrigin = absoluteOrigin();
|
||||
KDRect absRect = KDRectTranslate(rectNeedingRedraw, absOrigin);
|
||||
KDRect absClippingRect = KDRectIntersection(absoluteVisibleFrame(), absRect);
|
||||
KDSetDrawingArea(absOrigin, absoluteVisibleFrame());
|
||||
this->drawRect(rectNeedingRedraw);
|
||||
}
|
||||
|
||||
// Then, let's recursively draw our children over ourself
|
||||
for (uint8_t i=0; i<numberOfSubviews(); i++) {
|
||||
@@ -74,7 +62,7 @@ void View::redraw(KDRect rect) {
|
||||
}
|
||||
|
||||
// Eventually, mark that we don't need to be redrawn
|
||||
m_needsRedraw = false;
|
||||
m_dirtyRect = KDRectZero;
|
||||
}
|
||||
|
||||
View * View::subview(int index) {
|
||||
@@ -88,19 +76,23 @@ View * View::subview(int index) {
|
||||
|
||||
void View::setFrame(KDRect frame) {
|
||||
// TODO: Return if frame is equal to m_frame
|
||||
m_frame = frame;
|
||||
if (m_superview != nullptr) {
|
||||
/* We have moved this view. This left a blank spot in its superview were it
|
||||
* previously was.
|
||||
/* We will move this view. This will leave a blank spot in its superview
|
||||
* were it previously was.
|
||||
* At this point, we know that the only area that really needs to be redrawn
|
||||
* in the superview is the value of m_frame at the start of that method.
|
||||
* However, let's not try to optimize too early, and let's simply mark the
|
||||
* whole superview as needing redraw. */
|
||||
m_superview->markAsNeedingRedraw();
|
||||
* in the superview is the value of m_frame at the start of that method. */
|
||||
m_superview->markRectAsDirty(m_frame);
|
||||
}
|
||||
layoutSubviews();
|
||||
|
||||
markAsNeedingRedraw();
|
||||
m_frame = frame;
|
||||
|
||||
/* Now that we have moved, we have also dirtied our new absolute frame.
|
||||
* There are two ways to declare this, which are semantically equivalent: we
|
||||
* can either mark an area of our superview as dirty, or mark our whole frame
|
||||
* as dirty. We pick the second option because it is more efficient. */
|
||||
markRectAsDirty(bounds());
|
||||
|
||||
layoutSubviews();
|
||||
}
|
||||
|
||||
KDRect View::bounds() const {
|
||||
|
||||
Reference in New Issue
Block a user