// // Clipping region routines for the Fast Light Tool Kit (FLTK). // // Copyright 2018 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this // file is missing or damaged, see the license at: // // https://www.fltk.org/COPYING.php // // Please see the following page on how to report bugs and issues: // // https://www.fltk.org/bugs.php // #include #include "Fl_Android_Graphics_Driver.H" #include "Fl_Android_Application.H" #include /** Create an empty clipping region. */ Fl_Rect_Region::Fl_Rect_Region() : pLeft(0), pTop(0), pRight(0), pBottom(0) { } /** Create a clipping region based on position and size. \param x, y position \param w, h size */ Fl_Rect_Region::Fl_Rect_Region(int x, int y, int w, int h) : pLeft(x), pTop(y), pRight(x+w), pBottom(y+h) { } /** Clone a clipping rectangle. */ Fl_Rect_Region::Fl_Rect_Region(const Fl_Rect_Region &r) : pLeft(r.pLeft), pTop(r.pTop), pRight(r.pRight), pBottom(r.pBottom) { } /** Clone a clipping rectangle. The pointer can be NULL if an empty rectangle is needed. */ Fl_Rect_Region::Fl_Rect_Region(enum Type what) { if (what==INFINITE) { pLeft = pTop = INT_MIN; pRight = pBottom = INT_MAX; } else { pLeft = pTop = pRight = pBottom = 0; } } /** If the rectangle has no width or height, it's considered empty. \return true, if everything will be clipped and there is nothing to draw */ bool Fl_Rect_Region::is_empty() const { return (pRight<=pLeft || pBottom<=pTop); } /** Return true, if the rectangle is of unlimited size and nothing should be clipped. \return treu, if there is no clipping */ bool Fl_Rect_Region::is_infinite() const { return (pLeft==INT_MIN); } /** Set an empty clipping rect. */ void Fl_Rect_Region::set_empty() { pLeft = pTop = pRight = pBottom = 0; } /** Set a clipping rect using position and size \param x, y position \param w, h size */ void Fl_Rect_Region::set(int x, int y, int w, int h) { pLeft = x; pTop = y; pRight = x+w; pBottom = y+h; } /** Set a rectangle using the coordinates of two points, top left and bottom right. \param l, t left and top coordinate \param r, b right and bottom coordinate */ void Fl_Rect_Region::set_ltrb(int l, int t, int r, int b) { pLeft = l; pTop = t; pRight = r; pBottom = b; } /** Copy the corrdinates from another rect. \param r source rectangle */ void Fl_Rect_Region::set(const Fl_Rect_Region &r) { pLeft = r.pLeft; pTop = r.pTop; pRight = r.pRight; pBottom = r.pBottom; } /** Set this rect to be the intersecting area between the original rect and another rect. \param r another rectangular region \return EMPTY, if rectangles are not intersecting, SAME if this and rect are equal, LESS if the new rect is smaller than the original rect */ int Fl_Rect_Region::intersect_with(const Fl_Rect_Region &r) { if (is_empty()) { return EMPTY; } if (r.is_empty()) { set_empty(); return EMPTY; } bool same = true; if ( pLeft != r.pLeft ) { same = false; if ( r.pLeft > pLeft ) pLeft = r.pLeft; } if ( pTop != r.pTop ) { same = false; if ( r.pTop > pTop ) pTop = r.pTop; } if ( pRight != r.pRight ) { same = false; if ( r.pRight < pRight ) pRight = r.pRight; } if ( pBottom != r.pBottom ) { same = false; if ( r.pBottom < pBottom ) pBottom = r.pBottom; } if (same) return SAME; if (is_empty()) return EMPTY; return LESS; } /** Use rectangle as a bounding box and add the outline of another rect. */ void Fl_Rect_Region::add_to_bbox(const Fl_Rect_Region &r) { if (is_empty()) return; if (r.pLeftpRight) pRight = r.pRight; if (r.pBottom>pBottom) pBottom = r.pBottom; } /** Print the coordinates of the rect to the log. \param label some text that is logged with this message. */ void Fl_Rect_Region::print(const char *label) const { Fl_Android_Application::log_i("---> Fl_Rect_Region: %s", label); Fl_Android_Application::log_i("Rect l:%d t:%d r:%d b:%d", left(), top(), right(), bottom()); } // ============================================================================= /** Create an empty complex region. */ Fl_Complex_Region::Fl_Complex_Region() : Fl_Rect_Region() { } /** Create a complex region with the same bounds as the give rect. \param r region size */ Fl_Complex_Region::Fl_Complex_Region(const Fl_Rect_Region &r) : Fl_Rect_Region(r) { } /** Delete this region, all subregions recursively, and all following regions. */ Fl_Complex_Region::~Fl_Complex_Region() { delete_all_subregions(); } /** Delete all subregions of this region. The pSubregion pointer should always be seen as a list of subregions, rather than a single region and some pNext pointer. So everything we do, we should probably do for every object in that list. Also note, that the top level region never has pNext pointing to anything. */ void Fl_Complex_Region::delete_all_subregions() { // Do NOT delete the chain in pNext! The caller has to that job. // A top-level coplex region has pNext always set to NULL, and it does // delete all subregions chained via the subregion pNext. while (pSubregion) { Fl_Complex_Region *rgn = pSubregion; pSubregion = rgn->pNext; delete rgn; rgn = 0; } } /** Print the entire content of this region recursively. */ void Fl_Complex_Region::print(const char *label) const { Fl_Android_Application::log_i("---> Fl_Complex_Region: %s", label); print_data(0); } /* Print the rectangular data only. */ void Fl_Complex_Region::print_data(int indent) const { static const char *space = " "; if (pSubregion) { Fl_Android_Application::log_i("%sBBox l:%d t:%d r:%d b:%d", space+16-indent, left(), top(), right(), bottom()); pSubregion->print_data(indent+1); } else { Fl_Android_Application::log_i("%sRect l:%d t:%d r:%d b:%d", space+16-indent, left(), top(), right(), bottom()); } if (pNext) { pNext->print_data(indent); } } /** Replace this region with a rectangle. \param r the source rectangle */ void Fl_Complex_Region::set(const Fl_Rect_Region &r) { Fl_Rect_Region::set(r); delete_all_subregions(); } /** Replace this region with a copy of another region. This operation can be expensive for very complex regions. \param r the source region */ void Fl_Complex_Region::set(const Fl_Complex_Region &r) { Fl_Rect_Region::set((const Fl_Rect_Region&)r); delete_all_subregions(); Fl_Complex_Region *srcRgn = r.pSubregion; if (srcRgn) { // copy first subregion Fl_Complex_Region *dstRgn = pSubregion = new Fl_Complex_Region(); pSubregion->set(*srcRgn); // copy rest of list while (srcRgn) { dstRgn->pNext = new Fl_Complex_Region(); dstRgn = dstRgn->next(); dstRgn->set(*srcRgn); srcRgn = srcRgn->next(); } } } /** Set this region to the intersection of the original region and some rect. \param r intersect with this rectangle \return EMPTY, SAME, LESS */ int Fl_Complex_Region::intersect_with(const Fl_Rect_Region &r) { if (pSubregion) { Fl_Complex_Region *rgn = pSubregion; while (rgn) { rgn->intersect_with(r); rgn = rgn->next(); } compress(); } else { Fl_Rect_Region::intersect_with(r); } return 0; } /** Subtract a rectangular region from this region. \param r the rect that we want removed \return currently 0, but could return something meaningful */ int Fl_Complex_Region::subtract(const Fl_Rect_Region &r) { if (pSubregion) { Fl_Complex_Region *rgn = pSubregion; while (rgn) { rgn->subtract(r); rgn = rgn->next(); } compress(); } else { // Check if we overlap at all Fl_Rect_Region s(r); int intersects = s.intersect_with(*this); switch (intersects) { case EMPTY: // nothing to do break; case SAME: set_empty(); // Will be deleted by compress() break; case LESS: // split this rect into 1, 2, 3, or 4 new ones subtract_smaller_region(s); break; default: Fl_Android_Application::log_e("Invalid case in %s:%d", __FUNCTION__, __LINE__); break; } if (pSubregion) compress(); // because intersecting this may have created subregions } return 0; } /** Compress the subregion of this region if possible and update the bounding box of this region. Does not recurse down the tree! */ void Fl_Complex_Region::compress() { // Can't compress anything that does not have a subregion if (!pSubregion) return; // remove all empty regions, because the really don't add anything (literally) // print("Compress"); Fl_Complex_Region *rgn = pSubregion; while (rgn && rgn->is_empty()) { pSubregion = rgn->next(); delete rgn; rgn = pSubregion; } if (!pSubregion) return; rgn = pSubregion; while (rgn) { while (rgn->pNext && rgn->pNext->is_empty()) { Fl_Complex_Region *nextNext = rgn->pNext->pNext; delete rgn->pNext; rgn->pNext = nextNext; } rgn = rgn->next(); } // find rectangles that can be merged into a single new rectangle // (Too much work for much too little benefit) // if there is only a single subregion left, merge it into this region if (pSubregion->pNext==nullptr) { set((Fl_Rect_Region&)*pSubregion); // deletes subregion for us } if (!pSubregion) return; // finally, update the boudning box Fl_Rect_Region::set((Fl_Rect_Region&)*pSubregion); for (rgn=pSubregion->pNext; rgn; rgn=rgn->pNext) { add_to_bbox(*rgn); } } /** Subtract a smaller rect from a larger rect, potentially creating four new rectangles. This assumes that the calling region is NOT complex. \param r subtract the area of this rectangle; r must fit within ``this``. \return currently 0, but this may change */ int Fl_Complex_Region::subtract_smaller_region(const Fl_Rect_Region &r) { // subtract a smaller rect from a larger rect and create subrects as needed // if there is only one single coordinate different, we can reuse this container if (left()==r.left() && top()==r.top() && right()==r.right() && bottom()==r.bottom()) { // this should not happen set_empty(); } else if (left()!=r.left() && top()==r.top() && right()==r.right() && bottom()==r.bottom()) { pRight = r.left(); } else if (left()==r.left() && top()!=r.top() && right()==r.right() && bottom()==r.bottom()) { pBottom = r.top(); } else if (left()==r.left() && top()==r.top() && right()!=r.right() && bottom()==r.bottom()) { pLeft = r.right(); } else if (left()==r.left() && top()==r.top() && right()==r.right() && bottom()!=r.bottom()) { pTop = r.bottom(); } else { // create multiple regions if (pTop!=r.top()) { Fl_Complex_Region *s = add_subregion(); s->set_ltrb(pLeft, pTop, pRight, r.top()); } if (pBottom!=r.bottom()) { Fl_Complex_Region *s = add_subregion(); s->set_ltrb(pLeft, r.bottom(), pRight, pBottom); } if (pLeft!=r.left()) { Fl_Complex_Region *s = add_subregion(); s->set_ltrb(pLeft, r.top(), r.left(), r.bottom()); } if (pRight!=r.right()) { Fl_Complex_Region *s = add_subregion(); s->set_ltrb(r.right(), r.top(), pRight, r.bottom()); } } return 0; } /** Add an empty subregion to the current region. \return a pointer to the newly created region. */ Fl_Complex_Region *Fl_Complex_Region::add_subregion() { Fl_Complex_Region *r = new Fl_Complex_Region(); r->pParent = this; r->pNext = pSubregion; pSubregion = r; return r; } // ----------------------------------------------------------------------------- /** Returns an iterator object for loops that traverse the entire region tree. C++11 interface to range-based loops. \return Iterator pointing to the first element. */ Fl_Complex_Region::Iterator Fl_Complex_Region::begin() { return Iterator(this); } /** Returns an interator object to mark the end of travesing the tree. C++11 interface to range-based loops. \return */ Fl_Complex_Region::Iterator Fl_Complex_Region::end() { return Iterator(nullptr); } /** Create an iterator to walk the entire tree. \param r Iterate through this region, r must not have a parent(). */ Fl_Complex_Region::Iterator::Iterator(Fl_Complex_Region *r) : pRegion(r) { } /** Compare two iterators. C++11 needs this to find the end of a for loop. \param other \return */ bool Fl_Complex_Region::Iterator::operator!=(const Iterator &other) const { return pRegion != other.pRegion; } /** Set the iterator to the next object in the tree, down first. C++11 needs this to iterate in a for loop. \return */ const Fl_Complex_Region::Iterator &Fl_Complex_Region::Iterator::operator++() { if (pRegion->subregion()) { pRegion = pRegion->subregion(); } else if (pRegion->next()) { pRegion = pRegion->next(); } else { pRegion = pRegion->parent(); } return *this; } /** Return the current object while iterating through the tree. \return */ Fl_Complex_Region *Fl_Complex_Region::Iterator::operator*() const { return pRegion; } // ----------------------------------------------------------------------------- /** Use this to iterate through a region, hitting only nodes that intersect with this rect. \param r find all parts of the region that intersect with this rect. \return an object that can be used in range-based for loops in C++11. */ Fl_Complex_Region::Overlapping Fl_Complex_Region::overlapping(const Fl_Rect_Region &r) { return Overlapping(this, r); } /** A helper object for iterating through a region, finding only overlapping rects. \param rgn \param rect */ Fl_Complex_Region::Overlapping::Overlapping(Fl_Complex_Region *rgn, const Fl_Rect_Region &rect) : pRegion(rgn), pOriginalRect(rect), pClippedRect(rect) { } /** Return an itertor for the first clipping rectangle inside the region. \return */ Fl_Complex_Region::Overlapping::OverlappingIterator Fl_Complex_Region::Overlapping::begin() { find_intersecting(); return OverlappingIterator(this); } /** Return an iterator for the end of forward iteration. \return */ Fl_Complex_Region::Overlapping::OverlappingIterator Fl_Complex_Region::Overlapping::end() { return OverlappingIterator(nullptr); } /** Return the result of intersecting the original rect with this iterator. \return */ Fl_Rect_Region &Fl_Complex_Region::Overlapping::clipped_rect() { return pClippedRect; } /** Store the intersection in pClippedRect and return true if there was an intersection. \return */ bool Fl_Complex_Region::Overlapping::intersects() { return (pClippedRect.intersect_with(*pRegion) != EMPTY); } /** Find the next element in the tree that actually intersects with the initial rect. Starting the search at the current object, NOT the next object. \return */ bool Fl_Complex_Region::Overlapping::find_intersecting() { for (;;) { if (!pRegion) return false; pClippedRect.set(pOriginalRect); if (intersects()) { if (!pRegion->subregion()) { return true; } else { pRegion = pRegion->subregion(); } } else { find_next(); } } } /** Find the next object in the tree, complex, simple, intersecting or not. \return */ bool Fl_Complex_Region::Overlapping::find_next() { if (pRegion->subregion()) { pRegion = pRegion->subregion(); } else if (pRegion->next()) { pRegion = pRegion->next(); } else { pRegion = pRegion->parent(); // can be NULL } return (pRegion != nullptr); } // ----------------------------------------------------------------------------- /** Create the actual iterator for finding true clipping rects. \see Fl_Complex_Region::Overlapping \param ov */ Fl_Complex_Region::Overlapping::OverlappingIterator::OverlappingIterator( Overlapping *ov) : pOv(ov) { } /** Compare two iterator. This is used by C++11 range-based for loops to find the end of the range. \param other \return */ bool Fl_Complex_Region::Overlapping::OverlappingIterator::operator!=( const OverlappingIterator &other) const { auto thisRegion = pOv ? pOv->pRegion : nullptr; auto otherRegion = other.pOv ? other.pOv->pRegion : nullptr; return thisRegion != otherRegion; } /** Wrapper to find and set the next intersecting rectangle. \see Fl_Complex_Region::Overlapping::find_intersecting \see Fl_Complex_Region::Overlapping::find_next \return */ const Fl_Complex_Region::Overlapping::OverlappingIterator & Fl_Complex_Region::Overlapping::OverlappingIterator::operator++() { pOv->find_next(); if (pOv->pRegion) pOv->find_intersecting(); return *this; } /** Return the Fl_Complex_Region::Overlapping state for this iterator. This gives the user access to the current rectangular fragment of the clipping region. \return */ Fl_Complex_Region::Overlapping * Fl_Complex_Region::Overlapping::OverlappingIterator::operator*() const { return pOv; } // ============================================================================= void Fl_Android_Graphics_Driver::restore_clip() { fl_clip_state_number++; // find the current user clipping rectangle Fl_Region b = rstack[rstackptr]; // Fl_Region is a pointer to Fl_Rect_Region if (b) { if (b->is_empty()) { // if this is an empty region, the intersection is always empty as well pClippingRegion.set_empty(); } else { // if there is a region, copy the full window region pClippingRegion.set(pDesktopWindowRegion); if (!b->is_infinite()) { // if the rect has dimensions, calculate the intersection pClippingRegion.intersect_with(*b); } } } else { // no rect? Just copy the window region pClippingRegion.set(pDesktopWindowRegion); } } void Fl_Android_Graphics_Driver::clip_region(Fl_Region r) { Fl_Region oldr = rstack[rstackptr]; if (oldr) ::free(oldr); rstack[rstackptr] = r; restore_clip(); } Fl_Region Fl_Android_Graphics_Driver::clip_region() { return rstack[rstackptr]; } void Fl_Android_Graphics_Driver::push_clip(int x, int y, int w, int h) { Fl_Region r; if (w > 0 && h > 0) { r = new Fl_Rect_Region(x, y, w, h); Fl_Region current = rstack[rstackptr]; if (current) { r->intersect_with(*current); } } else { // make empty clip region: r = new Fl_Rect_Region(); } if (rstackptr < region_stack_max) rstack[++rstackptr] = r; else Fl::warning("Fl_Android_Graphics_Driver::push_clip: clip stack overflow!\n"); restore_clip(); } void Fl_Android_Graphics_Driver::push_no_clip() { if (rstackptr < region_stack_max) rstack[++rstackptr] = 0; else Fl::warning("Fl_Android_Graphics_Driver::push_no_clip: clip stack overflow!\n"); restore_clip(); } void Fl_Android_Graphics_Driver::pop_clip() { if (rstackptr > 0) { Fl_Region oldr = rstack[rstackptr--]; if (oldr) ::free(oldr); } else Fl::warning("Fl_Android_Graphics_Driver::pop_clip: clip stack underflow!\n"); restore_clip(); } /* Intersects the rectangle with the current clip region and returns the bounding box of the result. Returns non-zero if the resulting rectangle is different to the original. This can be used to limit the necessary drawing to a rectangle. \p W and \p H are set to zero if the rectangle is completely outside the region. \param[in] x,y,w,h position and size of rectangle \param[out] X,Y,W,H position and size of resulting bounding box. \returns Non-zero if the resulting rectangle is different to the original. */ int Fl_Android_Graphics_Driver::clip_box(int x, int y, int w, int h, int& X, int& Y, int& W, int& H) { Fl_Region r = rstack[rstackptr]; if (r) { Fl_Rect_Region a(x, y, w, h); int ret = a.intersect_with(*r); X = a.x(); Y = a.y(); W = a.w(); H = a.h(); return (ret!=Fl_Rect_Region::SAME); } else { X = x; Y = y; W = w; H = h; return 0; } } /* Does the rectangle intersect the current clip region? \param[in] x,y,w,h position and size of rectangle \returns non-zero if any of the rectangle intersects the current clip region. If this returns 0 you don't have to draw the object. \note Under X this returns 2 if the rectangle is partially clipped, and 1 if it is entirely inside the clip region. */ int Fl_Android_Graphics_Driver::not_clipped(int x, int y, int w, int h) { if (w <= 0 || h <= 0) return 0; Fl_Region r = rstack[rstackptr]; if (r) { Fl_Rect_Region a(x, y, w, h); // return 0 for empty, 1 for same, 2 if intersecting return a.intersect_with(*r); } else { return 1; } }