@@ -52,6 +52,14 @@ def __init__(self, display, width=None, height=None):
5252 self ._d = display
5353 self .width = width or getattr (display , 'width' , 128 )
5454 self .height = height or getattr (display , 'height' , 128 )
55+ # Detect if the backend needs greyscale ints (SSD1327) or accepts RGB tuples
56+ self ._needs_gray = not hasattr (display , 'fill_rect' ) or hasattr (display , 'framebuf' )
57+
58+ def _c (self , color ):
59+ """Convert color for the backend. Returns gray4 int for SSD1327, pass-through otherwise."""
60+ if self ._needs_gray :
61+ return rgb_to_gray4 (color )
62+ return color
5563
5664 # --- Adaptive properties ---
5765
@@ -113,7 +121,7 @@ def _resolve(self, at, text_len=0, scale=1):
113121 def title (self , text , color = GRAY ):
114122 """Draw title text at the top (N)."""
115123 x , y = self ._resolve ("N" , len (text ))
116- self ._d . text (text , x , y , color )
124+ self ._text (text , x , y , color )
117125
118126 def value (self , val , unit = None , at = "CENTER" , label = None ,
119127 color = WHITE , scale = 2 , y_offset = 0 ):
@@ -148,7 +156,7 @@ def value(self, val, unit=None, at="CENTER", label=None,
148156 # Optional label above
149157 if label :
150158 lx = x + tw // 2 - len (label ) * self .CHAR_W // 2
151- self ._d . text (label , lx , y - self .CHAR_H - 4 , GRAY )
159+ self ._text (label , lx , y - self .CHAR_H - 4 , GRAY )
152160
153161 # Value (large)
154162 self ._draw_scaled_text (text , x , y , color , scale )
@@ -160,7 +168,7 @@ def value(self, val, unit=None, at="CENTER", label=None,
160168 if hasattr (self ._d , 'draw_medium_text' ):
161169 self ._d .draw_medium_text (unit , ux , unit_y , LIGHT )
162170 else :
163- self ._d . text (unit , ux , unit_y , LIGHT )
171+ self ._text (unit , ux , unit_y , LIGHT )
164172
165173 def subtitle (self , * lines , color = DARK ):
166174 """Draw subtitle text at the bottom (S). Accepts multiple lines."""
@@ -177,7 +185,7 @@ def subtitle(self, *lines, color=DARK):
177185 block_h = (n - 1 ) * line_h
178186 start_y = base_y - block_h // 2
179187
180- draw = getattr (self ._d , 'draw_small_text' , self ._d . text )
188+ draw = getattr (self ._d , 'draw_small_text' , self ._text )
181189 for i , line in enumerate (lines ):
182190 x , _ = self ._resolve ("S" , len (line ))
183191 y = start_y + i * line_h
@@ -190,7 +198,7 @@ def bar(self, val, max_val=100, y_offset=0, color=LIGHT):
190198 bar_h = 8
191199 bx = cx - bar_w // 2
192200 by = cy + 20 + y_offset
193- fill_w = int (bar_w * min (val , max_val ) / max_val )
201+ fill_w = int (bar_w * min (val , max_val ) / max_val ) if max_val else 0
194202
195203 # Background
196204 self ._fill_rect (bx , by , bar_w , bar_h , DARK )
@@ -238,7 +246,7 @@ def gauge(self, val, min_val=0, max_val=100, unit=None, color=LIGHT):
238246 if hasattr (self ._d , 'draw_medium_text' ):
239247 self ._d .draw_medium_text (unit , ux , uy , LIGHT )
240248 else :
241- self ._d . text (unit , ux , uy , LIGHT )
249+ self ._text (unit , ux , uy , LIGHT )
242250
243251 # Min/max labels at arc endpoints (slightly inward to stay visible)
244252 min_t = str (int (min_val ))
@@ -251,7 +259,7 @@ def gauge(self, val, min_val=0, max_val=100, unit=None, color=LIGHT):
251259 ly = int (cy + r_label * math .sin (angle_s ))
252260 rx = int (cx + r_label * math .cos (angle_e )) - len (max_t ) * self .CHAR_W // 2
253261 ry = int (cy + r_label * math .sin (angle_e ))
254- draw_sm = getattr (self ._d , 'draw_small_text' , self ._d . text )
262+ draw_sm = getattr (self ._d , 'draw_small_text' , self ._text )
255263 draw_sm (min_t , lx , ly , GRAY )
256264 draw_sm (max_t , rx , ry , GRAY )
257265
@@ -272,7 +280,7 @@ def graph(self, data, min_val=0, max_val=100, color=LIGHT):
272280 if data :
273281 text = str (int (data [- 1 ]))
274282 draw_fn = getattr (self ._d , 'draw_medium_text' ,
275- self ._d . text )
283+ self ._text )
276284 tw = len (text ) * self .CHAR_W
277285 vx = cx - tw // 2
278286 vy = 31
@@ -284,7 +292,7 @@ def _fmt(v):
284292 return str (int (v // 1000 )) + "k"
285293 return str (int (v ))
286294
287- draw_sm = getattr (self ._d , 'draw_small_text' , self ._d . text )
295+ draw_sm = getattr (self ._d , 'draw_small_text' , self ._text )
288296 mid_val = (min_val + max_val ) / 2
289297 for val , yp in [(max_val , gy ),
290298 (mid_val , gy + gh // 2 ),
@@ -301,7 +309,7 @@ def _fmt(v):
301309 x = gx + 1
302310 while x < gx + gw :
303311 x2 = min (x + dash - 1 , gx + gw - 1 )
304- self ._d . line (x , mid_y , x2 , mid_y , (51 , 51 , 51 ))
312+ self ._line (x , mid_y , x2 , mid_y , (51 , 51 , 51 ))
305313 x += dash + gap
306314
307315 # Y axis (extend +1 to meet X axis corner)
@@ -341,9 +349,9 @@ def menu(self, items, selected=0, color=WHITE):
341349 iy = y + (i - start ) * item_h
342350 if i == selected :
343351 self ._fill_rect (15 , iy - 2 , self .width - 30 , item_h , DARK )
344- self ._d . text ("> " + items [i ], 18 , iy , color )
352+ self ._text ("> " + items [i ], 18 , iy , color )
345353 else :
346- self ._d . text (" " + items [i ], 18 , iy , GRAY )
354+ self ._text (" " + items [i ], 18 , iy , GRAY )
347355
348356 def compass (self , heading , color = LIGHT ):
349357 """Draw a compass with a rotating needle."""
@@ -359,7 +367,7 @@ def compass(self, heading, color=LIGHT):
359367 lx = cx + int ((r + 5 ) * math .sin (math .radians (angle )))
360368 ly = cy - int ((r + 5 ) * math .cos (math .radians (angle )))
361369 c = WHITE if label == "N" else GRAY
362- self ._d . text (label , lx - self .CHAR_W // 2 , ly - self .CHAR_H // 2 , c )
370+ self ._text (label , lx - self .CHAR_W // 2 , ly - self .CHAR_H // 2 , c )
363371
364372 # Tick marks (8 directions)
365373 for angle in range (0 , 360 , 45 ):
@@ -427,7 +435,7 @@ def watch(self, hours, minutes, seconds=0, color=LIGHT):
427435 lx = cx + int ((r - 15 ) * math .sin (rad ))
428436 ly = cy - int ((r - 15 ) * math .cos (rad ))
429437 tw = len (text ) * self .CHAR_W
430- self ._d . text (text , lx - tw // 2 , ly - self .CHAR_H // 2 , WHITE )
438+ self ._text (text , lx - tw // 2 , ly - self .CHAR_H // 2 , WHITE )
431439
432440 # Hour hand (short, thick)
433441 h_angle = (hours % 12 + minutes / 60 ) * 30
@@ -506,7 +514,7 @@ def text(self, text, at="CENTER", color=WHITE, scale=1):
506514 if scale > 1 :
507515 self ._draw_scaled_text (text , x , y , color , scale )
508516 else :
509- self ._d . text (text , x , y , color )
517+ self ._text (text , x , y , color )
510518
511519 def line (self , x1 , y1 , x2 , y2 , color = WHITE ):
512520 self ._line (x1 , y1 , x2 , y2 , color )
@@ -524,41 +532,49 @@ def rect(self, x, y, w, h, color=WHITE, fill=False):
524532 self ._rect (x , y , w , h , color )
525533
526534 def pixel (self , x , y , color = WHITE ):
527- self ._d . pixel (x , y , color )
535+ self ._pixel (x , y , self . _c ( color ) )
528536
529537 # --- Control ---
530538
531539 def clear (self , color = BLACK ):
532- self ._d .fill (color )
540+ self ._d .fill (self . _c ( color ) )
533541
534542 def show (self ):
535543 self ._d .show ()
536544
537545 # --- Internal drawing helpers ---
538546
547+ def _text (self , text , x , y , c ):
548+ self ._d .text (text , x , y , self ._c (c ))
549+
550+ def _pixel (self , x , y , c ):
551+ self ._d .pixel (x , y , self ._c (c ))
552+
539553 def _line (self , x1 , y1 , x2 , y2 , c ):
540- self ._d .line (x1 , y1 , x2 , y2 , c )
554+ self ._d .line (x1 , y1 , x2 , y2 , self . _c ( c ) )
541555
542556 def _hline (self , x , y , w , c ):
543- self ._d .line (x , y , x + w - 1 , y , c )
557+ self ._d .line (x , y , x + w - 1 , y , self . _c ( c ) )
544558
545559 def _vline (self , x , y , h , c ):
546- self ._d .line (x , y , x , y + h - 1 , c )
560+ self ._d .line (x , y , x , y + h - 1 , self . _c ( c ) )
547561
548562 def _fill_rect (self , x , y , w , h , c ):
563+ cc = self ._c (c )
549564 if hasattr (self ._d , 'fill_rect' ):
550- self ._d .fill_rect (x , y , w , h , c )
565+ self ._d .fill_rect (x , y , w , h , cc )
551566 elif hasattr (self ._d , 'framebuf' ):
552- self ._d .framebuf .fill_rect (x , y , w , h , rgb_to_gray4 ( c ) )
567+ self ._d .framebuf .fill_rect (x , y , w , h , cc )
553568 else :
554569 for row in range (h ):
555- self ._d .line (x , y + row , x + w - 1 , y + row , c )
570+ self ._d .line (x , y + row , x + w - 1 , y + row , cc )
556571
557572 def _rect (self , x , y , w , h , c ):
573+ cc = self ._c (c )
558574 if hasattr (self ._d , 'rect' ):
559- self ._d .rect (x , y , w , h , c )
575+ self ._d .rect (x , y , w , h , cc )
560576 elif hasattr (self ._d , 'framebuf' ):
561- self ._d .framebuf .rect (x , y , w , h , rgb_to_gray4 ( c ) )
577+ self ._d .framebuf .rect (x , y , w , h , cc )
562578 else :
563579 self ._hline (x , y , w , c )
564580 self ._hline (x , y + h - 1 , w , c )
@@ -578,21 +594,21 @@ def _draw_scaled_text(self, text, x, y, color, scale):
578594 # On real hardware without scaled text support, draw at scale=1
579595 # centered at the same position (best effort)
580596 if not hasattr (self ._d , 'pixel' ):
581- self ._d . text (text , x , y , color )
597+ self ._text (text , x , y , color )
582598 return
583599 # Render at 1x to a temporary buffer, then scale up
584600 # For MicroPython: draw each character using the display's text method
585601 # but multiple times offset for a bold effect at scale 2
586602 if scale == 2 :
587603 for dx in range (2 ):
588604 for dy in range (2 ):
589- self ._d . text (text , x + dx , y + dy , color )
605+ self ._text (text , x + dx , y + dy , color )
590606 elif scale == 3 :
591607 for dx in range (3 ):
592608 for dy in range (3 ):
593- self ._d . text (text , x + dx , y + dy , color )
609+ self ._text (text , x + dx , y + dy , color )
594610 else :
595- self ._d . text (text , x , y , color )
611+ self ._text (text , x , y , color )
596612
597613 def _draw_arc (self , cx , cy , r , start_deg , sweep_deg , color , width = 3 ):
598614 """Draw a thick arc using individual pixels."""
@@ -607,7 +623,7 @@ def _draw_arc(self, cx, cy, r, start_deg, sweep_deg, color, width=3):
607623 x = int (cx + (r + dr ) * math .cos (angle ))
608624 y = int (cy + (r + dr ) * math .sin (angle ))
609625 if 0 <= x < self .width and 0 <= y < self .height :
610- self ._d . pixel (x , y , color )
626+ self ._pixel (x , y , color )
611627
612628 def _draw_circle (self , cx , cy , r , color ):
613629 """Bresenham circle."""
@@ -617,7 +633,7 @@ def _draw_circle(self, cx, cy, r, color):
617633 (x , - y ), (y , - x ), (- x , - y ), (- y , - x )):
618634 px , py = cx + sx , cy + sy
619635 if 0 <= px < self .width and 0 <= py < self .height :
620- self ._d . pixel (px , py , color )
636+ self ._pixel (px , py , color )
621637 y += 1
622638 if d < 0 :
623639 d += 2 * y + 1
@@ -633,7 +649,7 @@ def _fill_circle(self, cx, cy, r, color):
633649 if 0 <= y < self .height :
634650 x1 = max (0 , cx - dx )
635651 x2 = min (self .width - 1 , cx + dx )
636- self ._d . line (x1 , y , x2 , y , color )
652+ self ._line (x1 , y , x2 , y , color )
637653
638654 def _fill_triangle (self , x0 , y0 , x1 , y1 , x2 , y2 , color ):
639655 """Filled triangle using scanline."""
@@ -659,4 +675,4 @@ def interp(ya, xa, yb, xb, y):
659675 x_start = max (0 , xl )
660676 x_end = min (self .width - 1 , xr )
661677 if x_start <= x_end :
662- self ._d . line (x_start , y , x_end , y , color )
678+ self ._line (x_start , y , x_end , y , color )
0 commit comments