From 1691c13b477271c45dcb29037702bc4fcaaa7924 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= <ayufan@ayufan.eu>
Date: Sat, 15 Jul 2023 05:30:19 +0900
Subject: [PATCH] display: Add helper methods to `Display::clip` and
 `Display::clamp_x/y_` (#5003)

* display: `Rect` make most of methods `const`

* display: add `clip` and `clamp_x/y_` methods for clipping to `Display`
---
 esphome/components/display/display.cpp | 45 +++++++++++++++++++++++---
 esphome/components/display/display.h   |  9 +++++-
 esphome/components/display/rect.cpp    |  6 ++--
 esphome/components/display/rect.h      | 12 +++----
 4 files changed, 57 insertions(+), 15 deletions(-)

diff --git a/esphome/components/display/display.cpp b/esphome/components/display/display.cpp
index 410ff58de3..22454aeddb 100644
--- a/esphome/components/display/display.cpp
+++ b/esphome/components/display/display.cpp
@@ -269,10 +269,7 @@ void Display::do_update_() {
   } else if (this->writer_.has_value()) {
     (*this->writer_)(*this);
   }
-  // remove all not ended clipping regions
-  while (is_clipping()) {
-    end_clipping();
-  }
+  this->clear_clipping_();
 }
 void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) {
   if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
@@ -322,13 +319,51 @@ void Display::shrink_clipping(Rect add_rect) {
     this->clipping_rectangle_.back().shrink(add_rect);
   }
 }
-Rect Display::get_clipping() {
+Rect Display::get_clipping() const {
   if (this->clipping_rectangle_.empty()) {
     return Rect();
   } else {
     return this->clipping_rectangle_.back();
   }
 }
+void Display::clear_clipping_() { this->clipping_rectangle_.clear(); }
+bool Display::clip(int x, int y) {
+  if (x < 0 || x >= this->get_width() || y < 0 || y >= this->get_height())
+    return false;
+  if (!this->get_clipping().inside(x, y))
+    return false;
+  return true;
+}
+bool Display::clamp_x_(int x, int w, int &min_x, int &max_x) {
+  min_x = std::max(x, 0);
+  max_x = std::min(x + w, this->get_width());
+
+  if (!this->clipping_rectangle_.empty()) {
+    const auto &rect = this->clipping_rectangle_.back();
+    if (!rect.is_set())
+      return false;
+
+    min_x = std::max(min_x, (int) rect.x);
+    max_x = std::min(max_x, (int) rect.x2());
+  }
+
+  return min_x < max_x;
+}
+bool Display::clamp_y_(int y, int h, int &min_y, int &max_y) {
+  min_y = std::max(y, 0);
+  max_y = std::min(y + h, this->get_height());
+
+  if (!this->clipping_rectangle_.empty()) {
+    const auto &rect = this->clipping_rectangle_.back();
+    if (!rect.is_set())
+      return false;
+
+    min_y = std::max(min_y, (int) rect.y);
+    max_y = std::min(max_y, (int) rect.y2());
+  }
+
+  return min_y < max_y;
+}
 
 DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {}
 void DisplayPage::show() { this->parent_->show_page(this); }
diff --git a/esphome/components/display/display.h b/esphome/components/display/display.h
index 08d8c70e0d..350fd40f26 100644
--- a/esphome/components/display/display.h
+++ b/esphome/components/display/display.h
@@ -472,14 +472,21 @@ class Display {
    *
    * return rect for active clipping region
    */
-  Rect get_clipping();
+  Rect get_clipping() const;
 
   bool is_clipping() const { return !this->clipping_rectangle_.empty(); }
 
+  /** Check if pixel is within region of display.
+   */
+  bool clip(int x, int y);
+
  protected:
+  bool clamp_x_(int x, int w, int &min_x, int &max_x);
+  bool clamp_y_(int y, int h, int &min_y, int &max_y);
   void vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, va_list arg);
 
   void do_update_();
+  void clear_clipping_();
 
   DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES};
   optional<display_writer_t> writer_{};
diff --git a/esphome/components/display/rect.cpp b/esphome/components/display/rect.cpp
index 6e91c86c4f..34b611191f 100644
--- a/esphome/components/display/rect.cpp
+++ b/esphome/components/display/rect.cpp
@@ -60,11 +60,11 @@ void Rect::shrink(Rect rect) {
   }
 }
 
-bool Rect::equal(Rect rect) {
+bool Rect::equal(Rect rect) const {
   return (rect.x == this->x) && (rect.w == this->w) && (rect.y == this->y) && (rect.h == this->h);
 }
 
-bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) {  // NOLINT
+bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) const {  // NOLINT
   if (!this->is_set()) {
     return true;
   }
@@ -75,7 +75,7 @@ bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) {  // NOLINT
   }
 }
 
-bool Rect::inside(Rect rect, bool absolute) {
+bool Rect::inside(Rect rect, bool absolute) const {
   if (!this->is_set() || !rect.is_set()) {
     return true;
   }
diff --git a/esphome/components/display/rect.h b/esphome/components/display/rect.h
index 867a9c67c7..a728ddd132 100644
--- a/esphome/components/display/rect.h
+++ b/esphome/components/display/rect.h
@@ -16,19 +16,19 @@ class Rect {
 
   Rect() : x(VALUE_NO_SET), y(VALUE_NO_SET), w(VALUE_NO_SET), h(VALUE_NO_SET) {}  // NOLINT
   inline Rect(int16_t x, int16_t y, int16_t w, int16_t h) ALWAYS_INLINE : x(x), y(y), w(w), h(h) {}
-  inline int16_t x2() { return this->x + this->w; };  ///< X coordinate of corner
-  inline int16_t y2() { return this->y + this->h; };  ///< Y coordinate of corner
+  inline int16_t x2() const { return this->x + this->w; };  ///< X coordinate of corner
+  inline int16_t y2() const { return this->y + this->h; };  ///< Y coordinate of corner
 
-  inline bool is_set() ALWAYS_INLINE { return (this->h != VALUE_NO_SET) && (this->w != VALUE_NO_SET); }
+  inline bool is_set() const ALWAYS_INLINE { return (this->h != VALUE_NO_SET) && (this->w != VALUE_NO_SET); }
 
   void expand(int16_t horizontal, int16_t vertical);
 
   void extend(Rect rect);
   void shrink(Rect rect);
 
-  bool inside(Rect rect, bool absolute = true);
-  bool inside(int16_t test_x, int16_t test_y, bool absolute = true);
-  bool equal(Rect rect);
+  bool inside(Rect rect, bool absolute = true) const;
+  bool inside(int16_t test_x, int16_t test_y, bool absolute = true) const;
+  bool equal(Rect rect) const;
   void info(const std::string &prefix = "rect info:");
 };