diff --git a/app/escher_demo.cpp b/app/escher_demo.cpp index 16c317bfe..5c1faae50 100644 --- a/app/escher_demo.cpp +++ b/app/escher_demo.cpp @@ -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; xfocus(&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) { } diff --git a/escher/include/escher/tab_view_controller.h b/escher/include/escher/tab_view_controller.h index 18dbb15f6..9556799d7 100644 --- a/escher/include/escher/tab_view_controller.h +++ b/escher/include/escher/tab_view_controller.h @@ -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; }; diff --git a/escher/include/escher/view.h b/escher/include/escher/view.h index 707750bce..91886632c 100644 --- a/escher/include/escher/view.h +++ b/escher/include/escher/view.h @@ -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 diff --git a/escher/src/scroll_view_indicator.cpp b/escher/src/scroll_view_indicator.cpp index a2a1dada6..0b693288f 100644 --- a/escher/src/scroll_view_indicator.cpp +++ b/escher/src/scroll_view_indicator.cpp @@ -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 diff --git a/escher/src/tab_view.cpp b/escher/src/tab_view.cpp index 07b511ead..64897b2e1 100644 --- a/escher/src/tab_view.cpp +++ b/escher/src/tab_view.cpp @@ -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) { diff --git a/escher/src/tab_view_cell.cpp b/escher/src/tab_view_cell.cpp index c418f6026..c2eb86528 100644 --- a/escher/src/tab_view_cell.cpp +++ b/escher/src/tab_view_cell.cpp @@ -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 { diff --git a/escher/src/tab_view_controller.cpp b/escher/src/tab_view_controller.cpp index 9d2ed1226..119cee8c4 100644 --- a/escher/src/tab_view_controller.cpp +++ b/escher/src/tab_view_controller.cpp @@ -13,7 +13,7 @@ TabViewController::ContentView::ContentView() : void TabViewController::ContentView::setActiveView(View * view) { m_activeView = view; layoutSubviews(); - markAsNeedingRedraw(); + markRectAsDirty(bounds()); } void TabViewController::ContentView::layoutSubviews() { diff --git a/escher/src/text_field.cpp b/escher/src/text_field.cpp index 5a21a1ca7..c42a6a21f 100644 --- a/escher/src/text_field.cpp +++ b/escher/src/text_field.cpp @@ -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; } diff --git a/escher/src/view.cpp b/escher/src/view.cpp index 714f7328d..80779599a 100644 --- a/escher/src/view.cpp +++ b/escher/src/view.cpp @@ -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; imarkAsNeedingRedraw(); + * 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 {