diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7bb7933 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.py[cod] +__pycache__/ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5c31e40..53f5cf5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,6 +13,7 @@ endif() set(LIBOPENUI_SRC libopenui_file.cpp bitmapbuffer.cpp + font.cpp window.cpp layer.cpp form.cpp diff --git a/src/bitmapbuffer.cpp b/src/bitmapbuffer.cpp index 6a4ec41..bdc1b65 100755 --- a/src/bitmapbuffer.cpp +++ b/src/bitmapbuffer.cpp @@ -26,31 +26,9 @@ #include "file_reader.h" #include "intconversions.h" -BitmapBuffer::BitmapBuffer(uint8_t format, uint16_t width, uint16_t height): - BitmapBufferBase(format, width, height, nullptr), - dataAllocated(true) +void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const Bitmap * bitmap, coord_t srcx, coord_t srcy, coord_t srcw, coord_t srch, float scale) { - data = (uint16_t *) malloc(align32(width * height * sizeof(uint16_t))); - dataEnd = data + (width * height); -} - -BitmapBuffer::BitmapBuffer(uint8_t format, uint16_t width, uint16_t height, uint16_t * data): - BitmapBufferBase(format, width, height, data), - dataAllocated(false) -{ -} - -BitmapBuffer::~BitmapBuffer() -{ - if (dataAllocated) { - free(data); - } -} - -template -void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx, coord_t srcy, coord_t srcw, coord_t srch, float scale) -{ - if (!data || !bmp) + if (!data || !bitmap) return; APPLY_OFFSET(); @@ -58,8 +36,8 @@ void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx, if (x >= xmax || y >= ymax) return; - coord_t bmpw = bmp->width(); - coord_t bmph = bmp->height(); + coord_t bmpw = bitmap->width(); + coord_t bmph = bitmap->height(); if (srcw == 0) srcw = bmpw; @@ -92,10 +70,10 @@ void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx, return; } - if (bmp->getFormat() == BMP_ARGB4444 || format == BMP_ARGB4444) - DMACopyAlphaBitmap(data, format == BMP_ARGB4444, _width, _height, x, y, bmp->getData(), bmp->getFormat() == BMP_ARGB4444, bmpw, bmph, srcx, srcy, srcw, srch); + if (bitmap->getFormat() == BMP_ARGB4444 || _format == BMP_ARGB4444) + DMACopyAlphaBitmap(data, _format == BMP_ARGB4444, _width, _height, x, y, bitmap->getData(), bitmap->getFormat() == BMP_ARGB4444, bmpw, bmph, srcx, srcy, srcw, srch); else - DMACopyBitmap(data, _width, _height, x, y, bmp->getData(), bmpw, bmph, srcx, srcy, srcw, srch); + DMACopyBitmap(data, _width, _height, x, y, bitmap->getData(), bmpw, bmph, srcx, srcy, srcw, srch); } else { if (x < xmin) { @@ -127,14 +105,14 @@ void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx, if (y + scaledh > _height) scaledh = _height - y; - if (format == BMP_ARGB4444) { + if (getFormat() == BMP_ARGB4444) { for (int i = 0; i < scaledh; i++) { pixel_t * p = getPixelPtrAbs(x, y + i); - const pixel_t * qstart = bmp->getPixelPtrAbs(srcx, srcy + int(i / scale)); + const pixel_t * qstart = bitmap->getPixelPtrAbs(srcx, srcy + int(i / scale)); for (int j = 0; j < scaledw; j++) { const pixel_t * q = qstart; - q = bmp->getNextPixel(q, j / scale); - if (bmp->getFormat() == BMP_RGB565) { + q = bitmap->getNextPixel(q, j / scale); + if (bitmap->getFormat() == BMP_RGB565) { RGB_SPLIT(*q, r, g, b); drawPixel(p, ARGB_JOIN(0xF, r>>1, g>>2, b>>1)); } @@ -148,11 +126,11 @@ void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx, else { for (int i = 0; i < scaledh; i++) { pixel_t * p = getPixelPtrAbs(x, y + i); - const pixel_t * qstart = bmp->getPixelPtrAbs(srcx, srcy + int(i / scale)); + const pixel_t * qstart = bitmap->getPixelPtrAbs(srcx, srcy + int(i / scale)); for (int j = 0; j < scaledw; j++) { const pixel_t * q = qstart; - q = bmp->getNextPixel(q, j / scale); - if (bmp->getFormat() == BMP_ARGB4444) { + q = bitmap->getNextPixel(q, j / scale); + if (bitmap->getFormat() == BMP_ARGB4444) { ARGB_SPLIT(*q, a, r, g, b); drawAlphaPixel(p, a, RGB_JOIN(r<<1, g<<2, b<<1)); } @@ -166,12 +144,7 @@ void BitmapBuffer::drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx, } } -template void BitmapBuffer::drawBitmap(coord_t, coord_t, BitmapBufferBase const *, coord_t, coord_t, coord_t, coord_t, float); -template void BitmapBuffer::drawBitmap(coord_t, coord_t, const BitmapBuffer *, coord_t, coord_t, coord_t, coord_t, float); -template void BitmapBuffer::drawBitmap(coord_t, coord_t, const RLEBitmap *, coord_t, coord_t, coord_t, coord_t, float); - -template -void BitmapBuffer::drawScaledBitmap(const T * bitmap, coord_t x, coord_t y, coord_t w, coord_t h) +void BitmapBuffer::drawScaledBitmap(const Bitmap * bitmap, coord_t x, coord_t y, coord_t w, coord_t h) { if (bitmap) { auto scale = bitmap->getScale(w, h); @@ -181,11 +154,9 @@ void BitmapBuffer::drawScaledBitmap(const T * bitmap, coord_t x, coord_t y, coor } } -template void BitmapBuffer::drawScaledBitmap(const BitmapBuffer *, coord_t, coord_t, coord_t, coord_t); - void BitmapBuffer::drawAlphaPixel(pixel_t * p, uint8_t alpha, Color565 color) { - if (format == BMP_RGB565) { + if (_format == BMP_RGB565) { if (alpha == ALPHA_MAX) { drawPixel(p, color); } @@ -199,7 +170,7 @@ void BitmapBuffer::drawAlphaPixel(pixel_t * p, uint8_t alpha, Color565 color) drawPixel(p, RGB_JOIN(r, g, b)); } } - else if (format == BMP_ARGB4444) { + else if (_format == BMP_ARGB4444) { if (alpha == ALPHA_MAX) { drawPixel(p, RGB565_TO_ARGB4444(color, 0xFF)); } @@ -306,146 +277,492 @@ void BitmapBuffer::drawVerticalLine(coord_t x, coord_t y, coord_t h, LcdColor co } } +void BitmapBuffer::drawLine(coord_t x1, coord_t y1, coord_t x2, coord_t y2, LcdColor color, uint8_t pat) +{ +// ---------------------------------------------------------------------------- +// Bresenham Line Drawing with Built-In Clipping // -// Liang-barsky clipping algo +// This C++ implementation is based on the algorithm described in the paper: +// "Bresenham's Line Generation Algorithm with Built-in Clipping" +// by Yevgeny P. Kuzmin // -static float lb_max(float arr[], int n) -{ - float m = 0; - for (int i = 0; i < n; ++i) - if (m < arr[i]) m = arr[i]; - return m; -} +// It also derives from the Python implementation by Campbell Barton (ideasman42): +// https://gitlab.com/ideasman42/bresenham-line-plot-clip-py +// +// The original Python version is licensed under the Apache License 2.0, +// which is compatible with the LGPLv3 license used by the libopenui project. +// +// This C++ translation and adaptation for use in libopenui was developed by: +// luftruepel, 2025 +// +// ---------------------------------------------------------------------------- +// Apache License, Version 2.0: +// ---------------------------------------------------------------------------- +// 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: +// +// http://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. +// ---------------------------------------------------------------------------- -// this function gives the minimum -static float lb_min(float arr[], int n) -{ - float m = 1; - for (int i = 0; i < n; ++i) - if (m > arr[i]) m = arr[i]; - return m; -} -bool BitmapBuffer::clipLine(coord_t & x1, coord_t & y1, coord_t & x2, coord_t & y2) -{ - // defining variables - float p1 = -(x2 - x1); - float p2 = -p1; - float p3 = -(y2 - y1); - float p4 = -p3; + // Offsets + x1 += offsetX; + y1 += offsetY; + x2 += offsetX; + y2 += offsetY; - float q1 = x1 - xmin; - float q2 = xmax - 1 - x1; - float q3 = y1 - ymin; - float q4 = ymax - 1 - y1; + auto rgb565 = COLOR_TO_RGB565(color); + uint8_t alpha = GET_COLOR_ALPHA(color); + + // ------------------ + // Case: single point + // ------------------ + if (x1 == x2 && y1 == y2) { + drawAlphaPixelAbs(x1, y1, alpha, rgb565); + return; + } + + // ------------------- + // Case: vertical line + // ------------------- + if (x1 == x2) { + // Case: vertical line completely outside of the clipping area (left or right) + if (x1 < xmin || x1 > xmax) { + return; + } - float posarr[5], negarr[5]; - int posind = 1, negind = 1; - posarr[0] = 1; - negarr[0] = 0; + // Sorting the coordinates to avoid further case differentiation + if (y1 > y2) { + std::swap(y1, y2); + } - if ((p1 == 0 && q1 < 0) || (p2 == 0 && q2 < 0) || (p3 == 0 && q3 < 0) || - (p4 == 0 && q4 < 0)) { - return false; + // Case: vertical line completely outside of the clipping area (top or bottom) + if (y2 < ymin || y1 > ymax) { + return; + } + + // If necessary, adjust the y-coordinates to fit within the clipping area + y1 = std::max(y1, ymin); + y2 = std::min(y2, ymax); + + // Loop through the y-coordinates and draw pixels + for (int y = y1; y <= y2; ++y) { + if ((1 << (y % 8)) & pat) { + drawAlphaPixelAbs(x1, y, alpha, rgb565); + } + } + + return; } + + // --------------------- + // Case: horizontal line + // --------------------- + if (y1 == y2) { + // Case: horizontal line completely outside of the clipping area (top or bottom) + if (y1 < ymin || y1 > ymax) { + return; + } + + // Sorting the coordinates to avoid further case differentiation + if (x1 > x2) { + std::swap(x1, x2); + } + + // Case: horizontal line completely outside of the clipping area (left or right) + if (x2 < xmin || x1 > xmax) { + return; + } + + // If necessary, adjust the x-coordinates to fit within the clipping area + x1 = std::max(x1, xmin); + x2 = std::min(x2, xmax); - if (p1 != 0) { - float r1 = q1 / p1; - float r2 = q2 / p2; - if (p1 < 0) { - negarr[negind++] = r1; // for negative p1, add it to negative array - posarr[posind++] = r2; // and add p2 to positive array - } else { - negarr[negind++] = r2; - posarr[posind++] = r1; + // Loop through the x-coordinates and draw pixels + for (int x = x1; x <= x2; ++x) { + if ((1 << (x % 8)) & pat) { + drawAlphaPixelAbs(x, y1, alpha, rgb565); + } } + + return; } - if (p3 != 0) { - float r3 = q3 / p3; - float r4 = q4 / p4; - if (p3 < 0) { - negarr[negind++] = r3; - posarr[posind++] = r4; - } else { - negarr[negind++] = r4; - posarr[posind++] = r3; + + // ------------------ + // Case: general line + // Check whether the line lies outside the clipping area, or if not, transform the coordinates if necessary to simplify the clipping logic + // ------------------ + int sign_x, sign_y; + + // Use local copies of the clipping bounds to avoid modifying the original global values during coordinate transformations + int clip_xmin = xmin; + int clip_xmax = xmax; + int clip_ymin = ymin; + int clip_ymax = ymax; + + if (x1 < x2) { + // Case: line completely outside the clipping area (left or right) + if (x1 > clip_xmax || x2 < clip_xmin) { + return; + } + + sign_x = 1; + } + else { + // Case: line completely outside the clipping area (left or right) + if (x2 > clip_xmax || x1 < clip_xmin) { + return; } + + sign_x = -1; + + // Transformation of the cooordinates. Due to the transformation delta_x and delta_y are allways positive, so the same logic can be used for both cases. + x1 = -x1; + x2 = -x2; + clip_xmin = -clip_xmin; + clip_xmax = -clip_xmax; + std::swap(clip_xmin, clip_xmax); } - float xn1, yn1, xn2, yn2; - float rn1, rn2; - rn1 = lb_max(negarr, negind); // maximum of negative array - rn2 = lb_min(posarr, posind); // minimum of positive array + if (y1 < y2) { + // Case: line completely outside of the clipping area (top or bottom) + if (y1 > clip_ymax || y2 < clip_ymin) { + return; + } + + sign_y = 1; + } + else { + // Case: line completely outside of the clipping area (top or bottom) + if (y2 > clip_ymax || y1 < clip_ymin) { + return; + } + + sign_y = -1; - if (rn1 > rn2) { // reject - return false; + // Transformation of the cooordinates. Due to the transformation delta_x and delta_y are allways positive, so the same logic can be used for both cases. + y1 = -y1; + y2 = -y2; + clip_ymin = -clip_ymin; + clip_ymax = -clip_ymax; + std::swap(clip_ymin, clip_ymax); } - xn1 = x1 + p2 * rn1; - yn1 = y1 + p4 * rn1; // computing new points + // Calculation of parameters for Bresenham's line algorithm + int delta_x = x2 - x1; + int delta_y = y2 - y1; + int delta_x_step = 2*delta_x; + int delta_y_step = 2*delta_y; - xn2 = x1 + p2 * rn2; - yn2 = y1 + p4 * rn2; + int x_pos = x1; + int y_pos = y1; + + // -------------- + // Case: 45° line + // -------------- + if (delta_x == delta_y) { + + bool set_exit = false; + + // Step 1: Apply clipping to the line starting point (x1, y1) + + // Check if the line intersects the top clipping boundary at y=clip_ymin + if (y1 < clip_ymin) { + // Determine the x-coordinate where the line (or its infinite extension) intersects y=clip_ymin + x_pos += clip_ymin - y1; + + // Case: The computed intersection at y=clip_ymin lies beyond the right clipping boundary at x=clip_xmax, so the line is completely outside the clipping area + if (x_pos > clip_xmax) { + return; + } - x1 = coord_t(xn1); - y1 = coord_t(yn1); - x2 = coord_t(xn2); - y2 = coord_t(yn2); + // Case: The computed intersection at y=clip_ymin lies on the top clipping boundary (clip_xmin== clip_xmin) { + y_pos = clip_ymin; - return true; -} + // If the line intersects the top edge of the clipping area, then the line cannot intersect the left edge of the clipping area, so the check in the next step is not necessary + set_exit = true; + } + } + + // Check if the line intersects the left clipping boundary at x=clip_xmin + if (!set_exit && x1 < clip_xmin) { + // Determine the y-coordinate where the line (or its infinite extension) intersects x=clip_xmin + y_pos += clip_xmin - x1; + + // Case: The computed intersection at x=clip_xmin lies below the bottom clipping boundary at y=clip_ymax, so the line is completely outside the clipping area. The second condition checks whether the intersection lies exactly on the bottom boundary at y=clip_ymax, but with the residual error in the Bresenham algorithm is large enough to cause a downward step. In this case, the point will not be drawn, and the line is effectively outside the visible area. + if (y_pos > clip_ymax) { + return; + } -void BitmapBuffer::drawLine(coord_t x1, coord_t y1, coord_t x2, coord_t y2, LcdColor color, uint8_t pat) -{ - // Offsets - x1 += offsetX; - y1 += offsetY; - x2 += offsetX; - y2 += offsetY; + x_pos = clip_xmin; + } - if (!clipLine(x1, y1, x2, y2)) - return; + // Step 2: Apply clipping to the line end point (x2, y2) + int x_pos_end = x2; + + // Case: The line end point lies below the bottom clipping boundary at y=clip_ymax + if (y2 > clip_ymax) { + // Determine the y-coordinate where the line intersects y=clip_ymax + x_pos_end = x1 + clip_ymax - y1; + } - auto rgb565 = COLOR_TO_RGB565(color); - uint8_t alpha = GET_COLOR_ALPHA(color); + // Ensure the end point does not exceed the clipping area. The +1 is needed because the line drawing loop below uses an exclusive condition (x_pos != x_pos_end), so we must go one step beyond the actual last drawable x position to include it. This adjustment must be applied before the sign-based coordinate back transformation,since applying +1 afterwards would shift the coordinate in the wrong direction when sign_x == -1. + x_pos_end = std::min(x_pos_end, clip_xmax) + 1; + + // Back transformation of the line coordinates + if (sign_y == -1) { + y_pos = -y_pos; + } + + if (sign_x == -1) { + x_pos = -x_pos; + x_pos_end = -x_pos_end; + } - int dx = x2 - x1; /* the horizontal distance of the line */ - int dy = y2 - y1; /* the vertical distance of the line */ - int dxabs = abs(dx); - int dyabs = abs(dy); - int sdx = sgn(dx); - int sdy = sgn(dy); - int x = dyabs >> 1; - int y = dxabs >> 1; - int px = x1; - int py = y1; - - if (dxabs >= dyabs) { - /* the line is more horizontal than vertical */ - for (int i = 0; i <= dxabs; i++) { - if ((1 << (px % 8)) & pat) { - drawAlphaPixelAbs(px, py, alpha, rgb565); + // Line drawing + while (x_pos != x_pos_end) { + if ((1 << (x_pos % 8)) & pat) { + drawAlphaPixelAbs(x_pos, y_pos, alpha, rgb565); } - y += dyabs; - if (y >= dxabs) { - y -= dxabs; - py += sdy; + + y_pos += sign_y; + x_pos += sign_x; + } + + // ----------------------------------------------- + // Case: the line is more horizontal than vertical + // ----------------------------------------------- + } + else if (delta_x > delta_y) { + int temp; + int msd; + int rem; + + int error = delta_y_step - delta_x; + bool set_exit = false; + + // Step 1: Apply clipping to the line starting point (x1, y1) + + // Check if the line intersects the top clipping boundary at y=clip_ymin + if (y1 < clip_ymin) { + // Determine the x-coordinate where the line (or its infinite extension) intersects y=clip_ymin, using Bresenham-style error handling + temp = (2*(clip_ymin - y1) - 1)*delta_x; + msd = temp/delta_y_step; + x_pos += msd; + + // Case: The computed intersection at y=clip_ymin lies beyond the right clipping boundary at x=clip_xmax, so the line is completely outside the clipping area + if (x_pos > clip_xmax) { + return; } - px += sdx; + + // Case: The computed intersection at y=clip_ymin lies on the top clipping boundary (clip_xmin== clip_xmin) { + rem = temp - msd*delta_y_step; + + y_pos = clip_ymin; + error -= rem + delta_x; + + if (rem > 0) { + x_pos += 1; + error += delta_y_step; + } + + // If the line intersects the top edge of the clipping area, then the line cannot intersect the left edge of the clipping area, so the check in the next step is not necessary + set_exit = true; + } } - } + + // Check if the line intersects the left clipping boundary at x=clip_xmin + if (!set_exit && x1 < clip_xmin) { + // Determine the y-coordinate where the line (or its infinite extension) intersects x=clip_xmin, using Bresenham-style error handling + temp = delta_y_step*(clip_xmin - x1); + msd = temp/delta_x_step; + y_pos += msd; + rem = temp % delta_x_step; + + // Case: The computed intersection at x=clip_xmin lies below the bottom clipping boundary at y=clip_ymax, so the line is completely outside the clipping area. The second condition checks whether the intersection lies exactly on the bottom boundary at y=clip_ymax, but with the residual error in the Bresenham algorithm is large enough to cause a downward step. In this case, the point will not be drawn, and the line is effectively outside the visible area. + if (y_pos > clip_ymax || (y_pos == clip_ymax && rem >= delta_x)) { + return; + } + + // The computed intersection at x=clip_xmin lies on the left clipping boundary (clip_ymin== delta_x) { + y_pos += 1; + error -= delta_x_step; + } + } + + // Step 2: Apply clipping to the line end point (x2, y2) + int x_pos_end = x2; + + // Case: The line end point lies below the bottom clipping boundary at y=clip_ymax + if (y2 > clip_ymax) { + // Determine the y-coordinate where the line intersects y=clip_ymax, using Bresenham-style error handling + temp = delta_x_step*(clip_ymax - y1) + delta_x; + msd = temp/delta_y_step; + x_pos_end = x1 + msd; + + // Case: The computed intersection at y=clip_ymax lies exactly on the pixel grid (with no residual error remains). + if ((temp - msd*delta_y_step) == 0) { + x_pos_end -= 1; + } + } + + // Ensure the end point does not exceed the clipping area. The +1 is needed because the Bresenham loop below uses an exclusive condition (x_pos != x_pos_end), so we must go one step beyond the actual last drawable x position to include it. This adjustment must be applied before the sign-based coordinate back transformation,since applying +1 afterwards would shift the coordinate in the wrong direction when sign_x == -1. + x_pos_end = std::min(x_pos_end, clip_xmax) + 1; + + // Back transformation of the line coordinates + if (sign_y == -1) { + y_pos = -y_pos; + } + + if (sign_x == -1) { + x_pos = -x_pos; + x_pos_end = -x_pos_end; + } + + // Bresenham's line algorithm + delta_x_step -= delta_y_step; + + while (x_pos != x_pos_end) { + if ((1 << (x_pos % 8)) & pat) { + drawAlphaPixelAbs(x_pos, y_pos, alpha, rgb565); + } + + if (error >= 0) { + y_pos += sign_y; + error -= delta_x_step; + } + else { + error += delta_y_step; + } + + x_pos += sign_x; + } + + // -------------------------------------------------------------------------------------------------------------- + // Case: the line is more vertical than horizontal (same as previous case, but with swapped x and y coordinates) + // -------------------------------------------------------------------------------------------------------------- + } else { - /* the line is more vertical than horizontal */ - for (int i = 0; i <= dyabs; i++) { - if ((1 << (py % 8)) & pat) { - drawAlphaPixelAbs(px, py, alpha, rgb565); + int temp; + int msd; + int rem; + + int error = delta_x_step - delta_y; + bool set_exit = false; + + // Step 1: Apply clipping to the line starting point (x1, y1) + + // Check if the line intersects the left clipping boundary at x=clip_xmin + if (x1 < clip_xmin) { + // Determine the y-coordinate where the line (or its infinite extension) intersects x=clip_xmin, using Bresenham-style error handling + temp = (2*(clip_xmin - x1) - 1)*delta_y; + msd = temp/delta_x_step; + y_pos += msd; + + // Case: The computed intersection at x=clip_xmin lies below the bottom clipping boundary at y=clip_ymax, so the line is completely outside the clipping area + if (y_pos > clip_ymax) { + return; + } + + // Case: The computed intersection at x=clip_xmin lies on the left clipping boundary (clip_ymin== clip_ymin) { + rem = temp - msd*delta_x_step; + + x_pos = clip_xmin; + error -= rem + delta_y; + + if (rem > 0) { + y_pos += 1; + error += delta_x_step; + } + + // If the line intersects the left edge of the clipping area, then the line cannot intersect the top edge of the clipping area, so the check in the next step is not necessary + set_exit = true; + } + } + + // Check if the line intersects the top clipping boundary at y=clip_ymin + if (!set_exit && y1 < clip_ymin) { + // Determine the x-coordinate where the line (or its infinite extension) intersects y=clip_ymin, using Bresenham-style error handling + temp = delta_x_step*(clip_ymin - y1); + msd = temp/delta_y_step; + x_pos += msd; + rem = temp % delta_y_step; + + // Case: The computed intersection at y=clip_ymin lies on the right of the clipping boundary at x=clip_xmax, so the line is completely outside the clipping area. The second condition checks whether the intersection lies exactly on the right boundary at x=clip_xmax, but with the residual error in the Bresenham algorithm is large enough to cause a rightward step. In this case, the point will not be drawn, and the line is effectively outside the visible area. + if (x_pos > clip_xmax || (x_pos == clip_xmax && rem >= delta_y)) { + return; + } + + // The computed intersection at y=clip_ymin lies on the top clipping boundary (clip_xmin== delta_y) { + x_pos += 1; + error -= delta_y_step; + } + } + + // Step 2: Apply clipping to the line end point (x2, y2) + int y_pos_end = y2; + + // Case: The line end point lies beyond the right clipping boundary at x=clip_xmax + if (x2 > clip_xmax) { + // Determine the y-coordinate where the line intersects x=clip_xmax, using Bresenham-style error handling + temp = delta_y_step*(clip_xmax - x1) + delta_y; + msd = temp/delta_x_step; + y_pos_end = y1 + msd; + + // Case: The computed intersection at x=clip_xmax lies exactly on the pixel grid (with no residual error remains). + if ((temp - msd*delta_x_step) == 0) { + y_pos_end -= 1; + } + } + + // Ensure the end point does not exceed the clipping area. The +1 is needed because the Bresenham loop below uses an exclusive condition (y_pos != y_pos_end), so we must go one step beyond the actual last drawable y position to include it. This adjustment must be applied before the sign-based coordinate back transformation,since applying +1 afterwards would shift the coordinate in the wrong direction when sign_y == -1. + y_pos_end = std::min(y_pos_end, clip_ymax) + 1; + + // Back transformation of the line coordinates + if (sign_x == -1) { + x_pos = -x_pos; + } + + if (sign_y == -1) { + y_pos = -y_pos; + y_pos_end = -y_pos_end; + } + + // Bresenham's line algorithm + delta_y_step -= delta_x_step; + + while (y_pos != y_pos_end) { + if ((1 << (y_pos % 8)) & pat) { + drawAlphaPixelAbs(x_pos, y_pos, alpha, rgb565); } - x += dxabs; - if (x >= dyabs) { - x -= dyabs; - px += sdx; + + if (error >= 0) { + x_pos += sign_x; + error -= delta_y_step; + } + else { + error += delta_x_step; } - py += sdy; + + y_pos += sign_y; } } } @@ -536,13 +853,13 @@ void BitmapBuffer::fillRectangle(coord_t x, coord_t y, coord_t w, coord_t h, pix void BitmapBuffer::drawPlainFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, Color565 color) { - if (format == BMP_RGB565) + if (_format == BMP_RGB565) fillRectangle(x, y, w, h, color); else fillRectangle(x, y, w, h, RGB565_TO_ARGB4444(color, 0xFF)); } -void BitmapBuffer::drawMaskFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, const BitmapMask * mask, Color565 color) +void BitmapBuffer::drawMaskFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, const Mask * mask, Color565 color) { coord_t maskHeight = mask->height(); while (h > 0) { @@ -728,7 +1045,7 @@ class Slope int value; }; -void BitmapBuffer::drawBitmapPatternPie(coord_t x, coord_t y, const uint8_t * img, LcdColor color, int startAngle, int endAngle) +void BitmapBuffer::drawBitmapPatternPie(coord_t x, coord_t y, const Mask * mask, LcdColor color, int startAngle, int endAngle) { if (endAngle == startAngle) { endAngle += 1; @@ -739,10 +1056,9 @@ void BitmapBuffer::drawBitmapPatternPie(coord_t x, coord_t y, const uint8_t * im auto rgb565 = COLOR_TO_RGB565(color); - auto bitmap = (BitmapData *)img; - coord_t width = bitmap->width(); - coord_t height = bitmap->height(); - const uint8_t * q = bitmap->data; + coord_t width = mask->width(); + coord_t height = mask->height(); + const uint8_t * q = mask->getData(); int w2 = width / 2; int h2 = height / 2; @@ -799,8 +1115,7 @@ void BitmapBuffer::drawAnnulusSector(coord_t x, coord_t y, coord_t internalRadiu } } -template -void BitmapBuffer::drawMask(coord_t x, coord_t y, const T * mask, Color565 color, coord_t srcx, coord_t srcy, coord_t srcw, coord_t srch) +void BitmapBuffer::drawMask(coord_t x, coord_t y, const Mask * mask, Color565 color, coord_t srcx, coord_t srcy, coord_t srcw, coord_t srch) { if (!mask) return; @@ -848,14 +1163,10 @@ void BitmapBuffer::drawMask(coord_t x, coord_t y, const T * mask, Color565 color } auto rgb565 = COLOR_TO_RGB565(color); - DMACopyAlphaMask(data, format == BMP_ARGB4444, _width, _height, x, y, mask->getData(), maskWidth, maskHeight, srcx, srcy, srcw, srch, rgb565); + DMACopyAlphaMask(data, _format == BMP_ARGB4444, _width, _height, x, y, mask->getData(), maskWidth, maskHeight, srcx, srcy, srcw, srch, rgb565); } -template void BitmapBuffer::drawMask(coord_t, coord_t, const BitmapData *, Color565, coord_t, coord_t, coord_t, coord_t); -template void BitmapBuffer::drawMask(coord_t, coord_t, const BitmapMask *, Color565, coord_t, coord_t, coord_t, coord_t); -template void BitmapBuffer::drawMask(coord_t, coord_t, const StaticMask *, Color565, coord_t, coord_t, coord_t, coord_t); - -void BitmapBuffer::drawMask(coord_t x, coord_t y, const BitmapMask * mask, const BitmapBuffer * srcBitmap, coord_t offsetX, coord_t offsetY, coord_t width, coord_t height) +void BitmapBuffer::drawMask(coord_t x, coord_t y, const Mask * mask, const Bitmap * srcBitmap, coord_t offsetX, coord_t offsetY, coord_t width, coord_t height) { if (!mask || !srcBitmap) return; @@ -899,12 +1210,9 @@ void BitmapBuffer::drawMask(coord_t x, coord_t y, const BitmapMask * mask, const } } -uint8_t BitmapBuffer::drawChar(coord_t x, coord_t y, const Font::Glyph & glyph, LcdColor color) +void BitmapBuffer::drawChar(coord_t x, coord_t y, const FontGlyph & glyph, LcdColor color) { - if (glyph.width) { - drawMask(x, y, glyph.font->getBitmapData(), color, glyph.offset, 0, glyph.width); - } - return glyph.width; + drawMask(x, y, glyph.data, color, glyph.offset, 0, glyph.width); } #define INCREMENT_POS(delta) do { if (flags & VERTICAL) y -= delta; else x += delta; } while(0) @@ -934,37 +1242,103 @@ coord_t BitmapBuffer::drawSizedText(coord_t x, coord_t y, const char * s, uint8_ coord_t & pos = (flags & VERTICAL) ? y : x; const coord_t orig_pos = pos; - for (int i = 0; len == 0 || i < len; ++i) { - unsigned int c = uint8_t(*s); - // TRACE("c = %d %o 0x%X '%c'", c, c, c, c); + auto curr = s; + while (len == 0 || curr - s < len) { + auto c = getNextUnicodeChar(curr); if (!c) { break; } - else if (c >= CJK_BYTE1_MIN) { - // CJK char - auto glyph = font->getCJKChar(c, *++s); - // TRACE("CJK = %d", c); - uint8_t width = drawChar(x, y, glyph, color); - INCREMENT_POS(width + CHAR_SPACING); - } - else if (c >= 0x20) { - auto glyph = font->getChar(c); - uint8_t width = drawChar(x, y, glyph, color); - if (c >= '0' && c <= '9') - INCREMENT_POS(font->getChar('9').width + CHAR_SPACING); + + if (c == ' ') { + INCREMENT_POS(font->getSpaceWidth()); + continue; + } + + if (c == '\n') { + pos = orig_pos; + if (flags & VERTICAL) + x += height; else - INCREMENT_POS(width + CHAR_SPACING); + y += height; + continue; + } + + auto glyph = font->getGlyph(c); + // TRACE("c = '%lc' 0x%X offset=%d width=%d", c, c, glyph.offset, glyph.width); + if (glyph.width) { + drawChar(x, y, glyph, color); + INCREMENT_POS(glyph.width + font->getSpacing()); + } + else { + TRACE("Missing glyph '%lc' hex=0x%X", c, c); + INCREMENT_POS(font->getSpacing()); + } + } + + RESTORE_OFFSET(); + + return ((flags & RIGHT) ? orig_pos : pos) - offsetX; +} + + +coord_t BitmapBuffer::drawSizedText(coord_t x, coord_t y, const wchar_t * s, uint8_t len, LcdColor color, LcdFlags flags) +{ + MOVE_OFFSET(); + + auto font = getFont(flags); + int height = font->getHeight(); + + if (y + height <= ymin || y >= ymax) { + RESTORE_OFFSET(); + return x; + } + + if (flags & (RIGHT | CENTERED)) { + int width = font->getTextWidth(s, len); + if (flags & RIGHT) { + INCREMENT_POS(-width); + } + else if (flags & CENTERED) { + INCREMENT_POS(-width / 2); + } + } + + coord_t & pos = (flags & VERTICAL) ? y : x; + const coord_t orig_pos = pos; + + auto curr = s; + while (len == 0 || curr - s < len) { + auto c = *curr++; + + if (!c) { + break; + } + + if (c == ' ') { + INCREMENT_POS(font->getSpaceWidth()); + continue; } - else if (c == '\n') { + + if (c == '\n') { pos = orig_pos; if (flags & VERTICAL) x += height; else y += height; + continue; } - s++; + auto glyph = font->getGlyph(c); + // TRACE("c = '%lc' 0x%X offset=%d width=%d", c, c, glyph.offset, glyph.width); + if (glyph.width) { + drawChar(x, y, glyph, color); + INCREMENT_POS(glyph.width + font->getSpacing()); + } + else { + TRACE("Missing glyph '%lc' hex=0x%X", c, c); + INCREMENT_POS(font->getSpacing()); + } } RESTORE_OFFSET(); @@ -1050,20 +1424,20 @@ coord_t BitmapBuffer::drawNumber(coord_t x, coord_t y, int32_t val, LcdColor col //} // -BitmapBuffer * BitmapBuffer::load(const char * filename, int maxSize) +Bitmap * Bitmap::load(const char * path, int maxSize) { - auto ext = getFileExtension(filename); + auto ext = getFileExtension(path); if (ext && !strcmp(ext, ".bmp")) - return load_bmp(filename, maxSize); + return load_bmp(path, maxSize); else - return load_stb(filename, maxSize); + return load_stb(path, maxSize); } -BitmapMask * BitmapMask::load(const char * filename, int maxSize) +Mask * Mask::load(const char * path, int maxSize) { - BitmapBuffer * bitmap = BitmapBuffer::load(filename, maxSize); + auto bitmap = Bitmap::load(path, maxSize); if (bitmap) { - BitmapMask * result = BitmapMask::allocate(BMP_RGB565, bitmap->width(), bitmap->height()); + Mask * result = Mask::allocate(bitmap->width(), bitmap->height()); if (result) { auto * q = result->getData(); for (const auto * p = bitmap->getData(); p < bitmap->getDataEnd(); p++) { @@ -1076,10 +1450,51 @@ BitmapMask * BitmapMask::load(const char * filename, int maxSize) return nullptr; } +Mask * Mask::decodeRle(const uint8_t * data) +{ + auto width = *((uint16_t *)data); + auto height = *((uint16_t *)(data + 2)); + + auto result = Mask::allocate(width, height); + + uint8_t prevByte = 0; + bool prevByteValid = false; + + auto d = result->getData(); + auto destDataEnd = result->getDataEnd(); + + data += 4; + + while (d < destDataEnd) { + uint8_t byte = *data++; + *d++ = byte; + + if (prevByteValid && byte == prevByte) { + uint8_t count = *data++; + + if (d + count > destDataEnd) { + TRACE("RLE decode error: destination overflow!"); + delete result; + return nullptr; + } + + memset(d, byte, count); + d += count; + prevByteValid = false; + } + else { + prevByte = byte; + prevByteValid = true; + } + } + + return result; +} + BitmapBuffer * BitmapBuffer::loadMaskOnBackground(const char * filename, Color565 foreground, Color565 background, int maxSize) { BitmapBuffer * result = nullptr; - const auto * mask = BitmapMask::load(filename, maxSize); + const auto * mask = Mask::load(filename, maxSize); if (mask) { result = BitmapBuffer::allocate(BMP_RGB565, mask->width(), mask->height()); if (result) { @@ -1091,7 +1506,7 @@ BitmapBuffer * BitmapBuffer::loadMaskOnBackground(const char * filename, Color56 return result; } -BitmapBuffer * BitmapBuffer::load_bmp(const char * filename, int maxSize) +Bitmap * Bitmap::load_bmp(const char * filename, int maxSize) { uint8_t palette[16]; @@ -1174,7 +1589,7 @@ BitmapBuffer * BitmapBuffer::load_bmp(const char * filename, int maxSize) return nullptr; } - auto bmp = BitmapBuffer::allocate(BMP_RGB565, w, h); + auto bmp = Bitmap::allocate(BMP_RGB565, w, h); if (!bmp) { TRACE("Bitmap::load(%s) failed: malloc error", filename); return nullptr; @@ -1316,7 +1731,7 @@ void * stb_realloc(void *ptr, unsigned int oldsz, unsigned int newsz) #define STB_IMAGE_IMPLEMENTATION #include "thirdparty/stb/stb_image.h" -BitmapBuffer * BitmapBuffer::load_stb(const char * filename, int maxSize) +Bitmap * Bitmap::load_stb(const char * filename, int maxSize) { int w, h, n; unsigned char * img; @@ -1354,7 +1769,7 @@ BitmapBuffer * BitmapBuffer::load_stb(const char * filename, int maxSize) } // convert to RGB565 or ARGB4444 format - auto bmp = BitmapBuffer::allocate(n == 4 ? BMP_ARGB4444 : BMP_RGB565, w, h); + auto bmp = Bitmap::allocate(n == 4 ? BMP_ARGB4444 : BMP_RGB565, w, h); if (!bmp) { TRACE("Bitmap::load(%s) malloc failed", filename); stbi_image_free(img); diff --git a/src/bitmapbuffer.h b/src/bitmapbuffer.h index 51b79e9..1cbaa3f 100755 --- a/src/bitmapbuffer.h +++ b/src/bitmapbuffer.h @@ -22,14 +22,14 @@ #include #include #include -#include "bitmapdata.h" #include "libopenui_types.h" #include "libopenui_defines.h" #include "libopenui_depends.h" #include "libopenui_helpers.h" -#include "font.h" #include "debug.h" +class FontGlyph; + constexpr uint8_t SOLID = 0xFF; constexpr uint8_t DOTTED = 0x55; constexpr uint8_t DASHED = 0x33; @@ -49,138 +49,119 @@ enum BitmapFormat }; template -class BitmapBufferBase +class Raster { public: - BitmapBufferBase(uint8_t format, uint16_t width, uint16_t height, T * data): - format(format), - _width(width), - _height(height), - xmax(width), - ymax(height), - data(data), - dataEnd(data + (width * height)) - { - } - - BitmapBufferBase(uint8_t format, T * data): - format(format), - _width(*((uint16_t*)data)), - _height(*(((uint16_t*)data) + 1)), - xmax(_width), - ymax(_height), - data((T *)(((uint16_t *)data) + 2)), - dataEnd((T *)(((uint16_t *)data) + 2) + (_width * _height)) - { - } - - [[nodiscard]] inline bool isValid() const - { - return data != nullptr; - } - - inline void clearClippingRect() - { - xmin = 0; - xmax = _width; - ymin = 0; - ymax = _height; - } - - inline void setClippingRect(coord_t xmin, coord_t xmax, coord_t ymin, coord_t ymax) - { - this->xmin = max(0, xmin); - this->xmax = min(_width, xmax); - this->ymin = max(0, ymin); - this->ymax = min(_height, ymax); - } - - inline void setClippingRect(const rect_t & rect) - { - setClippingRect(rect.left(), rect.right(), rect.top(), rect.bottom()); - } - - inline void getClippingRect(coord_t & xmin, coord_t & xmax, coord_t & ymin, coord_t & ymax) const - { - xmin = this->xmin; - xmax = this->xmax; - ymin = this->ymin; - ymax = this->ymax; - } - - inline rect_t getClippingRect() const - { - return {xmin, ymin, xmax - xmin, ymax - ymin }; - } + using DataType = T; - inline void setOffsetX(coord_t offsetX) + Raster(const Raster & other): + _width(other._width), + _height(other._height), + data(other.data), + dataEnd(other.dataEnd), + dataAllocated(false) { - this->offsetX = offsetX; } - inline void setOffsetY(coord_t offsetY) + Raster(uint16_t width, uint16_t height): + _width(width), + _height(height), + data((T *)malloc(align32(width * height * sizeof(T)))), + dataEnd(data + (width * height)), + dataAllocated(true) { - this->offsetY = offsetY; } - inline void setOffset(coord_t offsetX, coord_t offsetY) + Raster(uint16_t width, uint16_t height, T * data): + _width(width), + _height(height), + data(data), + dataEnd(data + (width * height)), + dataAllocated(false) { - setOffsetX(offsetX); - setOffsetY(offsetY); } - inline void clearOffset() + ~Raster() { - setOffset(0, 0); + if (dataAllocated) { + free(data); + } } - inline void reset() + [[nodiscard]] inline uint16_t width() const { - clearOffset(); - clearClippingRect(); + return _width; } - [[nodiscard]] coord_t getOffsetX() const + [[nodiscard]] inline uint16_t height() const { - return offsetX; + return _height; } - [[nodiscard]] coord_t getOffsetY() const + [[nodiscard]] float getScale(coord_t w, coord_t h) const { - return offsetY; + auto vscale = float(h) / height(); + auto hscale = float(w) / width(); + return vscale < hscale ? vscale : hscale; } - [[nodiscard]] inline uint8_t getFormat() const + [[nodiscard]] inline T * getData() const { - return format; + return data; } - [[nodiscard]] inline uint16_t width() const + [[nodiscard]] inline T * getDataEnd() const { - return _width; + return dataEnd; } - [[nodiscard]] inline uint16_t height() const + [[nodiscard]] inline bool isValid() const { - return _height; + return data != nullptr; } - [[nodiscard]] inline T * getData() const + [[nodiscard]] uint32_t getDataSize() const { - return data; + return (const uint8_t *)dataEnd - (const uint8_t *)data; } - [[nodiscard]] inline T * getDataEnd() const + [[nodiscard]] inline const T * getPixelPtrAbs(coord_t x, coord_t y) const { - return dataEnd; +#if LCD_ORIENTATION == 180 + x = _width - x - 1; + y = _height - y - 1; +#elif LCD_ORIENTATION == 270 + #if defined(LTDC_OFFSET_X) + if (isLcdFrameBuffer(data)) + return &data[x * (_height + LTDC_OFFSET_X) + y + LTDC_OFFSET_X]; + else + return &data[x * _height + y]; + #else + return &data[x * _height + y]; + #endif +#endif + return &data[y * _width + x]; } - [[nodiscard]] uint32_t getDataSize() const + inline T * getPixelPtrAbs(coord_t x, coord_t y) { - return _width * _height * sizeof(T); +#if LCD_ORIENTATION == 180 + x = _width - x - 1; + y = _height - y - 1; +#elif LCD_ORIENTATION == 270 + #if defined(LTDC_OFFSET_X) + if (isLcdFrameBuffer(data)) + return &data[x * (_height + LTDC_OFFSET_X) + y + LTDC_OFFSET_X]; + else + return &data[x * _height + y]; + #else + return &data[x * _height + y]; + #endif +#endif + return &data[y * _width + x]; } - inline T * getNextPixel(T * pixel, coord_t count = 1) + inline const T * getNextPixel(const T * pixel, coord_t count = 1) const { #if LCD_ORIENTATION == 180 return pixel - count; @@ -200,7 +181,7 @@ class BitmapBufferBase #endif } - inline const T * getNextPixel(const T * pixel, coord_t count = 1) const + inline T * getNextPixel(T * pixel, coord_t count = 1) { #if LCD_ORIENTATION == 180 return pixel - count; @@ -220,309 +201,368 @@ class BitmapBufferBase #endif } - inline T * getPixelPtrAbs(coord_t x, coord_t y) + protected: + uint16_t _width; + uint16_t _height; + T * data; + T * dataEnd; + bool dataAllocated = false; +}; + +class Mask: public Raster +{ + protected: + using Raster::Raster; + + public: + Mask(const uint8_t * data): + Raster(*((uint16_t *)(data + 0)), *((uint16_t *)(data + 2)), (uint8_t *)(data + 4)) { -#if LCD_ORIENTATION == 180 - x = _width - x - 1; - y = _height - y - 1; -#elif LCD_ORIENTATION == 270 - #if defined(LTDC_OFFSET_X) - if (isLcdFrameBuffer(data)) - return &data[x * (_height + LTDC_OFFSET_X) + y + LTDC_OFFSET_X]; - else - return &data[x * (_height) + y]; - #else - return &data[x * (_height) + y]; - #endif -#endif - return &data[y * _width + x]; } - [[nodiscard]] inline const T * getPixelPtrAbs(coord_t x, coord_t y) const + static Mask * allocate(uint16_t width, uint16_t height) { -#if LCD_ORIENTATION == 180 - x = _width - x - 1; - y = _height - y - 1; -#elif LCD_ORIENTATION == 270 - #if defined(LTDC_OFFSET_X) - if (isLcdFrameBuffer(data)) - return &data[x * (_height + LTDC_OFFSET_X) + y + LTDC_OFFSET_X]; - else - return &data[x * (_height) + y]; - #else - return &data[x * (_height) + y]; - #endif -#endif - return &data[y * _width + x]; + auto result = new Mask(width, height); + if (result && !result->isValid()) { + delete result; + result = nullptr; + } + return result; } - template - C * horizontalFlip() const + static Mask * allocate(const Mask * from, uint16_t width, uint16_t height) { - auto * result = C::allocate(format, width(), height()); - if (!result) { - return nullptr; - } + return allocate(width, height); + } -#if LCD_ORIENTATION == 270 - for (uint8_t y = 0; y < height(); y++) { - for (uint8_t x = 0; x < width(); x++) { - auto * destData = &result->data[x * height() + y]; - auto * srcData = &data[(width() - 1 - x) * height() + y]; - *destData = *srcData; - } - } -#else - auto * srcData = data + width(); - auto * destData = result->data; - for (uint8_t y = 0; y < height(); y++) { - for (uint8_t x = 0; x < width(); x++) { - *(destData++) = *(--srcData); - } - srcData += 2 * width(); - } -#endif - return result; + static Mask * load(const char * path, int maxSize = -1); + + static Mask * decodeRle(const uint8_t * data); +}; + +class Bitmap: public Raster +{ + public: + Bitmap(uint8_t format, uint16_t width, uint16_t height, pixel_t * data): + Raster(width, height, data), + _format(format) + { } - template - C * verticalFlip() const + Bitmap(const uint8_t * data): + Bitmap(*((uint32_t *)data), *((uint16_t *)(data + 4)), *((uint16_t *)(data + 6)), (pixel_t *)(data + 8)) { - auto * result = C::allocate(format, width(), height()); - if (!result) { - return nullptr; - } - -#if LCD_ORIENTATION == 270 - for (uint8_t y = 0; y < height(); y++) { - for (uint8_t x = 0; x < width(); x++) { - auto * destData = &result->data[x * height() + y]; - auto * srcData = &data[x * height() + height() - 1 - y]; - *destData = *srcData; - } - } -#else - auto * srcData = getDataEnd() - width(); - auto * destData = result->data; - for (uint8_t y = 0; y < height(); y++) { - for (uint8_t x = 0; x < width(); x++) { - *(destData++) = *(srcData++); - } - srcData -= 2 * width(); + } + + static Bitmap * allocate(uint8_t format, uint16_t width, uint16_t height) + { + auto result = new Bitmap(format, width, height); + if (result && !result->isValid()) { + delete result; + result = nullptr; } -#endif return result; } - template - C * rotate(float radians) const + static Bitmap * allocate(const Bitmap * from, uint16_t width, uint16_t height) { - float sine = sinf(radians); - float cosine = cosf(radians); + return allocate(from->getFormat(), width, height); + } - auto w = width(); - auto h = height(); + static Bitmap * load(const char * path, int maxSize = -1); - auto x0 = w / 2; - auto y0 = h / 2; + [[nodiscard]] uint8_t getFormat() const + { + return _format; + } - auto * result = C::allocate(format, w, h); - if (!result) { - return nullptr; - } + void setFormat(uint8_t format) + { + _format = format; + } - auto * srcData = data; - auto * destData = result->data; - - for (unsigned rX = 0; rX < w; rX++) { - coord_t dX = rX - x0; - for (unsigned rY = 0; rY < h; rY++) { - coord_t dY = rY - y0; - coord_t x = round(dX * cosine + dY * sine + x0); - if (x >= 0 && x < w) { - coord_t y = round(dY * cosine - dX * sine + y0); - if (y >= 0 && y < h) { - destData[rY * w + rX] = srcData[y * w + x]; - } - else { - destData[rY * w + rX] = 0; - } - } - else { - destData[rY * w + rX] = 0; - } - } - } + protected: + uint8_t _format; - return result; + Bitmap(uint8_t format, uint16_t width, uint16_t height): + Raster(width, height), + _format(format) + { } - template - C * rotate90() const - { - auto * result = C::allocate(format, height(), width()); - if (!result) { - return nullptr; + static Bitmap * load_bmp(const char * filename, int maxSize = -1); + static Bitmap * load_stb(const char * filename, int maxSize = -1); +}; + +template +C * invert(const C * source) +{ + auto result = C::allocate(source, source->width(), source->height()); + if (result) { + auto * srcData = source->getData(); + auto * destData = result->getData(); + for (coord_t y = 0; y < source->height(); y++) { + for (coord_t x = 0; x < source->width(); x++) { + destData[x] = 0xFF - srcData[x]; } + srcData += source->width(); + destData += source->width(); + } + } + return result; +} + +template +C * verticalFlip(const C * source) +{ + auto * result = C::allocate(source, source->width(), source->height()); + if (!result) { + return nullptr; + } + +#if LCD_ORIENTATION == 270 + auto sourceData = source->getData(); + auto sourceWidth = source->width(); + auto sourceHeight = source->height(); + auto resultData = result->getData(); + for (coord_t y = 0; y < sourceHeight; y++) { + for (coord_t x = 0; x < sourceWidth; x++) { + auto * p = &sourceData[x * sourceHeight + sourceHeight - 1 - y]; + auto * q = &resultData[x * sourceHeight + y]; + *q = *p; + } + } +#else + auto * srcData = source->getData() + source->width() * (source->height() - 1); + auto * destData = result->getData(); + for (coord_t y = 0; y < source->height(); y++) { + for (coord_t x = 0; x < source->width(); x++) { + *(destData++) = *(srcData++); + } + srcData -= 2 * source->width(); + } +#endif + return result; +} - #if LCD_ORIENTATION == 270 - auto * srcData = data; - auto * destData = result->data; - for (uint8_t y = 0; y < width(); y++) { - for (uint8_t x = 0; x < height(); x++) { - destData[x * width() + y] = srcData[y * height() + x]; +template +C * rotate(const C * source, float radians) +{ + float sine = sinf(radians); + float cosine = cosf(radians); + + auto w = source->width(); + auto h = source->height(); + + auto x0 = w / 2; + auto y0 = h / 2; + + auto * result = C::allocate(source, w, h); + if (!result) { + return nullptr; + } + + auto * srcData = source->getData(); + auto * destData = result->getData(); + + for (unsigned rX = 0; rX < w; rX++) { + coord_t dX = rX - x0; + for (unsigned rY = 0; rY < h; rY++) { + coord_t dY = rY - y0; + coord_t x = round(dX * cosine + dY * sine + x0); + if (x >= 0 && x < w) { + coord_t y = round(dY * cosine - dX * sine + y0); + if (y >= 0 && y < h) { + destData[rY * w + rX] = srcData[y * w + x]; } - } - #else - auto * destData = result->data + height(); - for (uint8_t y = 0; y < width(); y++) { - auto * srcData = data + y; - for (uint8_t x = 0; x < height(); x++) { - destData--; - *destData = *srcData; - srcData += width(); + else { + destData[rY * w + rX] = 0; } - destData += 2 * height(); } - #endif - return result; + else { + destData[rY * w + rX] = 0; + } } + } - template - C * rotate180() const - { - auto * result = C::allocate(format, width(), height()); - if (!result) { - return nullptr; - } + return result; +} - auto * srcData = data; - auto * destData = result->dataEnd; - while (destData > result->data) { - *(--destData) = *srcData++; - } +template +static C * horizontalFlip(const C * source) +{ + auto * result = C::allocate(source, source->width(), source->height()); + if (!result) { + return nullptr; + } - return result; +#if LCD_ORIENTATION == 270 + auto sourceData = source->getData(); + auto sourceWidth = source->width(); + auto sourceHeight = source->height(); + auto resultData = result->getData(); + for (coord_t y = 0; y < sourceHeight; y++) { + for (coord_t x = 0; x < sourceWidth; x++) { + auto * p = &sourceData[(sourceWidth - 1 - x) * sourceHeight + y]; + auto * q = &resultData[x * sourceHeight + y]; + *q = *p; + } + } +#else + auto sourceWidth = source->width(); + auto * srcData = source->getData() + sourceWidth; + auto * destData = result->getData(); + for (coord_t y = 0; y < source->height(); y++) { + for (coord_t x = 0; x < sourceWidth; x++) { + *(destData++) = *(--srcData); + } + srcData += 2 * sourceWidth; + } +#endif + return result; +} + +template +C * rotate90(const C * source) +{ + auto * result = C::allocate(source, source->height(), source->width()); + if (!result) { + return nullptr; + } + +#if LCD_ORIENTATION == 270 + auto * srcData = source->getData(); + auto * destData = result->getData(); + for (coord_t y = 0; y < source->width(); y++) { + for (coord_t x = 0; x < source->height(); x++) { + destData[x * source->width() + y] = srcData[y * source->height() + x]; } + } +#else + auto * destData = result->getData() + source->height(); + for (coord_t y = 0; y < source->width(); y++) { + auto * srcData = source->getData() + y; + for (coord_t x = 0; x < source->height(); x++) { + destData--; + *destData = *srcData; + srcData += source->width(); + } + destData += 2 * source->height(); + } +#endif + return result; +} - protected: - uint8_t format; - coord_t _width; - coord_t _height; - coord_t xmin = 0; - coord_t xmax; - coord_t ymin = 0; - coord_t ymax; - coord_t offsetX = 0; - coord_t offsetY = 0; - T * data; - T * dataEnd; -}; +template +C * rotate180(const C * source) +{ + auto * result = C::allocate(source, source->width(), source->height()); + if (!result) { + return nullptr; + } + + auto * srcData = source->getData(); + auto * destData = result->getDataEnd(); + while (destData > result->getData()) { + *(--destData) = *srcData++; + } -typedef BitmapBufferBase Bitmap; -typedef BitmapBufferBase StaticMask; + return result; +} -class RLEBitmap: public BitmapBufferBase +class BitmapBuffer: public Bitmap { public: - RLEBitmap(uint8_t format, const uint8_t * rleData) : - BitmapBufferBase(format, 0, 0, nullptr) + BitmapBuffer(uint8_t format, uint16_t width, uint16_t height, pixel_t * data): + Bitmap(format, width, height, data), + xmax(width), + ymax(height) { - _width = *((uint16_t *)rleData); - _height = *(((uint16_t *)rleData) + 1); - uint32_t pixels = _width * _height; - data = (uint16_t*)malloc(align32(pixels * sizeof(uint16_t))); - decode((uint8_t *)data, pixels * sizeof(uint16_t), rleData + 4); - dataEnd = data + pixels; } - ~RLEBitmap() + BitmapBuffer(uint8_t format, uint16_t width, uint16_t height): + Bitmap(format, width, height), + xmax(width), + ymax(height) { - free(data); } - - static int decode(uint8_t * dest, unsigned int destSize, const uint8_t * src) + + inline void clearClippingRect() { - uint8_t prevByte = 0; - bool prevByteValid = false; - - const uint8_t * destEnd = dest + destSize; - uint8_t * d = dest; - - while (d < destEnd) { - uint8_t byte = *src++; - *d++ = byte; - - if (prevByteValid && byte == prevByte) { - uint8_t count = *src++; - - if (d + count > destEnd) { - TRACE("rle_decode_8bit: destination overflow!\n"); - return -1; - } + xmin = 0; + xmax = this->_width; + ymin = 0; + ymax = this->_height; + } + + inline void setClippingRect(coord_t xmin, coord_t xmax, coord_t ymin, coord_t ymax) + { + this->xmin = max(0, xmin); + this->xmax = min(this->_width, xmax); + this->ymin = max(0, ymin); + this->ymax = min(this->_height, ymax); + } + + inline void setClippingRect(const rect_t & rect) + { + setClippingRect(rect.left(), rect.right(), rect.top(), rect.bottom()); + } + + inline void getClippingRect(coord_t & xmin, coord_t & xmax, coord_t & ymin, coord_t & ymax) const + { + xmin = this->xmin; + xmax = this->xmax; + ymin = this->ymin; + ymax = this->ymax; + } + + inline rect_t getClippingRect() const + { + return {xmin, ymin, xmax - xmin, ymax - ymin }; + } - memset(d, byte, count); - d += count; - prevByteValid = false; - } - else { - prevByte = byte; - prevByteValid = true; - } - } + inline void setOffsetX(coord_t offsetX) + { + this->offsetX = offsetX; + } + + inline void setOffsetY(coord_t offsetY) + { + this->offsetY = offsetY; + } - return d - dest; + inline void setOffset(coord_t offsetX, coord_t offsetY) + { + setOffsetX(offsetX); + setOffsetY(offsetY); } -}; -class BitmapMask: public BitmapBufferBase -{ - public: - static BitmapMask * allocate(uint8_t format, uint16_t width, uint16_t height) + inline void clearOffset() { - auto result = new BitmapMask(format, width, height); - if (result && !result->isValid()) { - delete result; - result = nullptr; - } - return result; + setOffset(0, 0); } - protected: - BitmapMask(uint8_t format, uint16_t width, uint16_t height): - BitmapBufferBase(format, width, height, (uint8_t *)malloc(align32(width * height))) + inline void reset() { + clearOffset(); + clearClippingRect(); } - public: - ~BitmapMask() + [[nodiscard]] coord_t getOffsetX() const { - free(data); + return offsetX; } - [[nodiscard]] BitmapMask * invert() const + [[nodiscard]] coord_t getOffsetY() const { - auto result = BitmapMask::allocate(format, width(), height()); - if (result) { - auto * srcData = data; - auto * destData = result->data; - for (auto y = 0; y < height(); y++) { - for (auto x = 0; x < width(); x++) { - destData[x] = 0xFF - srcData[x]; - } - srcData += width(); - destData += width(); - } - } - return result; + return offsetY; } - static BitmapMask * load(const char * filename, int maxSize = -1); -}; + [[nodiscard]] inline DataType * getDataEnd() const + { + return dataEnd; + } -class BitmapBuffer: public BitmapBufferBase -{ - public: static BitmapBuffer * allocate(uint8_t format, uint16_t width, uint16_t height) { auto result = new BitmapBuffer(format, width, height); @@ -533,24 +573,9 @@ class BitmapBuffer: public BitmapBufferBase return result; } - protected: - BitmapBuffer(uint8_t format, uint16_t width, uint16_t height); - - public: - BitmapBuffer(uint8_t format, uint16_t width, uint16_t height, uint16_t * data); - - ~BitmapBuffer(); - - [[nodiscard]] float getScale(coord_t w, coord_t h) const - { - auto vscale = float(h) / height(); - auto hscale = float(w) / width(); - return vscale < hscale ? vscale : hscale; - } - - inline void setFormat(uint8_t format) + static BitmapBuffer * allocate(const Bitmap * from, uint16_t width, uint16_t height) { - this->format = format; + return allocate(from->getFormat(), width, height); } inline void clear(Color565 color = 0 /*black*/) @@ -565,7 +590,7 @@ class BitmapBuffer: public BitmapBufferBase if (!applyPixelClippingRect(x, y)) return nullptr; - return BitmapBufferBase::getPixelPtrAbs(x, y); + return getPixelPtrAbs(x, y); } inline void drawPixel(coord_t x, coord_t y, pixel_t value) @@ -623,7 +648,7 @@ class BitmapBuffer: public BitmapBufferBase void drawPlainFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, Color565 color); - void drawMaskFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, const BitmapMask * mask, Color565 color); + void drawMaskFilledRectangle(coord_t x, coord_t y, coord_t w, coord_t h, const Mask * mask, Color565 color); void drawCircle(coord_t x, coord_t y, coord_t radius, LcdColor color); @@ -635,24 +660,26 @@ class BitmapBuffer: public BitmapBufferBase void drawBitmapPie(int x0, int y0, const uint16_t * img, int startAngle, int endAngle); - void drawBitmapPatternPie(coord_t x0, coord_t y0, const uint8_t * img, LcdColor color, int startAngle, int endAngle); - - static BitmapBuffer * load(const char * filename, int maxSize = -1); + void drawBitmapPatternPie(coord_t x0, coord_t y0, const Mask * mask, LcdColor color, int startAngle, int endAngle); static BitmapBuffer * loadMaskOnBackground(const char * filename, Color565 foreground, Color565 background, int maxSize = -1); - template - void drawMask(coord_t x, coord_t y, const T * mask, Color565 color, coord_t srcx = 0, coord_t srcy = 0, coord_t srcw = 0, coord_t srch = 0); - - void drawMask(coord_t x, coord_t y, const BitmapMask * mask, const BitmapBuffer * srcBitmap, coord_t offsetX = 0, coord_t offsetY = 0, coord_t width = 0, coord_t height = 0); + void drawMask(coord_t x, coord_t y, const Mask * mask, Color565 color, coord_t srcx = 0, coord_t srcy = 0, coord_t srcw = 0, coord_t srch = 0); + void drawMask(coord_t x, coord_t y, const Mask * mask, const Bitmap * srcBitmap, coord_t offsetX = 0, coord_t offsetY = 0, coord_t width = 0, coord_t height = 0); coord_t drawSizedText(coord_t x, coord_t y, const char * s, uint8_t len, LcdColor color, LcdFlags flags = 0); + coord_t drawSizedText(coord_t x, coord_t y, const wchar_t * s, uint8_t len, LcdColor color, LcdFlags flags = 0); coord_t drawText(coord_t x, coord_t y, const char * s, LcdColor color, LcdFlags flags = 0) { return drawSizedText(x, y, s, 0, color, flags); } + coord_t drawText(coord_t x, coord_t y, const wchar_t * s, LcdColor color, LcdFlags flags = 0) + { + return drawSizedText(x, y, s, 0, color, flags); + } + coord_t drawTextAtIndex(coord_t x, coord_t y, const char * s, uint8_t idx, LcdColor color, LcdFlags flags = 0) { char length = *s++; @@ -661,20 +688,22 @@ class BitmapBuffer: public BitmapBufferBase coord_t drawNumber(coord_t x, coord_t y, int32_t val, LcdColor color, LcdFlags flags = 0, uint8_t len = 0, const char * prefix = nullptr, const char * suffix = nullptr); - template - void drawBitmap(coord_t x, coord_t y, const T * bmp, coord_t srcx = 0, coord_t srcy = 0, coord_t srcw = 0, coord_t srch = 0, float scale = 0); - void copyFrom(const BitmapBuffer * other) { DMACopyBitmap(getData(), _width, _height, 0, 0, other->getData(), _width, _height, 0, 0, _width, _height); } - template - void drawScaledBitmap(const T * bitmap, coord_t x, coord_t y, coord_t w, coord_t h); + void drawBitmap(coord_t x, coord_t y, const Bitmap * bitmap, coord_t srcx = 0, coord_t srcy = 0, coord_t srcw = 0, coord_t srch = 0, float scale = 0); + + void drawScaledBitmap(const Bitmap * bitmap, coord_t x, coord_t y, coord_t w, coord_t h); protected: - static BitmapBuffer * load_bmp(const char * filename, int maxSize = -1); - static BitmapBuffer * load_stb(const char * filename, int maxSize = -1); + coord_t xmin = 0; + coord_t xmax; + coord_t ymin = 0; + coord_t ymax; + coord_t offsetX = 0; + coord_t offsetY = 0; inline bool applyClippingRect(coord_t & x, coord_t & y, coord_t & w, coord_t & h) const { @@ -716,7 +745,7 @@ class BitmapBuffer: public BitmapBufferBase return applyClippingRect(x, y, w, h); } - uint8_t drawChar(coord_t x, coord_t y, const Font::Glyph & glyph, LcdColor color); + void drawChar(coord_t x, coord_t y, const FontGlyph & glyph, LcdColor color); inline void drawPixel(pixel_t * p, pixel_t value) { @@ -752,15 +781,12 @@ class BitmapBuffer: public BitmapBufferBase void drawHorizontalLineAbs(coord_t x, coord_t y, coord_t w, LcdColor color, uint8_t pat = SOLID); - bool clipLine(coord_t& x1, coord_t& y1, coord_t& x2, coord_t& y2); - void fillRectangle(coord_t x, coord_t y, coord_t w, coord_t h, pixel_t color); void fillBottomFlatTriangle(coord_t x0, coord_t y0, coord_t x1, coord_t y1, coord_t x2, LcdColor color); void fillTopFlatTriangle(coord_t x0, coord_t y0, coord_t x1, coord_t x2, coord_t y2, LcdColor color); private: - bool dataAllocated; #if defined(DEBUG) bool leakReported = false; #endif diff --git a/src/bitmapdata.h b/src/bitmapdata.h deleted file mode 100644 index c02ce84..0000000 --- a/src/bitmapdata.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) OpenTX - * - * Source: - * https://github.com/opentx/libopenui - * - * This file is a part of libopenui library. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - */ - -#pragma once - -#include - -struct BitmapData -{ - uint16_t _width; - uint16_t _height; - uint8_t data[]; - - uint16_t width() const - { - return _width; - } - - uint16_t height() const - { - return _height; - } - - const uint8_t * getData() const - { - return data; - } -}; diff --git a/src/bufferedwindow.h b/src/bufferedwindow.h index 4603c58..60b2833 100644 --- a/src/bufferedwindow.h +++ b/src/bufferedwindow.h @@ -80,7 +80,7 @@ class TransparentBufferedWindow: public BufferedWindow class TransparentBitmapBackground: public TransparentBufferedWindow { public: - TransparentBitmapBackground(Window * parent, const rect_t & rect, const BitmapBuffer * bitmap): + TransparentBitmapBackground(Window * parent, const rect_t & rect, const Bitmap * bitmap): TransparentBufferedWindow(parent, rect, OPAQUE), background(bitmap) { @@ -94,7 +94,7 @@ class TransparentBitmapBackground: public TransparentBufferedWindow #endif protected: - const BitmapBuffer * background; + const Bitmap * background; void paintUpdate(BitmapBuffer * dc) override { diff --git a/src/choice.cpp b/src/choice.cpp index 58da394..31fe1d7 100644 --- a/src/choice.cpp +++ b/src/choice.cpp @@ -158,7 +158,6 @@ void Choice::openMenu() }); setEditMode(true); - invalidate(); } #if defined(HARDWARE_TOUCH) diff --git a/src/choice.h b/src/choice.h index 798a227..9fab7ee 100644 --- a/src/choice.h +++ b/src/choice.h @@ -87,6 +87,15 @@ class Choice: public ChoiceBase Choice(FormGroup * parent, const rect_t & rect, const char * values, int vmin, int vmax, std::function getValue, std::function setValue = nullptr, WindowFlags windowFlags = 0); + Choice(FormGroup * parent, const rect_t & rect, std::function getValue, std::function setValue = nullptr, WindowFlags windowFlags = 0): + ChoiceBase(parent, rect, CHOICE_TYPE_DROPOWN, windowFlags), + vmin(0), + vmax(-1), + getValue(std::move(getValue)), + setValue(std::move(setValue)) + { + } + void addValue(const char * value); void addValues(const char * const values[], uint8_t count); @@ -112,6 +121,11 @@ class Choice: public ChoiceBase bool onTouchEnd(coord_t x, coord_t y) override; #endif + void setGetValueHandler(std::function handler) + { + getValue = std::move(handler); + } + void setSetValueHandler(std::function handler) { setValue = std::move(handler); diff --git a/src/filechoice.cpp b/src/filechoice.cpp index c1a5c42..b55ebe4 100644 --- a/src/filechoice.cpp +++ b/src/filechoice.cpp @@ -117,7 +117,7 @@ bool FileChoice::openMenu() } } - new MessageDialog(this, STR_SDCARD, STR_NO_FILES_ON_SD, exclamationIcon); + new MessageDialog(this, STR_SDCARD, STR_NO_FILES_ON_SD, maskIconExclamation); return false; } diff --git a/src/font.cpp b/src/font.cpp new file mode 100644 index 0000000..2c7822b --- /dev/null +++ b/src/font.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) OpenTX + * + * Source: + * https://github.com/opentx/libopenui + * + * This file is a part of libopenui library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +#include +#include +#include "bitmapbuffer.h" +#include "font.h" +#include "debug.h" + +/* Font format + * 'F', 'N', 'T', '1' + * begin: 4bytes (glyphs index start) + * end: 4bytes (glyphs index end, not included) + * specs: 2bytes * (count + 1) + * data: bitmap data in RLE format + */ +bool Font::loadFile(const char * path) +{ + TRACE("Font::loadFile('%s')", path); + + auto file = (FIL *)malloc(sizeof(FIL)); + if (!file) { + return false; + } + + FRESULT result = f_open(file, path, FA_READ); + if (result != FR_OK) { + free(file); + return false; + } + + struct { + char fmt[4]; + char name[LEN_FONT_NAME + 1]; + uint8_t rangesCount; + uint8_t spacing; + uint8_t spaceWidth; + } header; + UINT read; + result = f_read(file, (uint8_t *)&header, sizeof(header), &read); + if (result != FR_OK || read != sizeof(header) || strncmp(header.fmt, "FNT1", sizeof(header.fmt)) != 0) { + TRACE("loadFont('%s'): invalid header", path); + f_close(file); + free(file); + return false; + } + + strlcpy(this->name, header.name, sizeof(this->name)); + this->spacing = header.spacing; + this->spaceWidth = header.spaceWidth; + + for (auto i = 0; i < header.rangesCount; i++) { + struct { + uint32_t begin; + uint32_t end; + uint32_t dataSize; + } rangeHeader; + + result = f_read(file, (uint8_t *)&rangeHeader, sizeof(rangeHeader), &read); + if (result != FR_OK || read != sizeof(rangeHeader) || rangeHeader.begin >= rangeHeader.end) { + TRACE("loadFont('%s'): invalid range header", path); + f_close(file); + free(file); + return false; + } + + uint32_t specsSize = sizeof(uint16_t) * (rangeHeader.end - rangeHeader.begin + 1); + auto specs = (uint16_t *)malloc(specsSize); + if (!specs) { + f_close(file); + free(file); + return false; + } + + result = f_read(file, (uint8_t *)specs, specsSize, &read); + if (result != FR_OK || read != specsSize) { + free(specs); + f_close(file); + free(file); + return false; + } + + auto data = (uint8_t *)malloc(rangeHeader.dataSize); + if (!data) { + free(specs); + f_close(file); + free(file); + return false; + } + + result = f_read(file, (uint8_t *)data, rangeHeader.dataSize, &read); + if (result != FR_OK || read != rangeHeader.dataSize) { + free(specs); + free(data); + f_close(file); + free(file); + return false; + } + + auto mask = Mask::decodeRle(data); + free(data); + if (mask) { + ranges.push_back({rangeHeader.begin, rangeHeader.end, mask, specs}); + } + } + + // TRACE("Ranges..."); + // for (auto & range: ranges) { + // TRACE(" %d-%d", range.begin, range.end); + // } + + f_close(file); + free(file); + + return true; +} diff --git a/src/font.h b/src/font.h index 624d1f2..6126665 100644 --- a/src/font.h +++ b/src/font.h @@ -19,56 +19,78 @@ #pragma once +#include +#include #include "libopenui_types.h" #include "libopenui_config.h" -#include "bitmapdata.h" +#include "bitmapbuffer.h" +#include "unicode.h" -constexpr uint8_t CJK_BYTE1_MIN = 0xFD; +constexpr uint8_t LEN_FONT_NAME = 8; -inline bool hasChineseChars(const char * str) -{ - while (*str) { - uint8_t c = *str++; - if (c >= CJK_BYTE1_MIN) { - return true; - } - } - return false; -} - -inline const char * findNextLine(const char * stack) +inline const char * findNextLine(const char * str) { while (true) { - const char * pos = strchr(stack, '\n'); - if (!pos) + auto current = str; + auto c = getNextUnicodeChar(str); + if (c == '\0') return nullptr; - if (pos == stack || *((uint8_t *)(pos - 1)) < CJK_BYTE1_MIN) - return pos; - stack = pos + 1; + else if (c == '\n') + return current; } } +class FontGlyph +{ + public: + const Mask * data; + unsigned offset; + uint8_t width; +}; + class Font { public: - struct Glyph + struct GlyphRange { - const Font * font; - unsigned offset; - uint8_t width; + uint32_t begin; + uint32_t end; + const Mask * data; + const uint16_t * specs; }; - Font(const char * name, uint16_t count, const BitmapData * data, const uint16_t * specs): - name(name), - count(count), - data(data), - specs(specs) + Font() = default; + + Font(const char * name) + { + strlcpy(this->name, name, sizeof(this->name)); + } + + Font(const char * name, uint8_t spacing, uint8_t spaceWidth, std::list ranges): + spacing(spacing), + spaceWidth(spaceWidth), + ranges(std::move(ranges)) { + strlcpy(this->name, name, sizeof(this->name)); + } + + bool loadFile(const char * path); + + void addGlyphs(const Font * other) + { + for (auto range: other->ranges) { + addRange(range); + } + } + + void addRange(const GlyphRange & range) + { + ranges.push_back(range); } coord_t getHeight() const { - return specs[0]; + return ranges.empty() ? 0 : ranges.front().data->height(); } const char * getName() const @@ -76,78 +98,147 @@ class Font return name; } - Glyph getGlyph(unsigned index) const + FontGlyph getGlyph(wchar_t c) const { - // TODO check index not over table - auto offset = specs[index + 1]; - return {this, offset, uint8_t(specs[index + 2] - offset)}; + for (auto & range: ranges) { + if (range.begin <= uint32_t(c) && uint32_t(c) < range.end) { + auto index = c - range.begin; + unsigned offset = range.specs[index]; + uint8_t width = range.specs[index + 1] - offset; + if (width > 0) { + return {range.data, offset, width}; + } + } + } + return {}; } - Glyph getChar(uint8_t c) const + bool hasGlyphs(const char * s) const { - if (c >= 0x20) { - return getGlyph(c - 0x20); - } - else { - return {this, 0, 0}; + auto curr = s; + while (true) { + auto c = getNextUnicodeChar(curr); + if (!c) { + return true; + } + if (c != '\n' && c != ' ') { + auto glyph = getGlyph(c); + if (glyph.width == 0) { + return false; + } + } } } - [[nodiscard]] Glyph getCJKChar(uint8_t byte1, uint8_t byte2) const + uint8_t getGlyphWidth(wchar_t c) const { - unsigned result = byte2 + ((byte1 - CJK_BYTE1_MIN) << 8u) - 1; - if (result >= 0x200) - result -= 1; - if (result >= 0x100) - result -= 1; - return getGlyph(CJK_FIRST_LETTER_INDEX + result); + if (c == ' ') { + return spaceWidth; + } + else { + auto glyph = getGlyph(c); + return glyph.width + spacing; + } } coord_t getTextWidth(const char * s, int len = 0) const { int currentLineWidth = 0; int result = 0; - for (int i = 0; len == 0 || i < len; ++i) { - unsigned c = uint8_t(*s); + + auto curr = s; + while (len == 0 || curr - s < len) { + auto c = getNextUnicodeChar(curr); if (!c) { break; } - else if (c >= CJK_BYTE1_MIN) { - currentLineWidth += getCJKChar(c, *++s).width + CHAR_SPACING; + if (c == '\n') { + if (currentLineWidth > result) + result = currentLineWidth; + currentLineWidth = 0; } - else if (c >= 0x20) { - if (c >= '0' && c <= '9') - currentLineWidth += getChar('9').width + CHAR_SPACING; - else - currentLineWidth += getChar(c).width + CHAR_SPACING; + else { + currentLineWidth += getGlyphWidth(c); + } + } + + return currentLineWidth > result ? currentLineWidth : result; + } + + coord_t getTextWidth(const wchar_t * s, int len = 0) const + { + int currentLineWidth = 0; + int result = 0; + + auto curr = s; + while (len == 0 || curr - s < len) { + auto c = *curr++; + if (!c) { + break; } - else if (c == '\n') { + if (c == '\n') { if (currentLineWidth > result) result = currentLineWidth; currentLineWidth = 0; } - - ++s; + else { + currentLineWidth += getGlyphWidth(c); + } } return currentLineWidth > result ? currentLineWidth : result; } - [[nodiscard]] const BitmapData * getBitmapData() const + uint8_t getSpacing() const + { + return spacing; + } + + uint8_t getSpaceWidth() const + { + return spaceWidth; + } + + wchar_t begin() const + { + wchar_t result = ' '; + for (auto & range: ranges) { + if (range.begin < uint32_t(result)) { + result = range.begin; + } + } + return result; + } + + wchar_t end() const + { + wchar_t result = ' '; + for (auto & range: ranges) { + if (range.end > uint32_t(result)) { + result = range.end; + } + } + return result; + } + + uint8_t isSubsetLoaded(const char * value) const { - return data; + return std::find_if(subsets.begin(), subsets.end(), [value](const char * s) { + return std::strcmp(s, value) == 0; + }) != subsets.end(); } - [[nodiscard]] bool hasCJKChars() const + void setSubsetLoaded(const char * value) { - return count > CJK_FIRST_LETTER_INDEX; + subsets.push_back(value); } protected: - const char * name; - uint16_t count; - const BitmapData * data; - const uint16_t * specs; + char name[LEN_FONT_NAME + 1]; + uint8_t spacing = 1; + uint8_t spaceWidth = 4; + std::list subsets; + std::list ranges; }; const Font * getFont(LcdFlags flags); @@ -158,7 +249,8 @@ inline coord_t getFontHeight(LcdFlags flags) return getFont(flags)->getHeight(); } -inline coord_t getTextWidth(const char * s, int len = 0, LcdFlags flags = 0) +template +inline coord_t getTextWidth(const T * s, int len = 0, LcdFlags flags = 0) { return getFont(flags)->getTextWidth(s, len); } diff --git a/src/keyboard_text.cpp b/src/keyboard_text.cpp index fdf66f0..b2bd5c1 100644 --- a/src/keyboard_text.cpp +++ b/src/keyboard_text.cpp @@ -22,87 +22,55 @@ using namespace ui; -TextKeyboard * TextKeyboard::_instance = nullptr; - -const uint8_t LBM_KEY_UPPERCASE[] = { -#include "mask_key_uppercase.lbm" -}; - -const uint8_t LBM_KEY_LOWERCASE[] = { -#include "mask_key_lowercase.lbm" -}; - -const uint8_t LBM_KEY_BACKSPACE[] = { -#include "mask_key_backspace.lbm" -}; - -const uint8_t LBM_KEY_LETTERS[] = { -#include "mask_key_letters.lbm" -}; - -const uint8_t LBM_KEY_NUMBERS[] = { -#include "mask_key_numbers.lbm" -}; - -const uint8_t LBM_KEY_SPACEBAR[] = { -#include "mask_key_spacebar.lbm" -}; - -const uint8_t * const LBM_SPECIAL_KEYS[] = { - LBM_KEY_BACKSPACE, - LBM_KEY_UPPERCASE, - LBM_KEY_LOWERCASE, - LBM_KEY_LETTERS, - LBM_KEY_NUMBERS, -}; +TextKeyboardBase * TextKeyboardBase::_instance = nullptr; const char * const KEYBOARD_QWERTY_LOWERCASE[] = { "qwertyuiop", " asdfghjkl", KEYBOARD_SET_UPPERCASE "zxcvbnm" KEYBOARD_BACKSPACE, - KEYBOARD_SET_NUMBERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SET_NUMBERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const KEYBOARD_AZERTY_LOWERCASE[] = { "azertyuiop", " qsdfghjklm", KEYBOARD_SET_UPPERCASE "wxcvbn," KEYBOARD_BACKSPACE, - KEYBOARD_SET_NUMBERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SET_NUMBERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const KEYBOARD_QWERTZ_LOWERCASE[] = { "qwertzuiop", " asdfghjkl", KEYBOARD_SET_UPPERCASE "yxcvbnm" KEYBOARD_BACKSPACE, - KEYBOARD_SET_NUMBERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SET_NUMBERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const KEYBOARD_QWERTY_UPPERCASE[] = { "QWERTYUIOP", " ASDFGHJKL", KEYBOARD_SET_LOWERCASE "ZXCVBNM" KEYBOARD_BACKSPACE, - KEYBOARD_SET_NUMBERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SET_NUMBERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const KEYBOARD_AZERTY_UPPERCASE[] = { "AZERTYUIOP", " QSDFGHJKLM", KEYBOARD_SET_LOWERCASE "WXCVBN," KEYBOARD_BACKSPACE, - KEYBOARD_SET_NUMBERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SET_NUMBERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const KEYBOARD_QWERTZ_UPPERCASE[] = { "QWERTZUIOP", " ASDFGHJKL", KEYBOARD_SET_LOWERCASE "YXCVBNM" KEYBOARD_BACKSPACE, - KEYBOARD_SET_NUMBERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SET_NUMBERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const KEYBOARD_NUMBERS[] = { "1234567890", - KEYBOARD_NUMBERS_FIRST_LINE_SPECIAL_CHARS, - KEYBOARD_NUMBERS_SECOND_LINE_SPECIAL_CHARS KEYBOARD_BACKSPACE, - KEYBOARD_SET_LETTERS KEYBOARD_SPACE KEYBOARD_ENTER + KEYBOARD_SPECIAL_CHAR_1 KEYBOARD_SPECIAL_CHAR_2 KEYBOARD_SPECIAL_CHAR_3 KEYBOARD_SPECIAL_CHAR_4 KEYBOARD_SPECIAL_CHAR_5 KEYBOARD_SPECIAL_CHAR_6 KEYBOARD_SPECIAL_CHAR_7 KEYBOARD_SPECIAL_CHAR_8 KEYBOARD_SPECIAL_CHAR_9 KEYBOARD_SPECIAL_CHAR_10, + KEYBOARD_SPECIAL_CHAR_11 KEYBOARD_SPECIAL_CHAR_12 KEYBOARD_SPECIAL_CHAR_13 KEYBOARD_SPECIAL_CHAR_14 KEYBOARD_SPECIAL_CHAR_15 KEYBOARD_SPECIAL_CHAR_16 KEYBOARD_SPECIAL_CHAR_17 KEYBOARD_SPECIAL_CHAR_18 KEYBOARD_SPECIAL_CHAR_19 KEYBOARD_BACKSPACE, + KEYBOARD_SET_LETTERS KEYBOARD_SPACEBAR KEYBOARD_ENTER }; const char * const * KEYBOARDS[] = { @@ -115,15 +83,21 @@ const char * const * KEYBOARDS[] = { KEYBOARD_NUMBERS, }; -TextKeyboard::TextKeyboard(): - Keyboard(TEXT_KEYBOARD_HEIGHT) -{ -} - -TextKeyboard::~TextKeyboard() -{ - _instance = nullptr; -} +#if defined(LIBOPENUI_USE_DEFAULT_TEXT_KEYBOARD) +extern const Mask * maskKeyUppercase; +extern const Mask * maskKeyLowercase; +extern const Mask * maskKeyBackspace; +extern const Mask * maskKeyLetters; +extern const Mask * maskKeyNumbers; +extern const Mask * maskKeySpacebar; + +const Mask * const MASKS_SPECIAL_KEYS[] = { + maskKeyBackspace, + maskKeyUppercase, + maskKeyLowercase, + maskKeyLetters, + maskKeyNumbers, +}; void TextKeyboard::paint(BitmapBuffer * dc) { @@ -139,21 +113,22 @@ void TextKeyboard::paint(BitmapBuffer * dc) if (*c == ' ') { x += 15; } - else if (*c == KEYBOARD_SPACE[0]) { + else if (uint8_t(*c) <= SPECIAL_KEY_WITH_BITMAP_LAST) { + // special keys drawn with a bitmap + dc->drawMask(x, y, MASKS_SPECIAL_KEYS[uint8_t(*c) - 1], DEFAULT_COLOR); + x += 45; + } + else if (*c == SPECIAL_KEY_SPACEBAR) { // spacebar - dc->drawMask(x, y, (const BitmapData *)LBM_KEY_SPACEBAR, DEFAULT_COLOR); + dc->drawMask(x, y, maskKeySpacebar, DEFAULT_COLOR); x += 135; } - else if (*c == KEYBOARD_ENTER[0]) { + else if (*c == SPECIAL_KEY_ENTER) { // enter dc->drawPlainFilledRectangle(x, y - 2, 80, 25, DISABLE_COLOR); - dc->drawText(x+40, y, "ENTER", DEFAULT_COLOR, CENTERED); + dc->drawText(x + 40, y, "ENTER", DEFAULT_COLOR, CENTERED); x += 80; } - else if (uint8_t(*c) <= KEYBOARD_SET_NUMBERS[0]) { - dc->drawMask(x, y, (const BitmapData *)LBM_SPECIAL_KEYS[uint8_t(*c) - 1], DEFAULT_COLOR); - x += 45; - } else { dc->drawSizedText(x, y, c, 1, DEFAULT_COLOR); x += 30; @@ -170,19 +145,19 @@ bool TextKeyboard::onTouchEnd(coord_t x, coord_t y) onKeyPress(); uint8_t row = max(0, y - 5) / 40; - const char * key = layout[row]; + const uint8_t * key = (uint8_t *)layout[row]; while (*key) { if (*key == ' ') { x -= 15; } - else if (*key == KEYBOARD_SPACE[0]) { + else if (*key == SPECIAL_KEY_SPACEBAR) { if (x <= 135) { pushEvent(EVT_VIRTUAL_KEY(' ')); return true; } x -= 135; } - else if (*key == KEYBOARD_ENTER[0]) { + else if (*key == SPECIAL_KEY_ENTER) { if (x <= 80) { // enter hide(); @@ -190,30 +165,29 @@ bool TextKeyboard::onTouchEnd(coord_t x, coord_t y) } x -= 80; } - else if (uint8_t(*key) <= KEYBOARD_SET_NUMBERS[0]) { + else if (*key <= SPECIAL_KEY_WITH_BITMAP_LAST) { if (x <= 45) { - uint8_t specialKey = *key; - switch (specialKey) { + switch (*key) { case SPECIAL_KEY_BACKSPACE: // backspace events.push(EVT_VIRTUAL_KEY(SPECIAL_KEY_BACKSPACE)); break; - case SPECIAL_KEY_SET_LOWERCASE: - layoutIndex = getKeyboardLayout() + LOWERCASE_OPTION; - invalidate(); - break; case SPECIAL_KEY_SET_UPPERCASE: layoutIndex = getKeyboardLayout(); invalidate(); break; - case SPECIAL_KEY_SET_NUMBERS: - layoutIndex = KEYBOARD_LAYOUT_NUMBERS; + case SPECIAL_KEY_SET_LOWERCASE: + layoutIndex = getKeyboardLayout() + LOWERCASE_OPTION; invalidate(); break; case SPECIAL_KEY_SET_LETTERS: layoutIndex = getKeyboardLayout() + LOWERCASE_OPTION; invalidate(); break; + case SPECIAL_KEY_SET_NUMBERS: + layoutIndex = KEYBOARD_LAYOUT_NUMBERS; + invalidate(); + break; } break; } @@ -231,3 +205,4 @@ bool TextKeyboard::onTouchEnd(coord_t x, coord_t y) return true; } +#endif diff --git a/src/keyboard_text.h b/src/keyboard_text.h index af330c8..c1cb9cc 100644 --- a/src/keyboard_text.h +++ b/src/keyboard_text.h @@ -22,8 +22,6 @@ #include "keyboard_base.h" #include "textedit.h" -extern const uint8_t LBM_KEY_SPACEBAR[]; -extern const uint8_t * const LBM_SPECIAL_KEYS[]; extern const char * const * KEYBOARDS[]; constexpr uint8_t LOWERCASE_OPTION = 1; @@ -48,50 +46,57 @@ enum KeyboardLayout namespace ui { -class TextKeyboard: public Keyboard +class TextKeyboardBase: public Keyboard { public: - TextKeyboard(); + TextKeyboardBase(): + Keyboard(TEXT_KEYBOARD_HEIGHT) + { + } - ~TextKeyboard() override; + ~TextKeyboardBase() override + { + _instance = nullptr; + } -#if defined(DEBUG_WINDOWS) - [[nodiscard]] std::string getName() const override + static void show(FormField * field) { - return "TextKeyboard"; + if (activeKeyboard != _instance) { + _instance->layoutIndex = KEYBOARD_LAYOUT_INDEX_START; + } + _instance->setField(field); } -#endif - static void setInstance(TextKeyboard * keyboard) + static void setInstance(TextKeyboardBase * keyboard) { _instance = keyboard; } - static TextKeyboard * instance() + static TextKeyboardBase * instance() { return _instance; } - static void show(TextEdit * field) +#if defined(DEBUG_WINDOWS) + [[nodiscard]] std::string getName() const override { - if (!_instance) { - _instance = new TextKeyboard(); - } - if (activeKeyboard != _instance) { - _instance->layoutIndex = KEYBOARD_LAYOUT_INDEX_START; - } - _instance->setField(field); + return "TextKeyboard"; } +#endif + protected: + static TextKeyboardBase * _instance; + unsigned layoutIndex = KEYBOARD_LAYOUT_INDEX_START; +}; + +class TextKeyboard: public TextKeyboardBase +{ + protected: void paint(BitmapBuffer * dc) override; #if defined(HARDWARE_TOUCH) bool onTouchEnd(coord_t x, coord_t y) override; #endif - - protected: - static TextKeyboard * _instance; - unsigned layoutIndex = KEYBOARD_LAYOUT_INDEX_START; }; } diff --git a/src/libopenui_compat.h b/src/libopenui_compat.h index a552df5..376235d 100644 --- a/src/libopenui_compat.h +++ b/src/libopenui_compat.h @@ -20,6 +20,7 @@ #pragma once #if defined(__MINGW32__) || !defined(__GNUC__) + #undef UNICODE #include #include #include diff --git a/src/libopenui_defines.h b/src/libopenui_defines.h index 10a0a35..f0c40e5 100644 --- a/src/libopenui_defines.h +++ b/src/libopenui_defines.h @@ -51,8 +51,8 @@ constexpr uint8_t DECIMALS(uint8_t value) return value << 4u; } -#define FONT_MASK 0x0F00u -#define FONTS_MAX_COUNT 16 +#define FONT_MASK 0x1F00u +#define MAX_FONTS 0x20 #define FONT_INDEX(flags) (((flags) & FONT_MASK) >> 8u) #define FONT(xx) (unsigned(FONT_ ## xx ## _INDEX) << 8u) diff --git a/src/libopenui_types.h b/src/libopenui_types.h index 6eea3ce..48e167b 100644 --- a/src/libopenui_types.h +++ b/src/libopenui_types.h @@ -104,4 +104,3 @@ typedef uint16_t event_t; typedef uint16_t Color565; typedef uint32_t LcdColor; typedef uint32_t WindowFlags; -typedef uint8_t charSuite[2]; diff --git a/src/menu.cpp b/src/menu.cpp index 44e8c6a..3b8b93f 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -27,17 +27,20 @@ using namespace ui; void MenuBody::select(int index) { selectedIndex = index; - if (innerHeight > height()) { - if (scrollPositionY + height() < MENUS_LINE_HEIGHT * (index + 1)) { - setScrollPositionY(MENUS_LINE_HEIGHT * (index + 1) - height()); - } - else if (scrollPositionY > MENUS_LINE_HEIGHT * index) { - setScrollPositionY(MENUS_LINE_HEIGHT * index); + + if (index >= 0) { + if (innerHeight > height()) { + if (scrollPositionY + height() < MENUS_LINE_HEIGHT * (index + 1)) { + setScrollPositionY(MENUS_LINE_HEIGHT * (index + 1) - height()); + } + else if (scrollPositionY > MENUS_LINE_HEIGHT * index) { + setScrollPositionY(MENUS_LINE_HEIGHT * index); + } } - } - if (lines[index].onSelect) { - lines[index].onSelect(); + if (lines[index].onSelect) { + lines[index].onSelect(); + } } invalidate(); @@ -50,13 +53,13 @@ void MenuBody::onEvent(event_t event) if (event == EVT_ROTARY_RIGHT) { if (!lines.empty()) { - select(int((selectedIndex + 1) % lines.size())); + select(int(selectedIndex < 0 ? defaultSelection : (selectedIndex >= lines.size() - 1 ? 0 : selectedIndex + 1))); onKeyPress(); } } else if (event == EVT_ROTARY_LEFT) { if (!lines.empty()) { - select(int(selectedIndex <= 0 ? lines.size() - 1 : selectedIndex - 1)); + select(int(selectedIndex < 0 ? defaultSelection : (selectedIndex == 0 ? lines.size() - 1 : selectedIndex - 1))); onKeyPress(); } } @@ -64,28 +67,28 @@ void MenuBody::onEvent(event_t event) if (!lines.empty()) { onKeyPress(); if (selectedIndex < 0) { - select(0); + select(defaultSelection); } - else { + else if (multiple) { + lines[selectedIndex].onPress(); + getParentMenu()->invalidate(); + } + else if (autoClose) { auto menu = getParentMenu(); - if (menu->multiple) { - lines[selectedIndex].onPress(); - menu->invalidate(); - } - else { - Layer::pop(menu); - lines[selectedIndex].onPress(); - menu->deleteLater(); // called at the end in case onPress changes the closeHandler - } + Layer::pop(menu); + lines[selectedIndex].onPress(); + menu->deleteLater(); // called at the end in case onPress changes the closeHandler + } + else { + lines[selectedIndex].onPress(); } } } else if (event == EVT_KEY_BREAK(KEY_EXIT)) { onKeyPress(); - if (onCancel) { - onCancel(); + if (!onCancel || onCancel()) { + Window::onEvent(event); } - Window::onEvent(event); } else { Window::onEvent(event); @@ -96,22 +99,26 @@ void MenuBody::onEvent(event_t event) #if defined(HARDWARE_TOUCH) bool MenuBody::onTouchEnd(coord_t /*x*/, coord_t y) { - Menu * menu = getParentMenu(); + auto * menu = getParentMenu(); int index = y / MENUS_LINE_HEIGHT; if (index < (int)lines.size()) { onKeyPress(); - if (menu->multiple) { + if (multiple) { if (selectedIndex == index && lines[index].onPress) lines[index].onPress(); else select(index); menu->invalidate(); } - else { + else if (autoClose) { Layer::pop(menu); lines[index].onPress(); menu->deleteLater(); // called at the end in case onPress changes the closeHandler } + else { + selectedIndex = index; + lines[index].onPress(); + } } return true; } @@ -155,8 +162,7 @@ void MenuBody::paint(BitmapBuffer * dc) } } - Menu * menu = getParentMenu(); - if (menu->multiple && line.isChecked) { + if (multiple && line.isChecked) { theme->drawCheckBox(dc, line.isChecked(), IS_TRANSLATION_RIGHT_TO_LEFT() ? MENUS_HORIZONTAL_PADDING : width() - MENUS_HORIZONTAL_PADDING - CHECKBOX_WIDTH, i * MENUS_LINE_HEIGHT + (MENUS_LINE_HEIGHT - CHECKBOX_WIDTH) / 2, 0); } @@ -166,9 +172,9 @@ void MenuBody::paint(BitmapBuffer * dc) } } -MenuWindowContent::MenuWindowContent(Menu * parent, bool footer): - ModalWindowContent(parent, {(LCD_W - MIN_MENUS_WIDTH) / 2, (LCD_H - MIN_MENUS_WIDTH) / 2, MIN_MENUS_WIDTH, 0}), - body(this, {0, 0, MIN_MENUS_WIDTH, 0}) +MenuWindowContent::MenuWindowContent(ModalWindow * parent, const rect_t & rect, bool multiple, bool footer): + ModalWindowContent(parent, rect), + body(this, {0, 0, MIN_MENUS_WIDTH, 0}, multiple) { body.setFocus(SET_FOCUS_DEFAULT); if (footer) { @@ -209,8 +215,7 @@ void MenuWindowContent::paint(BitmapBuffer * dc) Menu::Menu(Window * parent, bool multiple, bool footer): ModalWindow(parent, true), - content(createMenuWindow(this, footer)), - multiple(multiple) + content(createMenuWindow(this, multiple, footer)) { } @@ -235,7 +240,7 @@ void Menu::setTitle(std::string text) updatePosition(); } -void Menu::addLine(const std::string & text, const BitmapMask * mask, std::function onPress, std::function onSelect, std::function isChecked) +void Menu::addLine(const std::string & text, const Mask * mask, std::function onPress, std::function onSelect, std::function isChecked) { content->body.addLine(text, mask, std::move(onPress), std::move(onSelect), std::move(isChecked)); if (content->width() < MAX_MENUS_WIDTH) { @@ -248,7 +253,7 @@ void Menu::addLine(const std::string & text, const BitmapMask * mask, std::funct updatePosition(); } -void Menu::addCustomLine(std::function drawLine, std::function onPress, std::function onSelect, std::function isChecked) +void Menu::addCustomLine(std::function drawLine, std::function onPress, std::function onSelect, std::function isChecked) { content->body.addCustomLine(std::move(drawLine), std::move(onPress), std::move(onSelect), std::move(isChecked)); updatePosition(); @@ -266,7 +271,7 @@ void Menu::onEvent(event_t event) if (event == EVT_KEY_BREAK(KEY_EXIT)) { deleteLater(); } - else if (event == EVT_KEY_BREAK(KEY_ENTER) && !multiple) { + else if (event == EVT_KEY_BREAK(KEY_ENTER) && content->body.autoClose && !content->body.multiple) { deleteLater(); } } diff --git a/src/menu.h b/src/menu.h index 690a28e..28e4dbb 100644 --- a/src/menu.h +++ b/src/menu.h @@ -34,7 +34,6 @@ class MenuWindowContent; class MenuBody: public Window { - friend class MenuWindowContent; friend class Menu; class MenuLine @@ -42,7 +41,7 @@ class MenuBody: public Window friend class MenuBody; public: - MenuLine(std::string text, const BitmapMask * icon, std::function onPress, std::function onSelect, std::function isChecked): + MenuLine(std::string text, const Mask * icon, std::function onPress, std::function onSelect, std::function isChecked): text(std::move(text)), icon(icon), onPress(std::move(onPress)), @@ -51,7 +50,7 @@ class MenuBody: public Window { } - MenuLine(std::function drawLine, std::function onPress, std::function onSelect, std::function isChecked): + MenuLine(std::function drawLine, std::function onPress, std::function onSelect, std::function isChecked): drawLine(std::move(drawLine)), onPress(std::move(onPress)), onSelect(std::move(onSelect)), @@ -65,16 +64,17 @@ class MenuBody: public Window protected: std::string text; - const BitmapMask * icon; - std::function drawLine; + const Mask * icon; + std::function drawLine; std::function onPress; std::function onSelect; std::function isChecked; }; public: - MenuBody(Window * parent, const rect_t & rect): - Window(parent, rect, OPAQUE) + MenuBody(Window * parent, const rect_t & rect, bool multiple): + Window(parent, rect, OPAQUE), + multiple(multiple) { setPageHeight(MENUS_LINE_HEIGHT); } @@ -106,7 +106,7 @@ class MenuBody: public Window bool onTouchEnd(coord_t x, coord_t y) override; #endif - void addLine(const std::string & text, const BitmapMask * icon, std::function onPress, std::function onSelect, std::function isChecked) + void addLine(const std::string & text, const Mask * icon = nullptr, std::function onPress = nullptr, std::function onSelect = nullptr, std::function isChecked = nullptr) { lines.emplace_back(text, icon, std::move(onPress), std::move(onSelect), std::move(isChecked)); if (icon) @@ -114,7 +114,7 @@ class MenuBody: public Window invalidate(); } - void addCustomLine(std::function drawLine, std::function onPress, std::function onSelect, std::function isChecked) + void addCustomLine(std::function drawLine, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr) { lines.emplace_back(std::move(drawLine), std::move(onPress), std::move(onSelect), std::move(isChecked)); invalidate(); @@ -126,24 +126,46 @@ class MenuBody: public Window invalidate(); } - void setCancelHandler(std::function handler) + void setCancelHandler(std::function handler) { onCancel = std::move(handler); } + void setDefaultSelection(int index) + { + defaultSelection = index; + } + + int getDefaultSelection() + { + return defaultSelection; + } + + void setAutoClose(bool value) + { + autoClose = value; + } + void paint(BitmapBuffer * dc) override; - protected: std::vector lines; + + protected: #if defined(HARDWARE_TOUCH) int selectedIndex = -1; #else int selectedIndex = 0; #endif - std::function onCancel; + int defaultSelection = 0; + std::function onCancel; bool displayIcons = false; + bool autoClose = true; + bool multiple = false; - inline Menu * getParentMenu(); + inline Window * getParentMenu() + { + return getParent()->getParent(); + } }; class MenuWindowContent: public ModalWindowContent @@ -151,7 +173,7 @@ class MenuWindowContent: public ModalWindowContent friend class Menu; public: - explicit MenuWindowContent(Menu * parent, bool footer = false); + explicit MenuWindowContent(ModalWindow * parent, const rect_t & rect, bool multiple, bool footer); void deleteLater(bool detach = true, bool trash = true) override { @@ -175,6 +197,11 @@ class MenuWindowContent: public ModalWindowContent void paint(BitmapBuffer * dc) override; + MenuBody * getBody() + { + return &body; + } + FormGroup * getFooter() const { return footer; @@ -187,8 +214,6 @@ class MenuWindowContent: public ModalWindowContent class Menu: public ModalWindow { - friend class MenuBody; - public: explicit Menu(Window * parent, bool multiple = false, bool footer = false); @@ -199,7 +224,7 @@ class Menu: public ModalWindow } #endif - void setCancelHandler(std::function handler) + void setCancelHandler(std::function handler) { content->body.setCancelHandler(std::move(handler)); } @@ -211,13 +236,13 @@ class Menu: public ModalWindow void setTitle(std::string text); - void addLine(const std::string & text, const BitmapMask * icon, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr); + void addLine(const std::string & text, const Mask * icon, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr); void addLine(const std::string & text, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr) { addLine(text, nullptr, std::move(onPress), std::move(onSelect), std::move(isChecked)); } - void addCustomLine(std::function drawLine, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr); + void addCustomLine(std::function drawLine, std::function onPress, std::function onSelect = nullptr, std::function isChecked = nullptr); void removeLines(); @@ -245,14 +270,30 @@ class Menu: public ModalWindow protected: MenuWindowContent * content; - bool multiple; std::function waitHandler; void updatePosition(); }; -Menu * MenuBody::getParentMenu() +class MultiMenu: public ModalWindow { - return static_cast(getParent()->getParent()); -} + public: + explicit MultiMenu(Window * parent): + ModalWindow(parent, true) + { + } + + void addColumn(MenuWindowContent * column) + { + columns.emplace_back(column); + } + + MenuWindowContent * getColumn(uint8_t index) + { + return columns[index]; + } + + protected: + std::vector columns; +}; } diff --git a/src/menutoolbar.cpp b/src/menutoolbar.cpp index dd13169..a5610b9 100644 --- a/src/menutoolbar.cpp +++ b/src/menutoolbar.cpp @@ -26,9 +26,9 @@ void MenuToolbarButton::paint(BitmapBuffer * dc) { if (checked()) { dc->drawPlainFilledRectangle(MENUS_TOOLBAR_BUTTON_PADDING, MENUS_TOOLBAR_BUTTON_PADDING, MENUS_TOOLBAR_BUTTON_WIDTH - 2 * MENUS_TOOLBAR_BUTTON_PADDING, MENUS_TOOLBAR_BUTTON_WIDTH - 2 * MENUS_TOOLBAR_BUTTON_PADDING, FOCUS_BGCOLOR); - dc->drawSizedText(rect.w / 2, (rect.h - getFontHeight(FONT(STD))) / 2 + 1, &picto, 1, FOCUS_COLOR, CENTERED); + dc->drawSizedText(rect.w / 2, (rect.h - getFontHeight(FONT(M))) / 2 + 1, &picto, 1, FOCUS_COLOR, CENTERED); } else { - dc->drawSizedText(rect.w / 2, (rect.h - getFontHeight(FONT(STD))) / 2 + 1, &picto, 1, DEFAULT_COLOR, CENTERED); + dc->drawSizedText(rect.w / 2, (rect.h - getFontHeight(FONT(M))) / 2 + 1, &picto, 1, DEFAULT_COLOR, CENTERED); } } \ No newline at end of file diff --git a/src/modal_window.cpp b/src/modal_window.cpp index 35aa644..1b80c92 100644 --- a/src/modal_window.cpp +++ b/src/modal_window.cpp @@ -49,6 +49,6 @@ void ModalWindow::paint(BitmapBuffer * dc) void ModalWindowContent::paint(BitmapBuffer * dc) { dc->drawPlainFilledRectangle(0, 0, width(), POPUP_HEADER_HEIGHT, FOCUS_BGCOLOR); - dc->drawText(FIELD_PADDING_LEFT, (POPUP_HEADER_HEIGHT - getFontHeight(FONT(STD))) / 2, title.c_str(), FOCUS_COLOR); + dc->drawText(FIELD_PADDING_LEFT, (POPUP_HEADER_HEIGHT - getFontHeight(FONT(M))) / 2, title.c_str(), FOCUS_COLOR); dc->drawPlainFilledRectangle(0, POPUP_HEADER_HEIGHT, width(), height() - POPUP_HEADER_HEIGHT, DEFAULT_BGCOLOR); } diff --git a/src/numberedit.h b/src/numberedit.h index 0bbe5ce..c857851 100644 --- a/src/numberedit.h +++ b/src/numberedit.h @@ -56,7 +56,7 @@ class NumberEdit: public BaseNumberEdit ContextAll = 0xFF, }; - void setGetStringValueHandler(std::function handler, ValueContext context = ContextAll) + void setGetStringValueHandler(std::function handler, ValueContext context = ContextAll) { _getStringValueContext = context; _getStringValue = std::move(handler); @@ -111,7 +111,7 @@ class NumberEdit: public BaseNumberEdit if (value == 0 && !zeroText.empty()) return zeroText; else if (_getStringValue && (context & _getStringValueContext)) - return _getStringValue(value); + return _getStringValue(value, context == ContextKeyboard); else return getDefaultStringValue(value); } @@ -130,7 +130,7 @@ class NumberEdit: public BaseNumberEdit std::string suffix; std::string zeroText; std::function isValueAvailable; - std::function _getStringValue; + std::function _getStringValue; #if defined(SOFTWARE_KEYBOARD) bool keyboardEnabled = true; #endif diff --git a/src/roller.h b/src/roller.h index afd8cea..c8a44b2 100644 --- a/src/roller.h +++ b/src/roller.h @@ -93,7 +93,7 @@ class Roller: public Choice auto fgColor = DISABLE_COLOR; if (value == displayedValue) { - fgColor = FOCUS_COLOR | FONT(STD); + fgColor = FOCUS_COLOR | FONT(M); } unsigned valueIndex = displayedValue - vmin; diff --git a/src/static.h b/src/static.h index 97979df..11884bf 100644 --- a/src/static.h +++ b/src/static.h @@ -21,6 +21,7 @@ #include "window.h" #include "button.h" // TODO just for BUTTON_BACKGROUND +#include "font.h" constexpr coord_t STATIC_TEXT_INTERLINE_HEIGHT = 2; @@ -111,7 +112,7 @@ class Subtitle: public StaticText { public: Subtitle(Window * parent, const rect_t & rect, const char * text): - StaticText(parent, rect, text, 0, FONT(BOLD)) + StaticText(parent, rect, text, 0, FONT(M_BOLD)) { } @@ -132,21 +133,14 @@ class StaticBitmap: public Window { } - StaticBitmap(Window * parent, const rect_t & rect, const char * filename, bool scale = false): - Window(parent, rect), - bitmap(BitmapBuffer::load(filename)), - scale(scale) - { - } - - StaticBitmap(Window * parent, const rect_t & rect, const BitmapBuffer * bitmap, bool scale = false): + StaticBitmap(Window * parent, const rect_t & rect, const Bitmap * bitmap, bool scale = false): Window(parent, rect), bitmap(bitmap), scale(scale) { } - StaticBitmap(Window * parent, const rect_t & rect, const BitmapMask * mask, LcdFlags color, bool scale = false): + StaticBitmap(Window * parent, const rect_t & rect, const Mask * mask, LcdFlags color, bool scale = false): Window(parent, rect), mask(mask), color(color), @@ -154,30 +148,6 @@ class StaticBitmap: public Window { } - void setBitmap(const char * filename) - { - setBitmap(BitmapBuffer::load(filename)); - } - - void setMaskColor(LcdFlags value) - { - color = value; - } - - void setBitmap(const BitmapBuffer * newBitmap) - { - delete bitmap; - bitmap = newBitmap; - invalidate(); - } - - void setMask(const BitmapMask * newMask) - { - delete mask; - mask = newMask; - invalidate(); - } - #if defined(DEBUG_WINDOWS) [[nodiscard]] std::string getName() const override { @@ -199,8 +169,8 @@ class StaticBitmap: public Window } protected: - const BitmapMask * mask = nullptr; - const BitmapBuffer * bitmap = nullptr; + const Mask * mask = nullptr; + const Bitmap * bitmap = nullptr; LcdFlags color = 0; bool scale = false; }; @@ -257,7 +227,7 @@ class DynamicNumber: public Window void paint(BitmapBuffer * dc) override { - dc->drawNumber(0, FIELD_PADDING_TOP, value, DEFAULT_COLOR, textFlags, 0, prefix, suffix); + dc->drawNumber((textFlags & RIGHT) ? width() : 0, FIELD_PADDING_TOP, value, DEFAULT_COLOR, textFlags, 0, prefix, suffix); } void checkEvents() override diff --git a/src/textedit.cpp b/src/textedit.cpp index d2d83bc..1488e37 100644 --- a/src/textedit.cpp +++ b/src/textedit.cpp @@ -48,7 +48,7 @@ void TextEdit::setEditMode(bool newEditMode) #if defined(SOFTWARE_KEYBOARD) if (editMode) { - TextKeyboard::show(this); + TextKeyboardBase::show(this); } #endif @@ -105,67 +105,60 @@ void TextEdit::trim() } } +#if defined(SOFTWARE_KEYBOARD) || defined(SIMULATION) +void TextEdit::onVirtualKeyEvent(event_t event) +{ + auto c = event & MSK_VIRTUAL_KEY; + if (c == SPECIAL_KEY_BACKSPACE) { + if (cursorPos > 0) { + auto pos = getUnicodeStringAtPosition(value, cursorPos - 1); + char * tmp = pos; + auto len = getUnicodeCharLength(getNextUnicodeChar(tmp)); + memmove(pos, pos + len, value + length - pos - len); + memset(value + length - len, 0, len); + --cursorPos; + invalidate(); + changed = true; + } + } + else { + auto len = getUnicodeCharLength(c); + if (strlen(value) + len <= length) { + insertUnicodeChar(value, cursorPos, c, length); + cursorPos++; + invalidate(); + changed = true; + } + } +} +#endif + void TextEdit::onEvent(event_t event) { TRACE_WINDOWS("%s received event 0x%X", getWindowDebugString().c_str(), event); #if defined(SOFTWARE_KEYBOARD) || defined(SIMULATION) if (IS_VIRTUAL_KEY_EVENT(event)) { - uint8_t c = event & 0xFF; - if (c == SPECIAL_KEY_BACKSPACE) { - if (cursorPos > 0) { - memmove(value + cursorPos - 1, value + cursorPos, length - cursorPos); - value[length - 1] = '\0'; - --cursorPos; - invalidate(); - changed = true; - } - } -#if defined(KEYBOARD_DELETE) - else if (c == SPECIAL_KEY_DELETE) { - if (cursorPos < length - 1) { - memmove(value + cursorPos, value + cursorPos + 1, length - cursorPos - 1); - value[length - 1] = '\0'; - invalidate(); - changed = true; - } - } -#endif -#if defined(KEYBOARD_HOME) - else if (c == SPECIAL_KEY_HOME) { - setCursorPos(0); - } -#endif -#if defined(KEYBOARD_END) - else if (c == SPECIAL_KEY_END) { - setCursorPos(strlen(value)); - } -#endif - else if (cursorPos < length) { - memmove(value + cursorPos + 1, value + cursorPos, length - cursorPos - 1); - value[cursorPos++] = c; - invalidate(); - changed = true; - } + onVirtualKeyEvent(event); + return; } #endif #if defined(HARDWARE_KEYS) if (editMode) { - char previousChar = (cursorPos > 0 ? value[cursorPos - 1] : 0); - int c = (cursorPos < length ? value[cursorPos] : 0); - int v = c; + auto currentChar = getUnicodeCharAtPosition(value, cursorPos); + auto nextChar = currentChar; switch (event) { case EVT_ROTARY_RIGHT: for (int i = 0; i < ROTARY_ENCODER_SPEED(); i++) { - v = getNextChar(v, previousChar); + nextChar = getNextAvailableChar(nextChar); } break; case EVT_ROTARY_LEFT: for (int i = 0; i < ROTARY_ENCODER_SPEED(); i++) { - v = getPreviousChar(v); + nextChar = getPreviousAvailableChar(nextChar); } break; @@ -176,18 +169,11 @@ void TextEdit::onEvent(event_t event) break; case EVT_KEY_BREAK(KEY_RIGHT): - { -#if defined(SOFTWARE_KEYBOARD) - if (cursorPos < length && value[cursorPos] != '\0') { - setCursorPos(cursorPos + 1); - } -#else - if (cursorPos < length - 1 && value[cursorPos + 1] != '\0') { - setCursorPos(cursorPos + 1); + if (cursorPos < getUnicodeStringLength(value)) { + cursorPos++; + invalidate(); } -#endif break; - } case EVT_KEY_BREAK(KEY_ENTER): if (cursorPos < length - 1) { @@ -195,11 +181,12 @@ void TextEdit::onEvent(event_t event) value[cursorPos] = ' '; changed = true; } - setCursorPos(cursorPos + 1); + cursorPos++; if (value[cursorPos] == '\0') { value[cursorPos] = ' '; changed = true; } + invalidate(); } else { changeEnd(); @@ -211,7 +198,7 @@ void TextEdit::onEvent(event_t event) changeEnd(); FormField::onEvent(event); #if defined(HARDWARE_TOUCH) - TextKeyboard::hide(); + TextKeyboardBase::hide(); #endif break; @@ -239,12 +226,12 @@ void TextEdit::onEvent(event_t event) } case EVT_KEY_BREAK(KEY_UP): - v = toggleCase(v); + nextChar = toggleCase(nextChar); break; case EVT_KEY_LONG(KEY_LEFT): case EVT_KEY_LONG(KEY_RIGHT): - v = toggleCase(v); + nextChar = toggleCase(nextChar); if (event == EVT_KEY_LONG(KEY_LEFT)) { killEvents(KEY_LEFT); } @@ -263,9 +250,9 @@ void TextEdit::onEvent(event_t event) break; } - if (cursorPos < length && c != v) { + if (cursorPos < length && currentChar != nextChar) { // TRACE("value[%d] = %d", cursorPos, v); - value[cursorPos] = v; + // write(value[cursorPos] = nextChar; invalidate(); changed = true; } @@ -289,17 +276,17 @@ bool TextEdit::onTouchEnd(coord_t x, coord_t y) } #if defined(SOFTWARE_KEYBOARD) - TextKeyboard::show(this); + TextKeyboardBase::show(this); #endif - auto font = getFont(FONT(STD)); + auto font = getFont(FONT(M)); coord_t rest = x; for (cursorPos = 0; cursorPos < length; cursorPos++) { char c = value[cursorPos]; if (c == '\0') break; - uint8_t w = font->getChar(c).width + 1; + uint8_t w = font->getGlyph(c).width + 1; if (rest < w) break; rest -= w; @@ -313,7 +300,7 @@ bool TextEdit::onTouchEnd(coord_t x, coord_t y) void TextEdit::onFocusLost() { #if defined(SOFTWARE_KEYBOARD) - TextKeyboard::hide(); + TextKeyboardBase::hide(); #endif changeEnd(); diff --git a/src/textedit.h b/src/textedit.h index b60d3f7..7c905a7 100644 --- a/src/textedit.h +++ b/src/textedit.h @@ -19,6 +19,7 @@ #pragma once +#include #include "form.h" namespace ui { @@ -98,33 +99,29 @@ class TextEdit: public FormField } } - static uint8_t getNextChar(uint8_t c, uint8_t previous) + static uint32_t getNextAvailableChar(uint32_t index) { - if (c == ' ' || c == 0) - return (previous >= 'a' && previous <= 'z') ? 'a' : 'A'; - - for (auto & suite: charsSuite) { - if (c == suite[0]) - return suite[1]; + auto end = mFont.end(); + while (++index < uint32_t(end)) { + if (mFont.getGlyph(index).width > 0) { + return index; + } } - - return c + 1; + return index; } - static uint8_t getPreviousChar(uint8_t c) + static uint32_t getPreviousAvailableChar(uint32_t index) { - if (c == 'A') - return ' '; - - for (auto & suite: charsSuite) { - if (c == suite[1]) - return suite[0]; + auto begin = mFont.begin(); + while (--index >= uint32_t(begin)) { + if (mFont.getGlyph(index).width > 0) { + return index; + } } - - return c - 1; + return index; } - static uint8_t toggleCase(uint8_t c) + static wchar_t toggleCase(wchar_t c) { if (c >= 'A' && c <= 'Z') return c + 32; // tolower @@ -133,6 +130,8 @@ class TextEdit: public FormField else return c; } + + void onVirtualKeyEvent(event_t event); }; } diff --git a/src/theme.h b/src/theme.h index 5fe6b6c..3e3073a 100644 --- a/src/theme.h +++ b/src/theme.h @@ -54,7 +54,7 @@ class Theme virtual void drawChoice(BitmapBuffer * dc, ChoiceBase * choice, const char * str) const = 0; virtual void drawSlider(BitmapBuffer * dc, int vmin, int vmax, int value, const rect_t & rect, bool edit, bool focus) const = 0; virtual const BitmapBuffer * getIcon(uint8_t index, IconState state) const = 0; - virtual const BitmapMask * getIconMask(uint8_t index) const = 0; + virtual const Mask * getIconMask(uint8_t index) const = 0; virtual TextButton * createTextButton(FormGroup * parent, const rect_t & rect, std::string text, std::function pressHandler = nullptr, WindowFlags windowFlags = OPAQUE | BUTTON_BACKGROUND) const { @@ -64,7 +64,7 @@ class Theme extern Theme * theme; -MenuWindowContent * createMenuWindow(Menu * menu, bool footer = false); +MenuWindowContent * createMenuWindow(Menu * menu, bool multiple = false, bool footer = false); DialogWindowContent * createDialogWindow(Dialog * dialog, const rect_t & rect); } diff --git a/src/unicode.h b/src/unicode.h new file mode 100644 index 0000000..d3f8d84 --- /dev/null +++ b/src/unicode.h @@ -0,0 +1,150 @@ +/* + * Copyright (C) OpenTX + * + * Source: + * https://github.com/opentx/libopenui + * + * This file is a part of libopenui library. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +#pragma once + +#include + +inline bool isUnicodeContinuationByte(uint8_t c) +{ + return (c & 0b11000000) == 0b10000000; +} + +template +inline uint32_t getNextUnicodeChar(T & s) +{ + uint8_t c1 = uint8_t(*s++); + if (c1 < 0x80) { + return c1; + } + else if (c1 < 0xE0) { + uint8_t c2 = uint8_t(*s++); + return ((c1 & 0b11111) << 6) + (c2 & 0b111111); + } + else if (c1 < 0xF0) { + uint8_t c2 = uint8_t(*s++); + uint8_t c3 = uint8_t(*s++); + return ((c1 & 0b1111) << 12) + ((c2 & 0b111111) << 6) + (c3 & 0b111111); + } + else { + uint8_t c2 = uint8_t(*s++); + uint8_t c3 = uint8_t(*s++); + uint8_t c4 = uint8_t(*s++); + return ((c1 & 0b111) << 18) + ((c2 & 0b111111) << 12) + ((c3 & 0b111111) << 6) + (c4 & 0b111111); + } +} + +template +inline uint32_t getPreviousUnicodeChar(T * & s) +{ + while (isUnicodeContinuationByte(*(--s))); + auto tmp = s; + return getNextUnicodeChar(tmp); +} + +inline uint32_t getUnicodeStringLength(const char * s, uint8_t size = 0) +{ + const char * pos = s; + uint32_t result = 0; + while (true) { + uint8_t c = *pos; + if (c == 0) + return result; + else if (c < 0x80) + pos += 1; + else if (c < 0xE0) + pos += 2; + else if (c < 0xF0) + pos += 3; + else + pos += 4; + if (size && pos >= s + size) + return result; + result++; + } + return result; +} + +template +inline T * getUnicodeStringAtPosition(T * s, uint8_t index) +{ + while (index-- > 0) { + uint8_t c = *s; + if (c == 0) + return nullptr; + else if (c < 0x80) + s += 1; + else if (c < 0xE0) + s += 2; + else if (c < 0xF0) + s += 3; + else + s += 4; + } + return s; +} + +template +inline uint32_t getUnicodeCharAtPosition(T * s, uint8_t index) +{ + auto pos = getUnicodeStringAtPosition(s, index); + return getNextUnicodeChar(pos); +} + + +inline uint8_t getUnicodeCharLength(uint32_t c) +{ + if (c < 0x80) + return 1; + else if (c <= 0b11111111111) + return 2; + else if (c <= 0b1111111111111111) + return 3; + else + return 4; +} + +inline void writeUnicodeChar(char * s, uint32_t c) +{ + if (c < 0x80) { + *s = c; + } + else if (c <= 0b11111111111) { + *s++ = 0xC0 | ((c >> 6) & 0b11111); + *s = 0b10000000 | (c & 0b111111); + } + else if (c <= 0b1111111111111111) { + *s++ = 0xE0 | ((c >> 12) & 0b1111); + *s++ = 0b10000000 | ((c >> 6) & 0b111111); + *s = 0b10000000 | (c & 0b111111); + } + else { + *s++ = 0xF0 | ((c >> 18) & 0b111); + *s++ = 0b10000000 | ((c >> 12) & 0b111111); + *s++ = 0b10000000 | ((c >> 6) & 0b111111); + *s = 0b10000000 | (c & 0b111111); + } +} + +inline void insertUnicodeChar(char * s, uint8_t position, uint32_t c, uint8_t maxLength) +{ + auto len = getUnicodeCharLength(c); + auto pos = getUnicodeStringAtPosition(s, position); + memmove(pos + len, pos, s + maxLength - pos - len); +} diff --git a/src/window.cpp b/src/window.cpp index 3965a27..345a61a 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -544,7 +544,7 @@ coord_t Window::adjustHeight() void Window::moveWindowsTop(coord_t y, coord_t delta) // NOLINT(misc-no-recursion) { if (delta != 0) { - if (getWindowFlags() & FORWARD_SCROLL) { + if (parent && (getWindowFlags() & FORWARD_SCROLL)) { parent->moveWindowsTop(bottom(), delta); } diff --git a/src/window.h b/src/window.h index 0b1ba0d..398dbbc 100644 --- a/src/window.h +++ b/src/window.h @@ -151,6 +151,11 @@ class Window textFlags = flags; } + void clearCloseHandler() + { + closeHandler = nullptr; + } + void setCloseHandler(std::function handler) { closeHandler = std::move(handler); diff --git a/tools/encode-bitmap.py b/tools/encode_bitmap.py similarity index 57% rename from tools/encode-bitmap.py rename to tools/encode_bitmap.py index 7c8751c..28d4cce 100755 --- a/tools/encode-bitmap.py +++ b/tools/encode_bitmap.py @@ -2,11 +2,12 @@ import argparse from PIL import Image +import sys class RawMixin: def encode_byte(self, byte): - self.write(byte) + self.append_byte(byte) def encode_end(self): pass @@ -28,7 +29,7 @@ def eq_prev_byte(self, byte): def encode_byte(self, byte): if self.state == self.RLE_BYTE: - self.write(byte) + self.append_byte(byte) if self.eq_prev_byte(byte): self.state = self.RLE_SEQ self.count = 0 @@ -38,39 +39,64 @@ def encode_byte(self, byte): if self.eq_prev_byte(byte): self.count += 1 if self.count == 255: - self.write(self.count) + self.append_byte(self.count) self.prev_byte = None self.state = self.RLE_BYTE else: - self.write(self.count) - self.write(byte) + self.append_byte(self.count) + self.append_byte(byte) self.prev_byte = byte self.state = self.RLE_BYTE def encode_end(self): if self.state == self.RLE_SEQ: - self.write(self.count) + self.append_byte(self.count) class ImageEncoder: - def __init__(self, filename, size_format, orientation=0): - self.f = open(filename, "w") + def __init__(self, size_format, orientation=0): self.size_format = size_format self.orientation = orientation + self.bytes = [] - def write(self, value): - self.f.write("0x%02x," % value) + def append_byte(self, value): + self.bytes.append(value) + + def extend(self, values): + self.bytes.extend(values) - def write_size(self, width, height): + def append_short(self, value): + self.append_byte(value % 256) + self.append_byte(value // 256) + + def append_word(self, value): + self.append_short(value % 65536) + self.append_short(value // 65536) + + def append_format(self, value): + self.append_word(value) + + def append_size(self, width, height): if self.size_format == 2: - self.f.write("%d,%d,%d,%d,\n" % (width % 256, width // 256, height % 256, height // 256)) + self.append_short(width) + self.append_short(height) else: - self.f.write("%d,%d,\n" % (width, height)) + self.extend(width, height) + + @staticmethod + def guess_format(image): + if image.mode == "P": + print("Indexed bitmap, will use RGBA") + return "4/4/4/4" + elif image.mode == "RGBA": + return "4/4/4/4" + else: + return "5/6/5" def encode_1bit(self, image, rows): image = image.convert(mode='1') width, height = image.size - self.write_size(width, height // rows) + self.append_size(width, height // rows) for y in range(0, height, 8): for x in range(width): value = 0 @@ -83,13 +109,13 @@ def encode_1bit(self, image, rows): if image.getpixel((x, y + z)) == 0: value += 1 << z self.encode_byte(value) - self.f.write("\n") self.encode_end() + return self.bytes def encode_4bits(self, image): image = image.convert(mode='L') width, height = image.size - self.write_size(width, height) + self.append_size(width, height) for y in range(0, height, 2): for x in range(width): value = 0xFF @@ -104,62 +130,74 @@ def encode_4bits(self, image): if gray2 & (1 << (4 + i)): value -= 1 << (4 + i) self.encode_byte(value) - self.f.write("\n") self.encode_end() + return self.bytes def encode_8bits(self, image): image = image.convert(mode='L') width, height = image.size - self.write_size(width, height) - if self.orientation == 270: + self.append_size(width, height) + if self.orientation == 180: + image = image.transpose(Image.ROTATE_180) + elif self.orientation == 270: + image = image.transpose(Image.ROTATE_270) + image = image.transpose(Image.FLIP_LEFT_RIGHT) + width, height = image.size + for y in range(height): for x in range(width): - for y in range(height): - value = 0xFF - self.get_pixel(image, x, y) - self.encode_byte(value) - self.f.write("\n") - else: - for y in range(height): - for x in range(width): - value = 0xFF - self.get_pixel(image, x, y) - self.encode_byte(value) - self.f.write("\n") + value = 0xFF - image.getpixel((x, y)) + self.encode_byte(value) self.encode_end() + return self.bytes def encode_5_6_5(self, image): width, height = image.size - self.write_size(width, height) + self.append_format(0) + self.append_size(width, height) + if self.orientation == 180: + image = image.transpose(Image.ROTATE_180) + elif self.orientation == 270: + image = image.transpose(Image.ROTATE_270) + image = image.transpose(Image.FLIP_LEFT_RIGHT) + width, height = image.size for y in range(height): for x in range(width): - pixel = self.get_pixel(image, x, y) + pixel = image.getpixel((x, y)) val = ((pixel[0] >> 3) << 11) + ((pixel[1] >> 2) << 5) + ((pixel[2] >> 3) << 0) self.encode_byte(val & 255) self.encode_byte(val >> 8) self.encode_end() + return self.bytes def encode_4_4_4_4(self, image): width, height = image.size - self.write_size(width, height) + self.append_format(1) + self.append_size(width, height) + if self.orientation == 180: + image = image.transpose(Image.ROTATE_180) + elif self.orientation == 270: + image = image.transpose(Image.ROTATE_270) + image = image.transpose(Image.FLIP_LEFT_RIGHT) + width, height = image.size for y in range(height): for x in range(width): - pixel = self.get_pixel(image, x, y) + pixel = image.getpixel((x, y)) val = ((pixel[3] // 16) << 12) + ((pixel[0] // 16) << 8) + ((pixel[1] // 16) << 4) + ((pixel[2] // 16) << 0) self.encode_byte(val & 255) self.encode_byte(val >> 8) self.encode_end() + return self.bytes def get_pixel(self, image, x, y): - if self.orientation == 180: - return image.getpixel((image.width - x - 1, image.height - y - 1)) - else: - return image.getpixel((x, y)) + return image.getpixel((x, y)) @staticmethod - def create(filename, size_format=1, orientation=0, encode_mixin=RawMixin): + def create(size_format=1, orientation=0, encode_mixin=RawMixin): class ResultClass(ImageEncoder, encode_mixin): def __init__(self, *args, **kwargs): ImageEncoder.__init__(self, *args, **kwargs) encode_mixin.__init__(self) - return ResultClass(filename, size_format, orientation) + return ResultClass(size_format, orientation) def main(): @@ -175,19 +213,25 @@ def main(): args = parser.parse_args() image = Image.open(args.input) - output = args.output - encoder = ImageEncoder.create(output, args.size_format, args.orientation, RleMixin if args.rle else RawMixin) - - if args.format == "1bit": - encoder.encode_1bit(image, args.rows) - elif args.format == "4bits": - encoder.encode_4bits(image) - elif args.format == "8bits": - encoder.encode_8bits(image) - elif args.format == "4/4/4/4": - encoder.encode_4_4_4_4(image) - elif args.format == "5/6/5": - encoder.encode_5_6_5(image) + encoder = ImageEncoder.create(args.size_format, args.orientation, RleMixin if args.rle else RawMixin) + + format = args.format + if format == "auto": + format = encoder.guess_format(image) + if format == "1bit": + bytes = encoder.encode_1bit(image, args.rows) + elif format == "4bits": + bytes = encoder.encode_4bits(image) + elif format == "8bits": + bytes = encoder.encode_8bits(image) + elif format == "4/4/4/4": + bytes = encoder.encode_4_4_4_4(image) + elif format == "5/6/5": + bytes = encoder.encode_5_6_5(image) + + with open(args.output, "w") as f: + for byte in bytes: + f.write("%d," % byte) if __name__ == "__main__":