import machine import st7789py as st7789 from config import CURRENT_CONFIG import font class Display: def __init__(self): self.tft = None self.width = 240 self.height = 240 self._init_display() self.font = font.Font() def _init_display(self): print(">>> Initializing Display...") try: pins = CURRENT_CONFIG.pins spi = machine.SPI(2, baudrate=40000000, polarity=1, phase=1, sck=machine.Pin(pins['sck']), mosi=machine.Pin(pins['mosi'])) cs_pin = pins.get('cs') cs = machine.Pin(cs_pin, machine.Pin.OUT) if cs_pin is not None else None rst_pin = pins.get('rst') dc_pin = pins.get('dc') self.tft = st7789.ST7789(spi, self.width, self.height, reset=machine.Pin(rst_pin, machine.Pin.OUT) if rst_pin else None, dc=machine.Pin(dc_pin, machine.Pin.OUT) if dc_pin else None, cs=cs, backlight=None) self.tft.init() self.tft.fill(st7789.BLUE) except Exception as e: print(f"Display error: {e}") self.tft = None def fill(self, color): if self.tft: self.tft.fill(color) def fill_rect(self, x, y, w, h, color): if self.tft: self.tft.fill_rect(x, y, w, h, color) def set_ws(self, ws): if self.font: self.font.set_ws(ws) def text(self, text, x, y, color, wait=True): if self.tft: self.font.text(self.tft, text, x, y, color, wait=wait) def init_ui(self): """初始化 UI 背景""" if self.tft: self.tft.fill(st7789.BLACK) self.tft.fill_rect(0, 0, 240, 30, st7789.WHITE) def update_audio_bar(self, bar_height, last_bar_height): """更新音频可视化的柱状图""" if not self.tft: return last_bar_height # 确定当前颜色 color = st7789.GREEN if bar_height > 50: color = st7789.YELLOW if bar_height > 100: color = st7789.RED # 确定上一次颜色 last_color = st7789.GREEN if last_bar_height > 50: last_color = st7789.YELLOW if last_bar_height > 100: last_color = st7789.RED # 1. 如果变矮了,清除顶部多余部分 if bar_height < last_bar_height: self.tft.fill_rect(100, 240 - last_bar_height, 40, last_bar_height - bar_height, st7789.BLACK) # 2. 如果颜色变了,必须重绘整个条 if color != last_color: self.tft.fill_rect(100, 240 - bar_height, 40, bar_height, color) # 3. 如果颜色没变且变高了,只绘新增部分 elif bar_height > last_bar_height: self.tft.fill_rect(100, 240 - bar_height, 40, bar_height - last_bar_height, color) return bar_height def show_image(self, x, y, width, height, rgb565_data): """在指定位置显示RGB565格式的图片数据""" if not self.tft: return try: # 将字节数据转换为适合blit_buffer的格式 self.tft.blit_buffer(rgb565_data, x, y, width, height) except Exception as e: print(f"Show image error: {e}") def show_image_chunk(self, x, y, width, height, data, offset): """流式显示图片数据块""" if not self.tft: return # ST7789 blit_buffer expects a complete buffer for the window # But we can calculate which pixels this chunk corresponds to # This is tricky because blit_buffer sets a window and then writes data. # If we want to stream, we should probably set the window once and then write chunks. # But st7789py library might not expose raw write easily without window set. # Alternative: Calculate the sub-window for this chunk. # Data is a linear sequence of pixels (2 bytes per pixel) # We assume data length is even. try: # Simple approach: If offset is 0, we set the window for the whole image # And then write data. But st7789py's blit_buffer does both. # Let's look at st7789py implementation. # fill_rect sets window then writes. # blit_buffer sets window then writes. # We can use a modified approach: # If it's the first chunk, set window. # Then write data. # But we can't easily modify the library state from here. # So we calculate the rect for this chunk. # Total pixels total_pixels = width * height # Current pixel offset pixel_offset = offset // 2 num_pixels = len(data) // 2 # This only works if chunks align with rows, or if we can write partial rows. # ST7789 supports writing continuous memory. # Let's try to determine the x, y, w, h for this chunk. # This is complex if it wraps around lines. # Easier approach for ESP32 memory constrained environment: # We just need to use the raw write method of the display driver if available. if offset == 0: # Set window for the whole image self.tft.set_window(x, y, x + width - 1, y + height - 1) # Write raw data self.tft.write(None, data) except Exception as e: print(f"Show chunk error: {e}") def render_home_screen(self): """渲染首页""" if not self.tft: return self.tft.fill(st7789.BLACK) # 顶部标题栏 self.tft.fill_rect(0, 0, 240, 40, 0x2124) # Dark Grey self.text("量迹AI贴纸生成", 45, 12, st7789.WHITE) # 中间Logo区域(简单绘制一个框) self.tft.fill_rect(80, 80, 80, 80, st7789.BLUE) self.text("AI", 108, 110, st7789.WHITE) # 底部提示 self.text("正在启动...", 80, 200, st7789.CYAN) def render_wifi_connecting(self): """渲染WiFi连接中界面""" if not self.tft: return self.tft.fill(st7789.BLACK) self.text("WiFi连接中...", 60, 110, st7789.WHITE) # 加载动画会在主循环中绘制 def render_wifi_status(self, success): """渲染WiFi连接结果""" if not self.tft: return self.tft.fill(st7789.BLACK) if success: self.text("WiFi连接成功!", 60, 100, st7789.GREEN) self.draw_check_icon(110, 130) else: self.text("WiFi连接失败", 60, 100, st7789.RED) self.text("请重试", 95, 130, st7789.WHITE) def draw_top_tip(self, text): """在右上角显示提示文字""" if not self.tft: return # 清除区域 (假设背景是白色的,因为顶部栏通常是白色) # x=170, w=70, h=30 self.tft.fill_rect(170, 0, 70, 30, st7789.WHITE) if text: # 使用红色显示提示,醒目 self.text(text, 170, 8, st7789.RED, wait=False) def draw_check_icon(self, x, y): """绘制勾选图标""" if not self.tft: return self.tft.line(x, y + 5, x + 3, y + 8, st7789.GREEN) self.tft.line(x + 3, y + 8, x + 10, y, st7789.GREEN)