Chris 2 месяцев назад
Родитель
Сommit
22977c885b

+ 6 - 6
Marlin/Configuration_adv.h

@@ -2035,17 +2035,17 @@
   //#define STATUS_HEAT_PERCENT       // Show heating in a progress bar
   //#define STATUS_HEAT_POWER         // Show heater output power as a vertical bar
 
-  // Frivolous Game Options
-  //#define MARLIN_BRICKOUT
-  //#define MARLIN_INVADERS
-  //#define MARLIN_SNAKE
-  //#define GAMES_EASTER_EGG          // Add extra blank lines above the "Games" sub-menu
-
 #endif // HAS_MARLINUI_U8GLIB
 
 #if HAS_MARLINUI_U8GLIB || IS_DWIN_MARLINUI
   #define MENU_HOLLOW_FRAME           // Enable to save many cycles by drawing a hollow frame on Menu Screens
   //#define OVERLAY_GFX_REVERSE       // Swap the CW/CCW indicators in the graphics overlay
+
+  // Frivolous Game Options
+  //#define MARLIN_BRICKOUT
+  //#define MARLIN_INVADERS
+  //#define MARLIN_SNAKE
+  //#define GAMES_EASTER_EGG          // Add extra blank lines above the "Games" sub-menu
 #endif
 
 //

+ 80 - 0
Marlin/src/lcd/dogm/game.cpp

@@ -0,0 +1,80 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2025 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#include "../../inc/MarlinConfigPre.h"
+
+#if HAS_MARLINUI_U8GLIB && HAS_GAMES
+
+#include "../menu/game/types.h" // includes dogm/game.h
+
+void MarlinGame::frame_start() {
+  set_color(color::WHITE);
+}
+
+void MarlinGame::frame_end() {}
+
+void MarlinGame::set_color(const color color) {
+  switch (color) {
+    default:
+    case color::WHITE: u8g.setColorIndex(1); break;
+    case color::BLACK: u8g.setColorIndex(0); break;
+  }
+}
+
+void MarlinGame::draw_hline(const game_dim_t x, const game_dim_t y, const game_dim_t w) {
+  u8g.drawHLine(x, y, w);
+}
+
+void MarlinGame::draw_vline(const game_dim_t x, const game_dim_t y, const game_dim_t h) {
+  u8g.drawVLine(x, y, h);
+}
+
+void MarlinGame::draw_frame(const game_dim_t x, const game_dim_t y, const game_dim_t w, const game_dim_t h) {
+  u8g.drawFrame(x, y, w, h);
+}
+
+void MarlinGame::draw_box(const game_dim_t x, const game_dim_t y, const game_dim_t w, const game_dim_t h) {
+  u8g.drawBox(x, y, w, h);
+}
+
+void MarlinGame::draw_pixel(const game_dim_t x, const game_dim_t y) {
+  u8g.drawPixel(x, y);
+}
+
+void MarlinGame::draw_bitmap(const game_dim_t x, const game_dim_t y, const game_dim_t bytes_per_row, const game_dim_t rows, const pgm_bitmap_t bitmap) {
+  u8g.drawBitmapP(x, y, bytes_per_row, rows, bitmap);
+}
+
+int MarlinGame::draw_string(const game_dim_t x, const game_dim_t y, const char* str) {
+  lcd_moveto(x, y);
+  return lcd_put_u8str_P(str);
+}
+
+int MarlinGame::draw_string(const game_dim_t x, const game_dim_t y, FSTR_P const fstr) {
+  lcd_moveto(x, y);
+  return lcd_put_u8str(fstr);
+}
+
+void MarlinGame::draw_int(const game_dim_t x, const game_dim_t y, const int value) {
+  lcd_put_int(x, y, value);
+}
+
+#endif // HAS_MARLINUI_U8GLIB && HAS_GAMES

+ 33 - 0
Marlin/src/lcd/dogm/game.h

