diff --git a/lib/gui/include/widgets/drawablecanvas.h b/lib/gui/include/widgets/drawablecanvas.h index d007f36..980874d 100644 --- a/lib/gui/include/widgets/drawablecanvas.h +++ b/lib/gui/include/widgets/drawablecanvas.h @@ -1,3 +1,10 @@ +/** + * @file drawablecanvas.h + * @author Aditya Agarwal (aditya.agarwal@dumblebots.com) + * @brief File that declares the `DrawableCanvas` class, which provides a canvas to draw on + * + */ + #ifndef __ARDUINO_WIFI_TFT_LCD_CANVAS_APP_WIDGETS_DRAWABLE_CANVAS_H__ #define __ARDUINO_WIFI_TFT_LCD_CANVAS_APP_WIDGETS_DRAWABLE_CANVAS_H__ @@ -7,6 +14,10 @@ #include "WiFiS3.h" #include "cstring" +/** + * @brief Class that provides a canvas to draw on + * + */ class DrawableCanvas : public BasicWidget { public: @@ -18,48 +29,115 @@ class DrawableCanvas : public BasicWidget { constexpr static uint16_t DRAWABLE_H = HEIGHT - 2; constexpr static unsigned MAX_INLINE_COMPRESSED_SEGMENTS = 9; -class BufferedWiFiWriter { + /** + * @brief Class that provides a buffered TCP Stream to write to + * + */ + class BufferedTCPStream { -friend class DrawableCanvas; + friend class DrawableCanvas; -protected: + protected: - constexpr static unsigned BUFFER_CAPACITY = 1024; + constexpr static unsigned BUFFER_CAPACITY = 1024; - WiFiClient *client; - uint8_t buf[BUFFER_CAPACITY]; + /** Pointer to underlying TCP socket */ + WiFiClient *client {nullptr}; - unsigned used {0}; - bool flag {true}; + /** Buffer to store bytes in */ + uint8_t buf[BUFFER_CAPACITY]; -public: + /** Current size of buffer (number of bytes used up) */ + unsigned size {0}; + /** Whether the object is in a valid state or not */ + bool flag {true}; - signed connect(WiFiClient *ptr, const char *server_ip, const uint16_t server_port); - void write(const uint8_t *bytes, unsigned len); - void flush(); - void stop(); - }; + public: + /** + * @brief Connect to a server + * + * @param ptr Reference to TCP Socket to use (discarded after calling `stop`) + * @param server_ip IP Address of server + * @param server_port Port of server + * + * @return Return value of `WiFiClient::connect()` on `ptr` + * + */ + signed connect(WiFiClient *ptr, const char *server_ip, const uint16_t server_port); -class Compressor { + /** + * @brief Write a buffer + * + * @note It is necessary to check `flag` after calling this method to ensure that the object is in a valid state + * + * @param bytes Pointer to buffer + * @param len Number of bytes to pick from the buffer + * + */ + void write(const uint8_t *bytes, unsigned len); -public: + /** + * @brief Flush the stream + * + * @note It is necessary to check `flag` after calling this method to ensure that the object is in a valid state + * + */ + void flush(); - struct segment_t { - uint16_t code: 4; - uint16_t size: 11; - uint16_t flag: 1; + /** + * @brief Flush all data and disconnect from the server + * + */ + void stop(); }; - static_assert(sizeof(segment_t) == sizeof(uint16_t)); - struct canvas_row_t { - uint16_t segment_count: 6; - uint16_t pixel_count: 10; - segment_t *segments; - }; + /** + * @brief Utilities to compress arrays + * + */ + class Compressor { + + public: + + struct segment_t { + uint16_t code: 4; + uint16_t size: 11; + uint16_t flag: 1; + }; + static_assert(sizeof(segment_t) == sizeof(uint16_t)); + + struct canvas_row_t { + uint16_t segment_count: 6; + uint16_t pixel_count: 10; + segment_t *segments; + }; + + /** + * @brief Compress a single row of values into its segment representation + * + * @param row Pointer to destination, where compressed data will be stored + * @param max_segments Maximum number of segments that can be accomodated in the destination + * @param raw_data Pointer to source data, a raw buffer of bytes + * @param raw_data_len Length of raw data + * + * @return The size of the largest prefix of `raw_data` that could be compressed + * + */ static unsigned compress(canvas_row_t *row, unsigned max_segments, uint8_t *raw_data, unsigned raw_data_len); - static unsigned uncompress(canvas_row_t *row, uint8_t *raw_data, unsigned raw_data_len); + + /** + * @brief Decompress segments to a single row + * + * @param row Pointer to source, whether the compressed data is stored + * @param raw_data Pointer to destination, where raw data will be stored + * @param raw_data_len Maximum number of bytes that can be accomodated in the destination + * + * @return The number of bytes in raw data after decompressing + * + */ + static unsigned decompress(canvas_row_t *row, uint8_t *raw_data, unsigned raw_data_len); }; @@ -68,72 +146,276 @@ class Compressor { constexpr static unsigned BUFFER_WIDTH = 28; constexpr static unsigned BUFFER_HEIGHT = 28; + /** Reference to parent frame */ Frame *parent {nullptr}; + /** Flag that indicates if the bitmap is dirty or not */ bool dirty {false}; + /** Flag that indicates if the bitmap's visibility has been changed or not */ bool visibility_changed {false}; - + /** Flag to indicate if the bitmap is hidden or visible */ bool visible {true}; - unsigned widget_x {0}; - unsigned widget_y {0}; + /** X-coordinate of the canvas relative to its parent (offset from left-edge) */ + unsigned widget_x; + /** Y-coordinate of the canvas relative to its parent (offset from top-edge) */ + unsigned widget_y; - unsigned widget_absolute_x {0}; - unsigned widget_absolute_y {0}; + /** X-coordinate of the canvas relative to the display (offset from left-edge) */ + unsigned widget_absolute_x; + /** Y-coordinate of the canvas relative to the display (offset from top-edge) */ + unsigned widget_absolute_y; + /** 16-bit color of the pen */ uint16_t pen_color {0}; + /** Radius of the pen stroke */ unsigned pen_size {0}; + /** IP Address of the server */ char server_ip[16] {0}; + /** Port of the server */ uint16_t server_port {0}; + /** + * @brief In-memory buffer to draw-on + * + * Each stroke from the pen is first drawn on the canvas and then this buffer, + * The contents of the buffer are used while updating the compressed representation of the canvas. + * The entire contents of the canvas will not fit in memory, and therefore a compressed + * representation is stored. Each time the pen creates a new stroke, the compressed + * representation needs to be updated after reading the updated area of the screen. + * Since this is a slow operation, the drawing is also carried onto this in-memory buffer, + * which is used to (after offsetting the pixels) update the compressed representation + * + */ GFXcanvas1 drawing_buffer; + /** Compressed representation of the canvas */ Compressor::canvas_row_t compressed_rows[DRAWABLE_H]; + /** Compressed representation of a single row (used by member functions as buffer) */ Compressor::canvas_row_t cur_row; + /** Function to call when a drawing could successfully be saved/loaded */ InteractiveWidget::callback_t on_success {nullptr}; + /** Function to call when a connection could not be established with the server */ InteractiveWidget::callback_t on_connection_failure {nullptr}; + /** Function to call when the communication with the server failed midway */ InteractiveWidget::callback_t on_communication_failure {nullptr}; + /** Pointer to arguments passed to callbacks */ unsigned *args {nullptr}; + /** Reference to event queue for posting events */ RingQueueInterface *event_queue {nullptr}; public: + /** + * @brief Default constructor disabled (use the `create` method) + * + */ DrawableCanvas() = delete; + /** + * @brief Dynamically create a new canvas instance + * + * @warning This method returns a nullptr if a canvas instance could not be created + * + * @param parent The frame that should own this canvas + * @param x X-coordinate of the canvas, within `parent` (offset from left-edge) + * @param y Y-coordinate of the canvas, within `parent` (offset from top-edge) + * + * @return A pointer to the canvas instance (nullptr if the creation fails) + * + */ static DrawableCanvas *create(Frame *parent, unsigned x, unsigned y); + /** + * @brief Set the color of the stroke + * + * @param new_color 16-bit color to use + * + * @return Pointer to the canvas (allows chaining method calls) + * + */ DrawableCanvas *set_pen_color(uint16_t new_color); + + /** + * @brief Get the color of the stroke + * + * @return 16-bit color of the stroke + * + */ uint16_t get_pen_color() const; + /** + * @brief Set the radius of the stroke + * + * @param new_size Radius to use + * + * @return Pointer to the canvas (allows chaining method calls) + * + */ DrawableCanvas *set_pen_size(uint16_t new_size); + + /** + * @brief Get the radius of the stroke + * + * @return Radius of the stroke + * + */ unsigned get_pen_size() const; + /** + * @brief Set the address of the server + * + * @param new_server_ip IP address of the server (must be a valid IP address that is reachable) + * @param new_server_port Port on which the server is listening + * + * @return Pointer to the canvas (allows chaining method calls) + * + */ DrawableCanvas *set_server_addr(const char *new_server_ip, const uint16_t new_server_port); + /** + * @brief Draw a stroke at a point + * + * @param x X-coordinate of the stroke (offset from left-edge) + * @param y Y-coordinate of the stroke (offset from top-edge) + * + * @return Pointer to the canvas (allows chaining method calls) + * + */ DrawableCanvas *draw_at(unsigned x, unsigned y); + + /** + * @brief Reset the canvas to its original state + * + * @return Pointer to the canvas (allows chaining method calls) + * + */ DrawableCanvas *clear_canvas(); + /** + * @brief Set the function to be called when a drawing was successfully saved/loaded + * + * @param cb Reference to the callback function + * + * @return Pointer to the canvas (allows chaining method calls) + * + */ DrawableCanvas *set_success_callback(InteractiveWidget::callback_t cb); + + /** + * @brief Reset the function to be called when a drawing was successfully saved/loaded + * + * @return Pointer to the canvas (allows chaining method calls) + * + */ DrawableCanvas *reset_success_callback(); + /** + * @brief Set the function to be called when communication could not be established with server + * + * @param cb Reference to the callback function + * + * @return Pointer to the canvas (allows chaining method calls) + * + */ DrawableCanvas *set_connection_failure_callback(InteractiveWidget::callback_t cb); + + /** + * @brief Reset the function to be called when communication could not be established with server + * + * @return Pointer to the canvas (allows chaining method calls) + * + */ DrawableCanvas *reset_connection_failure_callback(); + /** + * @brief Set the function to be called when communication with the server broke midway + * + * @param cb Reference to the callback function + * + * @return Pointer to the canvas (allows chaining method calls) + * + */ DrawableCanvas *set_communication_failure_callback(InteractiveWidget::callback_t cb); + + /** + * @brief Reset the function to be called when communication with the server broke midway + * + * @return Pointer to the canvas (allows chaining method calls) + * + */ DrawableCanvas *reset_communication_failure_callback(); + /** + * @brief Set the reference to the arguments which must be passed to callbacks + * + * @param new_args Reference to the new arguments + * + * @return Pointer to the canvas (allows chaining method calls) + * + */ DrawableCanvas *set_args(unsigned *new_args); + + /** + * @brief Remove the reference to the arguments which are passed to callbacks + * + * @warning This method does not explicitly perform any garbage collection for the old arguments + * + * @return Pointer to the canvas (allows chaining method calls) + * + */ DrawableCanvas *reset_args(); + + /** + * @brief Get a reference to the arguments which are passed to callbacks (as set be calls to `set_args`) + * + * @return Pointer to arguments + * + */ unsigned *get_args() const; + /** + * @brief Set the queue on which the canvas can enqueue events + * + * @param new_event_queue Mutable reference to queue which should be used + * + * @return Pointer to the canvas (allows chaining method calls) + * + */ DrawableCanvas *set_event_queue(RingQueueInterface *new_event_queue); + + /** + * @brief Reset the event queue so that the widget may not enqueue any events + * + * @return Pointer to the canvas (allows chaining method calls) + * + */ DrawableCanvas *reset_event_queue(); + /** + * @brief Save the current drawing to the server + * + * @param slot The slot to save the drawing to (number between 0 and 255 inclusive) + * + * @return true If the drawing could successfully be saved + * @return false If the drawing could not be saved + * + */ bool save_to_server(uint8_t slot); + + /** + * @brief Load a drawing from the server to the canvas, overwriting its contents + * + * @param slot The slot to load the drawing from (number between 0 and 255) + * + * @return true If the drawing could succesfully be loaded + * @return false If the drawing oculd not be loaded + * + */ bool load_from_server(uint8_t slot); // BasicWidget overrides @@ -169,9 +451,21 @@ class Compressor { protected: + /** + * @brief Construct a new Drawable Canvas object + * + * @param parent The frame that should own this canvas + * @param x X-coordinate of the canvas, within `parent` (offset from left-edge) + * @param y Y-coordinate of the canvas, within `parent` (offset from top-edge) + * + */ DrawableCanvas(Frame *parent, unsigned x, unsigned y); + /** + * @brief Reset the compressed representation of the canvas + * + */ void reset_compressed(); }; -#endif \ No newline at end of file +#endif diff --git a/lib/gui/include/widgets/widget.h b/lib/gui/include/widgets/widget.h index 67e2e59..c46e99c 100644 --- a/lib/gui/include/widgets/widget.h +++ b/lib/gui/include/widgets/widget.h @@ -381,7 +381,7 @@ class InteractiveWidget { }; /** - * @brief Set the callback to be called when the widget is pressed + * @brief Set the function to be called when the widget is pressed * * @param cb Reference to the callback function * @@ -391,7 +391,7 @@ class InteractiveWidget { virtual InteractiveWidget *set_onpress(callback_t new_onpress) = 0; /** - * @brief Reset the callback to be called when the widget is pressed + * @brief Reset the function to be called when the widget is pressed * * @return Pointer to the widget (allows chaining method calls) * @@ -399,7 +399,7 @@ class InteractiveWidget { virtual InteractiveWidget *reset_onpress() = 0; /** - * @brief Set the callback to be called when the widget is released + * @brief Set the function to be called when the widget is released * * @param cb Reference to the callback function * @@ -409,7 +409,7 @@ class InteractiveWidget { virtual InteractiveWidget *set_onrelease(callback_t new_onrelease) = 0; /** - * @brief Reset the callback to be called when the widget is released + * @brief Reset the function to be called when the widget is released * * @return Pointer to the widget (allows chaining method calls) * @@ -422,6 +422,7 @@ class InteractiveWidget { * @param new_event_queue Mutable reference to queue which should be used * * @return Pointer to the widget (allows chaining method calls) + * */ virtual InteractiveWidget *set_event_queue(RingQueueInterface *new_event_queue) = 0; diff --git a/lib/gui/src/widgets/drawablecanvas.cpp b/lib/gui/src/widgets/drawablecanvas.cpp index 7fa54c2..c7c223c 100644 --- a/lib/gui/src/widgets/drawablecanvas.cpp +++ b/lib/gui/src/widgets/drawablecanvas.cpp @@ -1,3 +1,10 @@ +/** + * @file button.cpp + * @author Aditya Agarwal (aditya.agarwal@dumblebots.com) + * @brief This file implements the methods of the `Button` class + * + */ + #include "widgets/drawablecanvas.h" static DrawableCanvas::Compressor::segment_t segments1[(DrawableCanvas::DRAWABLE_W + 1) / 2]; @@ -97,7 +104,7 @@ DrawableCanvas *DrawableCanvas::draw_at(unsigned x, unsigned y) { if (compressed_rows[r].pixel_count < col_l) { continue; } - Compressor::uncompress(&compressed_rows[r], raw_pixels, DRAWABLE_W); + Compressor::decompress(&compressed_rows[r], raw_pixels, DRAWABLE_W); final_pixel = compressed_rows[r].pixel_count - 1; for (unsigned c = col_l; c <= col_h; ++c) { @@ -171,7 +178,7 @@ bool DrawableCanvas::save_to_server(uint8_t slot) { constexpr static unsigned MAX_SEGMENTS = (DRAWABLE_W + 1) / 2; uint8_t codes[DRAWABLE_W]; - BufferedWiFiWriter client; + BufferedTCPStream client; if (strnlen(server_ip, 16) == 0 || !client.connect(&sock, server_ip, server_port)) { @@ -196,7 +203,7 @@ bool DrawableCanvas::save_to_server(uint8_t slot) { for (unsigned r = 0; r < DRAWABLE_H; ++r) { - Compressor::uncompress(&compressed_rows[r], codes, DRAWABLE_W); + Compressor::decompress(&compressed_rows[r], codes, DRAWABLE_W); for (unsigned c = compressed_rows[r].pixel_count; c < DRAWABLE_W; ++c) { codes[c] = color_2_code(parent->get_at(widget_x + 1 + c, widget_y + 1 + r)); } @@ -395,42 +402,44 @@ void DrawableCanvas::reset_compressed() { } } -signed DrawableCanvas::BufferedWiFiWriter::connect(WiFiClient *ptr, const char *server_ip, const uint16_t server_port) { +signed DrawableCanvas::BufferedTCPStream::connect(WiFiClient *ptr, const char *server_ip, const uint16_t server_port) { client = ptr; + size = 0; + flag = false; return client->connect(IPAddress(server_ip), server_port); } -void DrawableCanvas::BufferedWiFiWriter::write(const uint8_t *bytes, unsigned len) { +void DrawableCanvas::BufferedTCPStream::write(const uint8_t *bytes, unsigned len) { unsigned n; while (len > 0) { - if (used == BUFFER_CAPACITY) { + if (size == BUFFER_CAPACITY) { flush(); } - n = min(len, BUFFER_CAPACITY - used); + n = min(len, BUFFER_CAPACITY - size); for (const uint8_t *ptr = bytes; ptr != &bytes[n]; ++ptr) { - buf[used++] = *ptr; + buf[size++] = *ptr; } len -= n; bytes += n; } } -void DrawableCanvas::BufferedWiFiWriter::flush() { +void DrawableCanvas::BufferedTCPStream::flush() { unsigned n; - n = client->write(buf, used); - if (n != used) { + n = client->write(buf, size); + if (n != size) { flag = false; } - used = 0; + size = 0; } -void DrawableCanvas::BufferedWiFiWriter::stop() { +void DrawableCanvas::BufferedTCPStream::stop() { flush(); client->flush(); client->stop(); @@ -466,7 +475,7 @@ unsigned DrawableCanvas::Compressor::compress(canvas_row_t *row, unsigned max_se return raw_data_len; } -unsigned DrawableCanvas::Compressor::uncompress(canvas_row_t *row, uint8_t *raw_data, unsigned raw_data_len) { +unsigned DrawableCanvas::Compressor::decompress(canvas_row_t *row, uint8_t *raw_data, unsigned raw_data_len) { uint8_t code; unsigned size;