| // Copyright 2024 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include "pw_draw/text_area.h" |
| |
| #include "pw_color/color.h" |
| #include "pw_draw/draw.h" |
| #include "pw_draw/font_set.h" |
| #include "pw_framebuffer/framebuffer.h" |
| #include "pw_framebuffer/writer.h" |
| |
| using pw::color::color_rgb565_t; |
| using pw::framebuffer::FramebufferWriter; |
| using pw::geometry::Vector2; |
| |
| namespace pw::draw { |
| |
| TextArea::TextArea(pw::framebuffer::Framebuffer& fb, const FontSet& font) |
| : font_(font), framebuffer_(fb) { |
| // SetFont(font); |
| // Default colors: White on Black |
| character_wrap_enabled_ = true; |
| foreground_color_ = 0xFFFF; |
| background_color_ = 0; |
| SetCursor(0, 0); |
| } |
| |
| void TextArea::SetCursor(int x, int y) { |
| cursor_x_ = x; |
| cursor_y_ = y; |
| column_count_ = 0; |
| } |
| |
| void TextArea::SetForegroundColor(color_rgb565_t color) { |
| foreground_color_ = color; |
| } |
| |
| void TextArea::SetBackgroundColor(color_rgb565_t color) { |
| background_color_ = color; |
| } |
| |
| void TextArea::SetCharacterWrap(bool new_setting) { |
| character_wrap_enabled_ = new_setting; |
| } |
| |
| void TextArea::MoveCursorRightOnce() { |
| cursor_x_ = cursor_x_ + font_.width; |
| column_count_++; |
| } |
| |
| void TextArea::InsertLineBreak() { |
| cursor_y_ = cursor_y_ + font_.height; |
| cursor_x_ = cursor_x_ - (column_count_ * font_.width); |
| column_count_ = 0; |
| |
| if (cursor_y_ >= framebuffer_.size().height) { |
| ScrollUp(1); |
| cursor_y_ = cursor_y_ - font_.height; |
| } |
| } |
| |
| void TextArea::DrawCharacter(int character) { |
| if (character == '\n') { |
| InsertLineBreak(); |
| return; |
| } |
| |
| if ((int)character < font_.starting_character || |
| (int)character > font_.ending_character) { |
| // Unprintable character |
| MoveCursorRightOnce(); |
| return; |
| } |
| |
| if (character_wrap_enabled_ && |
| (font_.width + cursor_x_) > framebuffer_.size().width) { |
| InsertLineBreak(); |
| } |
| |
| pw::draw::DrawCharacter(character, |
| Vector2<int>{cursor_x_, cursor_y_}, |
| foreground_color_, |
| background_color_, |
| font_, |
| framebuffer_); |
| |
| // Move cursor to the right by 1 glyph. |
| MoveCursorRightOnce(); |
| } |
| |
| void TextArea::DrawCharacter(int character, int x, int y) { |
| SetCursor(x, y); |
| DrawCharacter(character); |
| } |
| |
| void TextArea::DrawTestFontSheet(int character_column_width, int x, int y) { |
| SetCursor(x, y); |
| for (int c = font_.starting_character; c <= font_.ending_character; c++) { |
| int index = c - font_.starting_character; |
| if (index > 0 && index % character_column_width == 0) { |
| DrawCharacter('\n'); |
| } |
| DrawCharacter(c); |
| } |
| } |
| |
| // DrawText at x, y (upper left pixel of font). Carriage returns will move |
| // text to the next line. |
| void TextArea::DrawText(const char* str) { |
| for (const char* ch = str; *ch != '\0'; ch++) { |
| DrawCharacter(*ch); |
| } |
| } |
| |
| void TextArea::DrawText(const char* str, int x, int y) { |
| SetCursor(x, y); |
| DrawText(str); |
| } |
| |
| void TextArea::DrawText(const wchar_t* str) { |
| for (const wchar_t* ch = str; *ch != L'\0'; ch++) { |
| DrawCharacter(*ch); |
| } |
| } |
| |
| void TextArea::DrawText(const wchar_t* str, int x, int y) { |
| SetCursor(x, y); |
| DrawText(str); |
| } |
| |
| void TextArea::ScrollUp(int lines) { |
| int pixel_height = lines * font_.height; |
| int start_x = 0; |
| int start_y = pixel_height; |
| |
| FramebufferWriter writer(framebuffer_); |
| for (int current_x_ = 0; current_x_ < framebuffer_.size().width; |
| current_x_++) { |
| for (int current_y_ = start_y; current_y_ < framebuffer_.size().height; |
| current_y_++) { |
| if (auto pixel_color = writer.GetPixel(current_x_, current_y_); |
| pixel_color.ok()) { |
| writer.SetPixel( |
| start_x + current_x_, current_y_ - start_y, *pixel_color); |
| } |
| } |
| } |
| |
| // Draw a filled background_color rectangle at the bottom to erase the old |
| // text. |
| for (int x = 0; x < framebuffer_.size().width; x++) { |
| for (int y = framebuffer_.size().height - pixel_height; |
| y < framebuffer_.size().height; |
| y++) { |
| writer.SetPixel(x, y, background_color_); |
| } |
| } |
| } |
| |
| } // namespace pw::draw |