@@ -0,0 +1,33 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2025 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include "marlinui_DOGM.h"
+#include "../lcdprint.h"
+
+typedef uint8_t game_dim_t;
+typedef const u8g_pgm_uint8_t* pgm_bitmap_t;
+
+constexpr game_dim_t GAME_WIDTH = LCD_PIXEL_WIDTH;
+constexpr game_dim_t GAME_HEIGHT = LCD_PIXEL_HEIGHT;
+constexpr game_dim_t GAME_FONT_WIDTH = MENU_FONT_WIDTH;
+constexpr game_dim_t GAME_FONT_ASCENT = MENU_FONT_ASCENT;

+ 62 - 1
Marlin/src/lcd/e3v2/common/dwin_api.cpp

@@ -158,6 +158,7 @@ void dwinFrameClear(const uint16_t color) {
 }
 
 #if DISABLED(TJC_DISPLAY)
+
   // Draw a point
   //  color: point color
   //  width: point width   0x01-0x0F
@@ -173,7 +174,67 @@ void dwinFrameClear(const uint16_t color) {
     dwinWord(i, y);
     dwinSend(i);
   }
-#endif
+
+  // Draw a map of multiple points using minimal amount of point drawing commands
+  //  color: point color
+  //  point_width: point width   0x01-0x0F
+  //  point_height: point height 0x01-0x0F
+  //  x,y: upper left point
+  //  map_columns: columns in theh point map. each column is a byte in the map and contains 8 points
+  //  map_rows: rows in the point map
+  //  map: point bitmap. 2D array of points, 1 bit per point
+  // Note: somewhat similar to U8G's drawBitmap() function, see https://github.com/olikraus/u8glib/wiki/userreference#drawbitmap
+  void dwinDrawPointMap(
+    const uint16_t color,
+    const uint8_t point_width, const uint8_t point_height,
+    const uint16_t x, const uint16_t y,
+    const uint16_t map_columns, const uint16_t map_rows,
+    const uint8_t *map_data
+  ) {
+    // At how many bytes should we flush the send buffer?
+    // One byte is used (hidden) for F_HONE, and we need 4 bytes when appending a point.
+    // So we should flush the send buffer when we have less than 5 bytes left.
+    constexpr size_t flush_send_buffer_at = (COUNT(dwinSendBuf) - 1 - 4);
+
+    // How long is the header of each draw command?
+    // => 1B CMD, 2B COLOR, 1B WIDTH, 1B HEIGHT
+    constexpr size_t command_header_size = 5;
+
+    size_t i = 0;
+    for (uint16_t row = 0; row < map_rows; row++) {
+      for (uint16_t col = 0; col < map_columns; col++) {
+        const uint8_t map_byte = map_data[(row * map_columns) + col];
+        for (uint8_t bit = 0; bit < 8; bit++) {
+          // Draw a point at this position?
+          if (TEST(map_byte, bit)) {
+            // Flush the send buffer and prepare next draw if either
+            // a) The buffer reached the 'should flush' state, or
+            // b) This is the first point to draw
+            if (i >= flush_send_buffer_at || i == 0) {
+              // Dispatch the current draw command
+              if (i > command_header_size) dwinSend(i);
+
+              // Prepare the next draw command
+              i = 0;
+              dwinByte(i, 0x02); // cmd: draw point(s)
+              dwinWord(i, color);
+              dwinByte(i, point_width);
+              dwinByte(i, point_height);
+            }
+
+            // Append point coordinates to draw command
+            dwinWord(i, x + (point_width * ((8 * col) + (7 - bit)))); // x
+            dwinWord(i, y + (point_height * (row)));                  // y
+          }
+        }
+      }
+    }
+
+    // Dispatch final draw command if the buffer contains any points
+    if (i > command_header_size) dwinSend(i);
+  }
+
+#endif // !TJC_DISPLAY
 
 // Draw a line
 //  color: Line segment color

+ 18 - 0
Marlin/src/lcd/e3v2/common/dwin_api.h

@@ -164,6 +164,24 @@ inline void dwinDrawBox(uint8_t mode, uint16_t color, uint16_t xStart, uint16_t
   void dwinDrawPoint(uint16_t color, uint8_t width, uint8_t height, uint16_t x, uint16_t y);
 #endif
 
+// Draw a map of multiple points using minimal amount of point drawing commands
+//  color: point color
+//  point_width: point width   0x01-0x0F
+//  point_height: point height 0x01-0x0F
+//  x,y: upper left point
+//  map_columns: columns in theh point map. each column is a byte in the map and contains 8 points
+//  map_rows: rows in the point map
+//  map: point bitmap. 2D array of points, 1 bit per point
+#if DISABLED(TJC_DISPLAY)
+  void dwinDrawPointMap(
+    const uint16_t color,
+    const uint8_t point_width, const uint8_t point_height,
+    const uint16_t x, const uint16_t y,
+    const uint16_t map_columns, const uint16_t map_rows,
+    const uint8_t *map_data
+  );
+#endif
+
 // Move a screen area
 //  mode: 0, circle shift; 1, translation
 //  dir: 0=left, 1=right, 2=up, 3=down

+ 254 - 0
Marlin/src/lcd/e3v2/marlinui/game.cpp

@@ -0,0 +1,254 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2025 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#include "../../../inc/MarlinConfigPre.h"
+
+#if IS_DWIN_MARLINUI && HAS_GAMES
+
+// Enable performance counters (draw call count, frame timing) for debugging
+//#define GAME_PERFORMANCE_COUNTERS
+
+#include "../../menu/game/types.h" // includes e3v2/marlinui/game.h
+#include "../../lcdprint.h"
+#include "lcdprint_dwin.h"
+#include "marlinui_dwin.h"
+
+#if ENABLED(GAME_PERFORMANCE_COUNTERS)
+
+  typedef struct {
+    /**
+     * Number of draw calls sent to the LCD
+     */
+    uint32_t draw_calls;
+
+    /**
+     * millis() value at the start of the current frame
+     */
+    millis_t frame_draw_millis;
+
+    /**
+     * millis() value at the end of the previous frame (in frame_start)
+     * or time spend waiting for the next frame (in frame_end)
+     */
+    millis_t frame_wait_millis;
+  } dwin_game_perf_t;
+
+  static dwin_game_perf_t dwin_game_perf;
+
+  #define COUNT_DRAW_CALLS(n) dwin_game_perf.draw_calls += n
+
+#else // !GAME_PERFORMANCE_COUNTERS
+
+  #define COUNT_DRAW_CALLS(...) NOOP
+
+#endif // !GAME_PERFORMANCE_COUNTERS
+
+void MarlinGame::frame_start() {
+  // Clear the screen before each frame
+  //dwinFrameClear(CLEAR_COLOR);
+
+  // Instead of using dwinFrameClear, fill the play area with the background color
+  // This tends to be faster than clearing the whole screen
+  const uint16_t fg = dwin_font.fg;
+  dwin_font.fg = COLOR_BG_BLACK;
+  draw_box(0, 0, GAME_WIDTH, GAME_HEIGHT);
+  dwin_font.fg = fg;
+
+  // Ensure the correct font is selected
+  dwin_font.index = DWIN_FONT_MENU;
+
+  #if ENABLED(GAME_PERFORMANCE_COUNTERS)
+    // Reset draw call counters
+    dwin_game_perf.draw_calls = 0;
+
+    // Update timing information
+    const millis_t now = millis();
+    dwin_game_perf.frame_draw_millis = now;
+    dwin_game_perf.frame_wait_millis = now - dwin_game_perf.frame_wait_millis;
+  #endif
+}
+
+void MarlinGame::frame_end() {
+  #if ENABLED(GAME_PERFORMANCE_COUNTERS)
+    const millis_t now = millis();
+    const millis_t frame_wait_millis = dwin_game_perf.frame_wait_millis;
+    const millis_t frame_draw_millis = now - dwin_game_perf.frame_draw_millis;
+
+    dwin_game_perf.frame_wait_millis = now;
+
+    // Save previous font settings and set new ones
+    const uint16_t fg = dwin_font.fg;
+    const bool solid = dwin_font.solid;
+    set_color(color::YELLOW);
+    dwin_font.solid = true;
+
+    // Draw performance counters information
+    char perf_str[32];
+    sprintf_P(
+      perf_str,
+      PSTR("d%04lu w%04lu c%04lu"),
+      frame_draw_millis,
+      frame_wait_millis,
+      dwin_game_perf.draw_calls
+    );
+    lcd_moveto_xy(0, 0);
+    lcd_put_u8str(perf_str);
+
+    // Restore previous font settings
+    dwin_font.fg = fg;
+    dwin_font.solid = solid;
+  #endif // GAME_PERFORMANCE_COUNTERS
+}
+
+void MarlinGame::set_color(const color color) {
+  switch (color) {
+    default:
+    case color::WHITE:  dwin_font.fg = COLOR_WHITE; break;
+    case color::BLACK:  dwin_font.fg = COLOR_BG_BLACK; break;
+
+    // https://rgbcolorpicker.com/565/table
+    case color::RED:    dwin_font.fg = RGB(0x1F, 0x00, 0x00); break;
+    case color::GREEN:  dwin_font.fg = RGB(0x00, 0x3F, 0x00); break;
+    case color::BLUE:   dwin_font.fg = RGB(0x00, 0x00, 0x1F); break;
+    case color::YELLOW: dwin_font.fg = RGB(0x1F, 0x3F, 0x00); break;
+    case color::CYAN:   dwin_font.fg = RGB(0x00, 0x3F, 0x1F); break;
+    case color::MAGENTA:dwin_font.fg = RGB(0x1F, 0x00, 0x1F); break;
+  }
+}
+
+void MarlinGame::draw_hline(const game_dim_t x, const game_dim_t y, const game_dim_t w) {
+  // Draw lines as boxes, since DWIN lines are always 1px wide but we want to scale them
+  draw_box(x, y, w, 1);
+
+  COUNT_DRAW_CALLS(1);
+}
+
+void MarlinGame::draw_vline(const game_dim_t x, const game_dim_t y, const game_dim_t h) {
+  // Draw lines as boxes, since DWIN lines are always 1px wide but we want to scale them
+  draw_box(x, y, 1, h);
+
+  COUNT_DRAW_CALLS(1);
+}
+
+void MarlinGame::draw_frame(const game_dim_t x, const game_dim_t y, const game_dim_t w, const game_dim_t h) {
+  dwinDrawBox(
+    0, // mode = frame
+    dwin_font.fg, // color
+    dwin_game::game_to_screen(x) + dwin_game::x_offset,
+    dwin_game::game_to_screen(y) + dwin_game::y_offset,
+    dwin_game::game_to_screen(w),
+    dwin_game::game_to_screen(h)
+  );
+
+  COUNT_DRAW_CALLS(1);
+}
+
+void MarlinGame::draw_box(const game_dim_t x, const game_dim_t y, const game_dim_t w, const game_dim_t h) {
+  dwinDrawBox(
+    1, // mode = fill
+    dwin_font.fg, // color
+    dwin_game::game_to_screen(x) + dwin_game::x_offset,
+    dwin_game::game_to_screen(y) + dwin_game::y_offset,
+    dwin_game::game_to_screen(w),
+    dwin_game::game_to_screen(h)
+  );
+
+  COUNT_DRAW_CALLS(1);
+}
+
+void MarlinGame::draw_pixel(const game_dim_t x, const game_dim_t y) {
+  // Draw pixels using boxes.
+  // While DWIN protocol supports drawing points with different sizes, the
+  // 0x02 'draw point' command is slower per pixel than 0x05 'fill rectangle'
+  // (0.4 us vs 0.14 us per pixel)
+  draw_box(x, y, 1, 1);
+}
+
+void MarlinGame::draw_bitmap(const game_dim_t x, const game_dim_t y, const game_dim_t bytes_per_row, const game_dim_t rows, const pgm_bitmap_t bitmap) {
+  // DWIN theorethically supports bitmaps since kernel 2.1, but most screens don't support it
+  // (either because they use an older kernel version, or because they just (badly) emulate the DWIN protocol).
+  // So instead, we have to fall back to drawing points manually.
+
+  #if DISABLED(TJC_DISPLAY)
+
+    // DWIN T5UI actually supports drawing multiple points in one go using the 0x02 'draw point' command, ever since kernel 1.2.
+    // So we use that to draw the bitmap as a series of points, which is faster than drawing rectangles using draw_pixel.
+    dwinDrawPointMap(
+      dwin_font.fg,
+      dwin_game::game_to_screen(1),
+      dwin_game::game_to_screen(1),
+      dwin_game::game_to_screen(x) + dwin_game::x_offset,
+      dwin_game::game_to_screen(y) + dwin_game::y_offset,
+      bytes_per_row,
+      rows,
+      bitmap
+    );
+
+    COUNT_DRAW_CALLS(1);
+
+  #else // TJC_DISPLAY
+
+    // TJC displays don't seem to support the 0x02 'draw point' command, so instead we have to draw the bitmap
+    // as a series of rectangles using draw_pixel.
+    // This will absolutely suck for performance, but it's the best we can do on these screens.
+    for (game_dim_t row = 0; row < rows; row++) {
+      for (game_dim_t col = 0; col < bytes_per_row; col++) {
+        const uint8_t byte = bitmap[(row * bytes_per_row) + col];
+        for (uint8_t bit = 0; bit < 8; bit++) {
+          // Assuming that the drawing area was cleared before drawing
+          if (byte & (1 << bit)) {
+            draw_pixel(x + (col * 8) + (7 - bit + 1), y + row);
+            COUNT_DRAW_CALLS(1);
+          }
+        }
+      }
+    }
+
+  #endif // TJC_DISPLAY
+}
+
+int MarlinGame::draw_string(const game_dim_t x, const game_dim_t y, const char* str) {
+  COUNT_DRAW_CALLS(1);
+
+  lcd_moveto_xy(
+    dwin_game::game_to_screen(x) + dwin_game::x_offset,
+    dwin_game::game_to_screen(y) + dwin_game::y_offset
+  );
+
+  return lcd_put_u8str_max_P(str, PIXEL_LEN_NOLIMIT);
+}
+
+int MarlinGame::draw_string(const game_dim_t x, const game_dim_t y, FSTR_P const str) {
+  return draw_string(x, y, FTOP(str));
+}
+
+void MarlinGame::draw_int(const game_dim_t x, const game_dim_t y, const int value) {
+  COUNT_DRAW_CALLS(1);
+
+  lcd_moveto_xy(
+    dwin_game::game_to_screen(x) + dwin_game::x_offset,
+    dwin_game::game_to_screen(y) + dwin_game::y_offset
+  );
+
+  lcd_put_int(value);
+}
+
+#endif // IS_DWIN_MARLINUI && HAS_GAMES

+ 85 - 0
Marlin/src/lcd/e3v2/marlinui/game.h

@@ -0,0 +1,85 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2025 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include "../marlinui/marlinui_dwin.h"
+
+typedef uint8_t game_dim_t;
+typedef uint16_t screen_dim_t;
+typedef const uint8_t* pgm_bitmap_t;
+
+namespace dwin_game {
+  /**
+   * @brief Target the renderer at 128x64 pixels to match U8G screens
+   */
+  constexpr screen_dim_t TARGET_WIDTH = 128;
+  constexpr screen_dim_t TARGET_HEIGHT = 64;
+
+  constexpr int calculate_scale() {
+    // Use whichever is smaller: the width or height scaling factor
+    float scaling_factor = _MIN(
+      static_cast<float>(DWIN_WIDTH) / static_cast<float>(TARGET_WIDTH),
+      static_cast<float>(DWIN_HEIGHT) / static_cast<float>(TARGET_HEIGHT)
+    );
+
+    // Round DOWN to closest integer
+    return static_cast<int>(scaling_factor);
+  }
+
+  /**
+   * @brief Game render scale.
+   */
+  constexpr int scale = calculate_scale();
+
+  /**
+   * @brief Scale a game dimension to screen dimensions
+   */
+  constexpr game_dim_t screen_to_game(const screen_dim_t x) {
+    return x / scale;
+  }
+
+  /**
+   * @brief Scale a screen dimension to game dimensions
+   */
+  constexpr screen_dim_t game_to_screen(const game_dim_t x) {
+    return x * scale;
+  }
+
+  /**
+   * @brief Offset of the game window on the screen. Applied after scaling.
+   */
+  constexpr screen_dim_t x_offset = (DWIN_WIDTH - game_to_screen(TARGET_WIDTH)) / 2;
+  constexpr screen_dim_t y_offset = (DWIN_HEIGHT - game_to_screen(TARGET_HEIGHT)) / 2;
+
+  static_assert(game_to_screen(TARGET_WIDTH) + (x_offset * 2) <= DWIN_WIDTH, "DWIN game renderer failed to auto-scale, is too wide");
+  static_assert(game_to_screen(TARGET_HEIGHT) + (y_offset * 2) <= DWIN_HEIGHT, "DWIN game renderer failed to auto-scale, is too high");
+} // namespace dwin_game
+
+constexpr game_dim_t GAME_WIDTH = dwin_game::screen_to_game(DWIN_WIDTH - (dwin_game::x_offset * 2));
+constexpr game_dim_t GAME_HEIGHT = dwin_game::screen_to_game(DWIN_HEIGHT - (dwin_game::y_offset * 2));
+constexpr game_dim_t GAME_FONT_WIDTH = dwin_game::screen_to_game(MENU_FONT_WIDTH);
+constexpr game_dim_t GAME_FONT_ASCENT = dwin_game::screen_to_game(MENU_FONT_ASCENT);
+
+// DWIN screens don't page, so these macros are always true
+#define PAGE_OVER(ya) true
+#define PAGE_UNDER(yb) true
+#define PAGE_CONTAINS(ya, yb) true

+ 3 - 1
Marlin/src/lcd/e3v2/marlinui/lcdprint_dwin.cpp

@@ -52,7 +52,9 @@ void lcd_moveto(const lcd_uint_t col, const lcd_uint_t row) {
 inline void lcd_advance_cursor(const uint8_t len=1) { cursor.x += len * dwin_font.width; }
 
 void lcd_put_int(const int i) {
-  // TODO: Draw an int at the cursor position, advance the cursor
+  char buf[12]; // 10 digits + sign + null
+  itoa(i, buf, 10);
+  lcd_put_u8str_max(buf, PIXEL_LEN_NOLIMIT);
 }
 
 int lcd_put_dwin_string() {

+ 43 - 23
Marlin/src/lcd/menu/game/brickout.cpp

@@ -27,14 +27,14 @@
 #include "game.h"
 
 #define BRICK_H      5
-#define BRICK_TOP    MENU_FONT_ASCENT
+#define BRICK_TOP    GAME_FONT_ASCENT
 
 #define PADDLE_H     2
 #define PADDLE_VEL   3
-#define PADDLE_W    ((LCD_PIXEL_WIDTH) / 8)
-#define PADDLE_Y    (LCD_PIXEL_HEIGHT - 1 - PADDLE_H)
+#define PADDLE_W    ((GAME_WIDTH) / 8)
+#define PADDLE_Y    (GAME_HEIGHT - 1 - PADDLE_H)
 
-#define BRICK_W     ((LCD_PIXEL_WIDTH) / (BRICK_COLS))
+#define BRICK_W     ((GAME_WIDTH) / (BRICK_COLS))
 #define BRICK_BOT   (BRICK_TOP + BRICK_H * BRICK_ROWS - 1)
 
 #define BRICK_COL(X) ((X) / (BRICK_W))
@@ -53,7 +53,7 @@ void reset_ball() {
   bdat.ballv = FTOF(1.3f);
   bdat.ballh = -FTOF(1.25f);
   uint8_t bx = bdat.paddle_x + (PADDLE_W) / 2 + ball_dist;
-  if (bx >= LCD_PIXEL_WIDTH - 10) { bx -= ball_dist * 2; bdat.ballh = -bdat.ballh; }
+  if (bx >= GAME_WIDTH - 10) { bx -= ball_dist * 2; bdat.ballh = -bdat.ballh; }
   bdat.ballx = BTOF(bx);
   bdat.hit_dir = -1;
 }
@@ -61,7 +61,7 @@ void reset_ball() {
 void BrickoutGame::game_screen() {
   if (game_frame()) {     // Run logic twice for finer resolution
     // Update Paddle Position
-    bdat.paddle_x = constrain(int8_t(ui.encoderPosition), 0, (LCD_PIXEL_WIDTH - (PADDLE_W)) / (PADDLE_VEL));
+    bdat.paddle_x = constrain(int8_t(ui.encoderPosition), 0, (GAME_WIDTH - (PADDLE_W)) / (PADDLE_VEL));
     ui.encoderPosition = bdat.paddle_x;
     bdat.paddle_x *= (PADDLE_VEL);
 
@@ -70,7 +70,7 @@ void BrickoutGame::game_screen() {
 
       // Provisionally update the ball position
       const fixed_t newx = bdat.ballx + bdat.ballh, newy = bdat.bally + bdat.ballv;  // current next position
-      if (!WITHIN(newx, 0, BTOF(LCD_PIXEL_WIDTH - 1))) {    // out in x?
+      if (!WITHIN(newx, 0, BTOF(GAME_WIDTH - 1))) {    // out in x?
         bdat.ballh = -bdat.ballh; _BUZZ(5, 220);            // bounce x
       }
       if (newy < 0) {                                       // out in y?
@@ -78,7 +78,7 @@ void BrickoutGame::game_screen() {
         bdat.hit_dir = 1;
       }
       // Did the ball go below the bottom?
-      else if (newy > BTOF(LCD_PIXEL_HEIGHT)) {
+      else if (newy > BTOF(GAME_HEIGHT)) {
         _BUZZ(500, 75);
         if (--bdat.balls_left) reset_ball(); else game_state = 0;
         break; // done
@@ -134,32 +134,51 @@ void BrickoutGame::game_screen() {
     } while (false);
   }
 
-  u8g.setColorIndex(1);
+  frame_start();
 
-  // Draw bricks
+  // Draw bricks, cycling through colors for each brick
+  #if IS_DWIN_MARLINUI
+    const color brick_colors[] = { color::RED, color::CYAN, color::GREEN, color::YELLOW, color::MAGENTA, color::BLUE };
+    int color_index = 0;
+  #endif
   if (PAGE_CONTAINS(BRICK_TOP, BRICK_BOT)) {
     for (uint8_t y = 0; y < BRICK_ROWS; ++y) {
       const uint8_t yy = y * BRICK_H + BRICK_TOP;
       if (PAGE_CONTAINS(yy, yy + BRICK_H - 1)) {
         for (uint8_t x = 0; x < BRICK_COLS; ++x) {
+          #if IS_DWIN_MARLINUI
+            // Cycle through colors, even if the brick is gone.
+            // Otherwise, bricks would change color if their neighbor is hit
+            set_color(brick_colors[color_index++ % COUNT(brick_colors)]);
+          #endif
+
+          // Draw brick if it's still there
           if (TEST(bdat.bricks[y], x)) {
             const uint8_t xx = x * BRICK_W;
-            for (uint8_t v = 0; v < BRICK_H - 1; ++v)
-              if (PAGE_CONTAINS(yy + v, yy + v))
-                u8g.drawHLine(xx, yy + v, BRICK_W - 1);
+            #if IS_DWIN_MARLINUI
+              if (PAGE_CONTAINS(yy, yy + BRICK_H - 1))
+                draw_box(xx, yy, BRICK_W - 1, BRICK_H - 1);
+            #else
+              for (uint8_t v = 0; v < BRICK_H - 1; ++v)
+                if (PAGE_CONTAINS(yy + v, yy + v))
+                  u8g.drawHLine(xx, yy + v, BRICK_W - 1);
+            #endif
           }
         }
       }
     }
   }
 
+  // Everything else is white
+  TERN_(IS_DWIN_MARLINUI, set_color(color::WHITE));
+
   // Draw paddle
   if (PAGE_CONTAINS(PADDLE_Y-1, PADDLE_Y)) {
-    u8g.drawHLine(bdat.paddle_x, PADDLE_Y, PADDLE_W);
+    draw_hline(bdat.paddle_x, PADDLE_Y, PADDLE_W);
     #if PADDLE_H > 1
-      u8g.drawHLine(bdat.paddle_x, PADDLE_Y-1, PADDLE_W);
+      draw_hline(bdat.paddle_x, PADDLE_Y-1, PADDLE_W);
       #if PADDLE_H > 2
-        u8g.drawHLine(bdat.paddle_x, PADDLE_Y-2, PADDLE_W);
+        draw_hline(bdat.paddle_x, PADDLE_Y-2, PADDLE_W);
       #endif
     #endif
   }
@@ -168,29 +187,30 @@ void BrickoutGame::game_screen() {
   if (game_state) {
     const uint8_t by = FTOB(bdat.bally);
     if (PAGE_CONTAINS(by, by+1))
-      u8g.drawFrame(FTOB(bdat.ballx), by, 2, 2);
+      draw_frame(FTOB(bdat.ballx), by, 2, 2);
   }
   // Or draw GAME OVER
   else
     draw_game_over();
 
-  if (PAGE_UNDER(MENU_FONT_ASCENT)) {
+  if (PAGE_UNDER(GAME_FONT_ASCENT)) {
     // Score Digits
-    //const uint8_t sx = (LCD_PIXEL_WIDTH - (score >= 10 ? score >= 100 ? score >= 1000 ? 4 : 3 : 2 : 1) * MENU_FONT_WIDTH) / 2;
+    //const uint8_t sx = (GAME_WIDTH - (score >= 10 ? score >= 100 ? score >= 1000 ? 4 : 3 : 2 : 1) * GAME_FONT_WIDTH) / 2;
     constexpr uint8_t sx = 0;
-    lcd_put_int(sx, MENU_FONT_ASCENT - 1, score);
+    draw_int(sx, GAME_FONT_ASCENT - 1, score);
 
     // Balls Left
-    lcd_moveto(LCD_PIXEL_WIDTH - MENU_FONT_WIDTH * 3, MENU_FONT_ASCENT - 1);
     PGM_P const ohs = PSTR("ooo\0\0");
-    lcd_put_u8str_P(ohs + 3 - bdat.balls_left);
+    draw_string(GAME_WIDTH - GAME_FONT_WIDTH * 3, GAME_FONT_ASCENT - 1, ohs + 3 - bdat.balls_left);
   }
 
+  frame_end();
+
   // A click always exits this game
   if (ui.use_click()) exit_game();
 }
 
-#define SCREEN_M ((LCD_PIXEL_WIDTH) / 2)
+#define SCREEN_M ((GAME_WIDTH) / 2)
 
 void BrickoutGame::enter_game() {
   init_game(2, game_screen); // 2 = reset bricks on paddle hit

+ 8 - 8
Marlin/src/lcd/menu/game/game.cpp

@@ -40,15 +40,15 @@ bool MarlinGame::game_frame() {
 }
 
 void MarlinGame::draw_game_over() {
-  constexpr int8_t gowide = (MENU_FONT_WIDTH) * 9,
-                   gohigh = MENU_FONT_ASCENT - 3,
-                       lx = (LCD_PIXEL_WIDTH - gowide) / 2,
-                       ly = (LCD_PIXEL_HEIGHT + gohigh) / 2;
+  constexpr int8_t gowide = (GAME_FONT_WIDTH) * 9,
+                   gohigh = GAME_FONT_ASCENT - 3,
+                       lx = (GAME_WIDTH - gowide) / 2,
+                       ly = (GAME_HEIGHT + gohigh) / 2;
   if (PAGE_CONTAINS(ly - gohigh - 1, ly + 1)) {
-    u8g.setColorIndex(0);
-    u8g.drawBox(lx - 1, ly - gohigh - 1, gowide + 2, gohigh + 2);
-    u8g.setColorIndex(1);
-    if (ui.get_blink()) lcd_put_u8str(lx, ly, F("GAME OVER"));
+    set_color(color::BLACK);
+    draw_box(lx - 1, ly - gohigh - 1, gowide + 2, gohigh + 2);
+    set_color(color::WHITE);
+    if (ui.get_blink()) draw_string(lx, ly, F("GAME OVER"));
   }
 }
 

Некоторые файлы не были показаны из-за большого количества измененных файлов