Render Ox widgets on the Page Editor canvas as the device draws them
The Page Editor canvas now paints widgets WYSIWYG instead of as labelled placeholder boxes, following the ontrolOx draw() code and the R-ION firmware: - text drawn with the device's own bitmap fonts (fontBank .bin / fontBankXL .oxfl), Windows font fallback; alignment, clipping at the box edge, half-size subscript digits, transparent vs filled boxes; the line is centred on the box even when the font is taller - each widget shows the text its `in` selects: NumericLabel formats, lookup items, Boolean true/false text and colours, date and time - images from the local image banks, centred or cropped; missing ones drawn as the device's red/white checker; page background image - 10x10 pixel grid over the page colour, with a Grid toggle - widgets hit-tested by layout box so transparent labels stay clickable; hidden widgets shown as a dashed outline - layout row gets an X/Y/Width/Height dialog - scaled device frame cached per zoom (redraws ~15 ms) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,531 @@
|
||||
"""Draws Ox widgets on the Page Editor canvas the way the device's firmware draws them.
|
||||
|
||||
The rules are the R-ION firmware's (graphics.c, ontrolXx.c) and each widget's draw():
|
||||
|
||||
text boxes drawn with the device's own bitmap fonts from Sedona home's fontBank
|
||||
(a Windows font of the same size when the file is not there). The
|
||||
line is centred vertically, aligned by attributes bits 4-5 when the
|
||||
box is wider than the text, and cut at the character that would cross
|
||||
the right edge. A box whose draw-background bit (3) is clear is
|
||||
transparent. The last `subscriptDigits` characters are drawn half size.
|
||||
images centred in a box larger than the image, cropped to a smaller one; a
|
||||
file the editor cannot find is drawn as the device draws a missing
|
||||
one, a red and white checker.
|
||||
values each widget shows the text its `in` currently selects, so a live
|
||||
page reads as the device does and an offline one shows the defaults.
|
||||
|
||||
Everything is drawn in canvas units: device pixels times the zoom.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import glob
|
||||
import os
|
||||
import tkinter as tk
|
||||
from tkinter import font as tkfont
|
||||
|
||||
try:
|
||||
from PIL import Image, ImageTk
|
||||
_HAS_PIL = True
|
||||
except ImportError:
|
||||
_HAS_PIL = False
|
||||
|
||||
import app_state
|
||||
import properties
|
||||
from ox_widget_properties import (_Model, BACKGROUND_BIT, ALIGN_MASK, SUBSCRIPT_MASK,
|
||||
DATE_HIDE_YEAR, DATE_TWO_DIGIT_YEAR, DATE_LEADING_ZERO,
|
||||
DATE_MONTH_FIRST, IMAGE_EXTENSIONS, font_names,
|
||||
parse_lookup, parse_numeric_format, rgb565_to_hex)
|
||||
|
||||
PLACEHOLDER_FILL = "#dce6f2"
|
||||
PLACEHOLDER_OUTLINE = "#7f9db9"
|
||||
HIDDEN_OUTLINE = "#9a9a9a"
|
||||
CHECKER_COLOURS = ((232, 60, 60), (255, 255, 255)) # the firmware's RED, WHITE
|
||||
CHECKER_CELL = 4
|
||||
SEPARATORS = {0: "/", 1: ".", 2: "-", 3: " "}
|
||||
FALLBACK_FAMILY = "Segoe UI"
|
||||
|
||||
_font_cache = {}
|
||||
_families = None
|
||||
_text_cache = {} # rendered lines, so a redraw does not re-decode every glyph
|
||||
TEXT_CACHE_SIZE = 400
|
||||
|
||||
|
||||
def _font(family, pixels, style):
|
||||
"""A Tk font `pixels` tall; style is fontAttributes' 0 normal, 1 bold, 2 italic."""
|
||||
global _families
|
||||
if _families is None:
|
||||
_families = {f.lower() for f in tkfont.families()}
|
||||
if family.lower() not in _families:
|
||||
family = FALLBACK_FAMILY
|
||||
key = (family, pixels, style)
|
||||
if key not in _font_cache:
|
||||
_font_cache[key] = tkfont.Font(
|
||||
family=family, size=-max(1, pixels),
|
||||
weight="bold" if style == 1 else "normal",
|
||||
slant="italic" if style == 2 else "roman")
|
||||
return _font_cache[key]
|
||||
|
||||
|
||||
# --- the device's own bitmap fonts ----------------------------------------------------
|
||||
|
||||
class BitmapFont:
|
||||
"""A device font file: fontBank/'32pt Tahoma.bin', or fontBankXL/'.oxfl' (same layout).
|
||||
|
||||
0..63 header; u16 character count at 32, line height at 34
|
||||
64.. 10 bytes per character: code (u8, or u16 above 191), data offset (u24
|
||||
at 2), width, height, x offset (signed), y offset, x advance
|
||||
data 4-bit grey, two pixels a byte, high nibble first, row by row
|
||||
"""
|
||||
|
||||
def __init__(self, path):
|
||||
data = open(path, "rb").read()
|
||||
self.data = data
|
||||
self.height = data[34]
|
||||
count = data[32] | data[33] << 8
|
||||
self.glyphs = {}
|
||||
for index in range(count):
|
||||
e = data[64 + index * 10:74 + index * 10]
|
||||
if len(e) < 10:
|
||||
break
|
||||
code = e[0] if e[0] < 192 and e[1] == 0 else e[0] | e[1] << 8
|
||||
self.glyphs[code] = (e[2] | e[3] << 8 | e[4] << 16, e[5], e[6],
|
||||
e[7] - 256 if e[7] > 127 else e[7], e[8], e[9])
|
||||
|
||||
def metrics(self, code):
|
||||
"""(printWidth, xOffset) the way Calculate_Text_Width works them out."""
|
||||
_, width, _, x_off, _, advance = self.glyphs[code]
|
||||
x_offset = 1 if x_off < 0 else x_off + 1
|
||||
print_width = max(advance, width, x_offset + width)
|
||||
return print_width, x_offset
|
||||
|
||||
def glyph_image(self, code):
|
||||
offset, width, height, _, _, _ = self.glyphs[code]
|
||||
img = Image.new("L", (max(1, width), max(1, height)))
|
||||
if width and height:
|
||||
raw = self.data[offset:offset + (width * height + 1) // 2]
|
||||
pixels = bytearray(width * height)
|
||||
for i in range(width * height):
|
||||
byte = raw[i >> 1] if (i >> 1) < len(raw) else 0
|
||||
pixels[i] = ((byte >> 4) if i % 2 == 0 else (byte & 0x0F)) * 17
|
||||
img.putdata(pixels)
|
||||
return img
|
||||
|
||||
|
||||
_bitmap_fonts = {}
|
||||
|
||||
|
||||
def find_bitmap_font(family, size, style):
|
||||
"""The device font for fontName/fontSize/fontAttributes, or None when none is local.
|
||||
|
||||
get_Font_File_Name's naming: '<size>pt <Name>', prefixed 'bold ' for bold. There is
|
||||
no italic cut, so italic falls back to the regular one as the device's lookup would
|
||||
find nothing better.
|
||||
"""
|
||||
home = getattr(app_state, "SEDONA_HOME", None)
|
||||
if not home or not _HAS_PIL:
|
||||
return None
|
||||
stem = f"{'bold ' if style == 1 else ''}{size}pt {family}"
|
||||
candidates = [os.path.join(home, "ontrol", "fontBank", stem + ".bin"),
|
||||
os.path.join(home, "ontrol", "fontBankXL", stem + ".oxfl")]
|
||||
if style == 1:
|
||||
plain = f"{size}pt {family}"
|
||||
candidates += [os.path.join(home, "ontrol", "fontBank", plain + ".bin"),
|
||||
os.path.join(home, "ontrol", "fontBankXL", plain + ".oxfl")]
|
||||
for path in candidates:
|
||||
if path in _bitmap_fonts:
|
||||
return _bitmap_fonts[path]
|
||||
if os.path.isfile(path):
|
||||
try:
|
||||
_bitmap_fonts[path] = BitmapFont(path)
|
||||
except (OSError, IndexError):
|
||||
_bitmap_fonts[path] = None
|
||||
return _bitmap_fonts[path]
|
||||
return None
|
||||
|
||||
|
||||
def render_bitmap_text(font, text, box_width, subscript):
|
||||
"""(mask, text width): the line as Draw_Text_Box lays it out, in device pixels.
|
||||
|
||||
Characters the font lacks are skipped, the last `subscript` drawn ones are halved
|
||||
in both directions and sit on the lower half of the line (Stretch_Char), and the
|
||||
line stops at the first character that would cross the box's right edge.
|
||||
"""
|
||||
codes = [ord(ch) for ch in text if ord(ch) in font.glyphs]
|
||||
stretch_from = len(codes) - subscript if 0 < subscript <= len(codes) else len(codes)
|
||||
layout, width = [], 0
|
||||
for n, code in enumerate(codes):
|
||||
print_width, x_offset = font.metrics(code)
|
||||
stretched = n >= stretch_from
|
||||
if stretched:
|
||||
print_width, x_offset = print_width >> 1, x_offset >> 1
|
||||
if width + print_width > box_width:
|
||||
break
|
||||
layout.append((code, width + x_offset, stretched))
|
||||
width += print_width
|
||||
|
||||
mask = Image.new("L", (max(1, width), max(1, font.height)))
|
||||
for code, x, stretched in layout:
|
||||
glyph = font.glyph_image(code)
|
||||
_, _, height, _, y_offset, _ = font.glyphs[code]
|
||||
if stretched:
|
||||
glyph = glyph.resize((max(1, glyph.width >> 1), max(1, glyph.height >> 1)),
|
||||
Image.BOX)
|
||||
y_offset += height >> 1
|
||||
mask.paste(glyph, (x, y_offset), glyph)
|
||||
return mask, width
|
||||
|
||||
|
||||
def find_image_file(name):
|
||||
"""The local file for a device image name, looked for in Sedona home's image banks."""
|
||||
if not name:
|
||||
return None
|
||||
home = getattr(app_state, "SEDONA_HOME", None)
|
||||
if not home:
|
||||
return None
|
||||
for folder in sorted(glob.glob(os.path.join(home, "ontrol", "imageBank*"))):
|
||||
for ext in IMAGE_EXTENSIONS:
|
||||
path = os.path.join(folder, name + ext)
|
||||
if os.path.isfile(path):
|
||||
return path
|
||||
return None
|
||||
|
||||
|
||||
def _format_value(fmt, value):
|
||||
"""sprintf(fmt, value) for the %f formats NumericLabel carries."""
|
||||
parts = parse_numeric_format(fmt)
|
||||
if parts is not None:
|
||||
prefix, show, precision, suffix = parts
|
||||
return f"{prefix}{value:.{precision}f}{suffix}" if show else prefix
|
||||
try:
|
||||
return fmt % value
|
||||
except (TypeError, ValueError):
|
||||
return fmt
|
||||
|
||||
|
||||
def _lookup_text(text, index):
|
||||
"""Process_Text: prefix + the index'th item + suffix, or '<n>:Error'."""
|
||||
prefix, items, suffix = parse_lookup(text)
|
||||
if 0 <= index < len(items):
|
||||
return f"{prefix}{items[index]}{suffix}"
|
||||
return f"{index}:Error"
|
||||
|
||||
|
||||
def _date_text(options, separator):
|
||||
"""Today's date as DateLabelSet.prepareDateString lays it out."""
|
||||
today = datetime.date.today()
|
||||
no_zero = bool(options & DATE_LEADING_ZERO) # the firmware drops the zero when set
|
||||
day = str(today.day) if no_zero else f"{today.day:02d}"
|
||||
month = str(today.month) if no_zero else f"{today.month:02d}"
|
||||
sep = SEPARATORS.get(separator, "/")
|
||||
first, second = (month, day) if options & DATE_MONTH_FIRST else (day, month)
|
||||
text = f"{first}{sep}{second}"
|
||||
if not options & DATE_HIDE_YEAR:
|
||||
year = today.year % 100 if options & DATE_TWO_DIGIT_YEAR else today.year
|
||||
text += f"{sep}{year:02d}"
|
||||
return text
|
||||
|
||||
|
||||
def _time_text(prefix, minutes, am_pm):
|
||||
"""drawTimeTextBox with digits 0: prefix + HH:MM, optionally 12-hour."""
|
||||
minutes = int(minutes) % 1440
|
||||
hour, minute = divmod(minutes, 60)
|
||||
if not am_pm:
|
||||
return f"{prefix}{hour:02d}:{minute:02d}"
|
||||
suffix = " AM" if hour < 12 else " PM"
|
||||
hour = 12 if hour == 0 else hour - 12 if hour > 12 else hour
|
||||
return f"{prefix}{hour:02d}:{minute:02d}{suffix}"
|
||||
|
||||
|
||||
def _as_float(raw, fallback=0.0):
|
||||
try:
|
||||
return float(raw)
|
||||
except (TypeError, ValueError):
|
||||
return fallback
|
||||
|
||||
|
||||
def _bool_state(raw):
|
||||
return properties.bool_state(raw)
|
||||
|
||||
|
||||
class WidgetPainter:
|
||||
"""Paints one page's widgets; holds the PhotoImages the canvas needs kept alive."""
|
||||
|
||||
def __init__(self, canvas, origin_x, origin_y, zoom, page_colour):
|
||||
self.c = canvas
|
||||
self.ox = origin_x
|
||||
self.oy = origin_y
|
||||
self.z = zoom
|
||||
self.page_colour = page_colour
|
||||
self.images = []
|
||||
|
||||
# ── primitives ────────────────────────────────────────────────
|
||||
|
||||
def _xy(self, x, y):
|
||||
return self.ox + x * self.z, self.oy + y * self.z
|
||||
|
||||
def _shape(self, x, y, w, h, fill, shape, tags):
|
||||
x1, y1 = self._xy(x, y)
|
||||
x2, y2 = self._xy(x + w, y + h)
|
||||
maker = self.c.create_oval if shape else self.c.create_rectangle
|
||||
maker(x1, y1, x2, y2, fill=fill, outline="", tags=tags)
|
||||
|
||||
def text_box(self, m, text, x, y, w, h, text_colour, box_colour, tags,
|
||||
attributes=None, shape=None):
|
||||
"""A label's box and text.
|
||||
|
||||
The line box (the font's full height) is centred on the widget box even when
|
||||
it is the taller of the two, so the glyphs sit in the box as Workbench shows
|
||||
them rather than hanging below a short one.
|
||||
"""
|
||||
attributes = m.int("attributes", 0x08) if attributes is None else attributes
|
||||
shape = m.int("shape") if shape is None else shape
|
||||
names = font_names(m) if "fontName" in m.slots else ("Tahoma",)
|
||||
family_index = m.int("fontName")
|
||||
family = names[family_index] if 0 <= family_index < len(names) else names[0]
|
||||
size = m.int("fontSize", 16)
|
||||
style = m.int("fontAttributes")
|
||||
|
||||
if attributes & BACKGROUND_BIT:
|
||||
self._shape(x, y, w, h, rgb565_to_hex(box_colour), shape, tags)
|
||||
if not text:
|
||||
return
|
||||
|
||||
digits = attributes & SUBSCRIPT_MASK
|
||||
bitmap = find_bitmap_font(family, size, style)
|
||||
if bitmap is not None:
|
||||
self._bitmap_text(bitmap, text, x, y, w, h, attributes, digits, text_colour, tags)
|
||||
else:
|
||||
self._tk_text(family, size, style, text, x, y, w, h, attributes, digits,
|
||||
text_colour, tags)
|
||||
|
||||
@staticmethod
|
||||
def _left_space(attributes, box_width, text_width):
|
||||
if box_width <= text_width:
|
||||
return 0
|
||||
alignment = attributes & ALIGN_MASK
|
||||
if alignment == 0x20:
|
||||
return box_width - text_width
|
||||
if alignment == 0x10:
|
||||
return (box_width - text_width) / 2
|
||||
return 0
|
||||
|
||||
def _bitmap_text(self, font, text, x, y, w, h, attributes, digits, text_colour, tags):
|
||||
key = (id(font), text, w, digits, text_colour, self.z)
|
||||
cached = _text_cache.get(key)
|
||||
if cached is None:
|
||||
mask, width = render_bitmap_text(font, text, w, digits)
|
||||
if width == 0:
|
||||
return
|
||||
size = (max(1, round(mask.width * self.z)), max(1, round(mask.height * self.z)))
|
||||
if size != mask.size:
|
||||
# Up close the device's pixels are shown as pixels; below 1:1 they blend.
|
||||
mask = mask.resize(size, Image.NEAREST if self.z >= 2 else Image.LANCZOS)
|
||||
r, g, b = (int(rgb565_to_hex(text_colour)[i:i + 2], 16) for i in (1, 3, 5))
|
||||
img = Image.new("RGBA", mask.size, (r, g, b, 0))
|
||||
img.putalpha(mask)
|
||||
if len(_text_cache) > TEXT_CACHE_SIZE:
|
||||
_text_cache.clear()
|
||||
cached = _text_cache[key] = (ImageTk.PhotoImage(img), width)
|
||||
photo, width = cached
|
||||
self.images.append(photo)
|
||||
left = self._left_space(attributes, w, width)
|
||||
top = (h - font.height) / 2
|
||||
self.c.create_image(*self._xy(x + left, y + top), anchor="nw", image=photo, tags=tags)
|
||||
|
||||
def _tk_text(self, family, size, style, text, x, y, w, h, attributes, digits,
|
||||
text_colour, tags):
|
||||
"""The fallback when no device font file is found: a Windows font of the size."""
|
||||
z = self.z
|
||||
main_font = _font(family, round(size * z), style)
|
||||
half_font = _font(family, round(size * z / 2), style)
|
||||
split = len(text) - digits if 0 < digits < len(text) else len(text)
|
||||
chars = [(ch, main_font if i < split else half_font) for i, ch in enumerate(text)]
|
||||
|
||||
limit = w * z
|
||||
drawn, width = [], 0.0
|
||||
for ch, fnt in chars:
|
||||
advance = fnt.measure(ch)
|
||||
if width + advance > limit + 0.5:
|
||||
break
|
||||
drawn.append((ch, fnt))
|
||||
width += advance
|
||||
|
||||
bx, by = self._xy(x, y)
|
||||
cursor = bx + self._left_space(attributes, limit, width)
|
||||
top = by + (h * z - main_font.metrics("linespace")) / 2
|
||||
colour = rgb565_to_hex(text_colour)
|
||||
main_text = "".join(ch for ch, fnt in drawn if fnt is main_font)
|
||||
tail_text = "".join(ch for ch, fnt in drawn if fnt is half_font)
|
||||
if main_text:
|
||||
self.c.create_text(cursor, top, text=main_text, anchor="nw", fill=colour,
|
||||
font=main_font, tags=tags)
|
||||
cursor += main_font.measure(main_text)
|
||||
if tail_text:
|
||||
baseline = top + main_font.metrics("ascent") - half_font.metrics("ascent")
|
||||
self.c.create_text(cursor, baseline, text=tail_text, anchor="nw", fill=colour,
|
||||
font=half_font, tags=tags)
|
||||
|
||||
def image_box(self, name, x, y, w, h, tags):
|
||||
path = find_image_file(name)
|
||||
if not _HAS_PIL:
|
||||
self._placeholder(x, y, w, h, name or "image", tags)
|
||||
return
|
||||
if path is None:
|
||||
self._checker(x, y, w, h, tags)
|
||||
return
|
||||
try:
|
||||
img = Image.open(path).convert("RGBA")
|
||||
except Exception:
|
||||
self._checker(x, y, w, h, tags)
|
||||
return
|
||||
iw, ih = img.size
|
||||
crop_w, crop_h = min(iw, w), min(ih, h)
|
||||
img = img.crop((0, 0, crop_w, crop_h))
|
||||
left = (w - iw) // 2 if w > iw else 0
|
||||
top = (h - ih) // 2 if h > ih else 0
|
||||
size = (max(1, round(crop_w * self.z)), max(1, round(crop_h * self.z)))
|
||||
if size != img.size:
|
||||
img = img.resize(size, Image.LANCZOS)
|
||||
photo = ImageTk.PhotoImage(img)
|
||||
self.images.append(photo)
|
||||
self.c.create_image(*self._xy(x + left, y + top), anchor="nw", image=photo, tags=tags)
|
||||
|
||||
def _checker(self, x, y, w, h, tags):
|
||||
cw, ch = max(1, round(w * self.z)), max(1, round(h * self.z))
|
||||
cell = max(1, round(CHECKER_CELL * self.z))
|
||||
img = Image.new("RGB", (cw, ch))
|
||||
pixels = img.load()
|
||||
for py in range(ch):
|
||||
row = (py // cell) & 1
|
||||
for px in range(cw):
|
||||
pixels[px, py] = CHECKER_COLOURS[((px // cell) & 1) ^ row]
|
||||
photo = ImageTk.PhotoImage(img)
|
||||
self.images.append(photo)
|
||||
self.c.create_image(*self._xy(x, y), anchor="nw", image=photo, tags=tags)
|
||||
|
||||
def _placeholder(self, x, y, w, h, label, tags):
|
||||
x1, y1 = self._xy(x, y)
|
||||
x2, y2 = self._xy(x + w, y + h)
|
||||
self.c.create_rectangle(x1, y1, x2, y2, fill=PLACEHOLDER_FILL,
|
||||
outline=PLACEHOLDER_OUTLINE, tags=tags)
|
||||
self.c.create_text((x1 + x2) / 2, (y1 + y2) / 2, text=label, fill="#000000",
|
||||
font=(FALLBACK_FAMILY, max(7, min(10, int(min(x2 - x1, y2 - y1) // 8)))),
|
||||
tags=tags)
|
||||
|
||||
def page_image(self, name, width, height):
|
||||
"""OxPage.imageFileName, drawn over the page colour from the top-left corner."""
|
||||
if not name or find_image_file(name) is None:
|
||||
return
|
||||
self.image_box(name, 0, 0, width, height, ("page_image",))
|
||||
|
||||
# ── widgets ───────────────────────────────────────────────────
|
||||
|
||||
def draw(self, widget, tags, short_label):
|
||||
"""Paints `widget`; returns its box (x, y, w, h) in device pixels."""
|
||||
m = _Model(widget)
|
||||
x, y = m.int("x"), m.int("y")
|
||||
w, h = max(1, m.int("width", 80)), max(1, m.int("height", 80))
|
||||
kind = widget.get("type", "").rsplit("::", 1)[-1]
|
||||
|
||||
if _bool_state(m.raw("visibility")) == "false":
|
||||
x1, y1 = self._xy(x, y)
|
||||
x2, y2 = self._xy(x + w, y + h)
|
||||
self.c.create_rectangle(x1, y1, x2, y2, outline=HIDDEN_OUTLINE, dash=(3, 3),
|
||||
tags=tags)
|
||||
return x, y, w, h
|
||||
|
||||
painter = getattr(self, f"_draw_{kind}", None)
|
||||
if painter is None:
|
||||
self._placeholder(x, y, w, h, f"{widget.get('name', '?')}\n({short_label})", tags)
|
||||
else:
|
||||
painter(m, x, y, w, h, tags)
|
||||
return x, y, w, h
|
||||
|
||||
def _draw_PushButtonLabel(self, m, x, y, w, h, tags):
|
||||
self.text_box(m, m.text("text"), x, y, w, h, m.int("textColor"),
|
||||
m.int("backgroundColor", 0xFF00), tags)
|
||||
|
||||
def _draw_NumericLabel(self, m, x, y, w, h, tags):
|
||||
fmt = m.text("text")
|
||||
value = _as_float(m.raw("in"))
|
||||
tab = m.int("valueTabPosition")
|
||||
split = fmt.find("%")
|
||||
if tab == 0 or split <= 0:
|
||||
self.text_box(m, _format_value(fmt, value), x, y, w, h, m.int("textColor"),
|
||||
m.int("backgroundColor", 0xFF00), tags)
|
||||
return
|
||||
attributes = m.int("attributes", 0x08)
|
||||
self.text_box(m, fmt[:split].replace("%%", "%"), x, y, tab, h, m.int("textColor"),
|
||||
m.int("backgroundColor", 0xFF00), tags, shape=0)
|
||||
self.text_box(m, _format_value(": " + fmt[split:], value), x + tab, y, w - tab, h,
|
||||
m.int("textColor"), m.int("backgroundColor", 0xFF00), tags,
|
||||
attributes=attributes & 0xCF, shape=0)
|
||||
|
||||
_draw_NumericLabelSet = _draw_NumericLabel
|
||||
|
||||
def _draw_NumericSelectLabel(self, m, x, y, w, h, tags):
|
||||
text = m.text("text")
|
||||
if not text:
|
||||
return
|
||||
self.text_box(m, _lookup_text(text, m.int("in")), x, y, w, h, m.int("textColor"),
|
||||
m.int("backgroundColor", 0xFF00), tags)
|
||||
|
||||
def _draw_BooleanLabel(self, m, x, y, w, h, tags):
|
||||
state = _bool_state(m.raw("in"))
|
||||
if state == "true":
|
||||
text, fg, bg = m.text("trueText"), m.int("trueTextColor"), m.int("trueBackgroundColor")
|
||||
else:
|
||||
text = m.text("falseText") if state == "false" else "Null"
|
||||
fg, bg = m.int("falseTextColor"), m.int("falseBackgroundColor")
|
||||
attributes = m.int("attributes", 0x08)
|
||||
if not text:
|
||||
attributes &= 0xF7
|
||||
self.text_box(m, text, x, y, w, h, fg, bg, tags, attributes=attributes)
|
||||
|
||||
def _draw_ScheduleLabelSet(self, m, x, y, w, h, tags):
|
||||
state = _bool_state(m.raw("in"))
|
||||
if state == "true":
|
||||
img, fg, bg, index = (m.text("trueImageFileName"), m.int("trueTextColor"),
|
||||
m.int("trueBackgroundColor"), 1)
|
||||
else:
|
||||
img = m.text("falseImageFileName") if state == "false" else ""
|
||||
fg, bg = m.int("falseTextColor"), m.int("falseBackgroundColor")
|
||||
index = 0 if state == "false" else 2
|
||||
if img:
|
||||
self.image_box(img, x, y, w, h, tags)
|
||||
elif m.text("text"):
|
||||
self.text_box(m, _lookup_text(m.text("text"), index), x, y, w, h, fg, bg, tags)
|
||||
|
||||
def _draw_DateLabelSet(self, m, x, y, w, h, tags):
|
||||
text = _date_text(m.int("dateFormatOptions"), m.int("dateSeparatorCharacter"))
|
||||
self.text_box(m, text, x, y, w, h, m.int("textColor"),
|
||||
m.int("backgroundColor", 0xFF00), tags)
|
||||
|
||||
def _draw_TimeLabelSet(self, m, x, y, w, h, tags):
|
||||
am_pm = _bool_state(m.raw("amPmFormat")) == "true"
|
||||
text = _time_text(m.text("text"), m.int("in"), am_pm)
|
||||
self.text_box(m, text, x, y, w, h, m.int("textColor"),
|
||||
m.int("backgroundColor", 0xFF00), tags,
|
||||
attributes=m.int("attributes", 0x08) & 0xF8)
|
||||
|
||||
def _draw_Geometry(self, m, x, y, w, h, tags):
|
||||
if _bool_state(m.raw("transparent")) != "true":
|
||||
self._shape(x, y, w, h, rgb565_to_hex(m.int("backgroundColor", 0xFF00)),
|
||||
m.int("shape"), tags)
|
||||
|
||||
def _draw_CalibrationButton(self, m, x, y, w, h, tags):
|
||||
self._shape(x, y, w, h, rgb565_to_hex(m.int("backgroundColor", 0xFF00)),
|
||||
m.int("shape"), tags)
|
||||
|
||||
def _draw_PushButtonImage(self, m, x, y, w, h, tags):
|
||||
self.image_box(m.text("imageFileName"), x, y, w, h, tags)
|
||||
|
||||
def _draw_BooleanImage(self, m, x, y, w, h, tags):
|
||||
state = _bool_state(m.raw("in"))
|
||||
if state == "null":
|
||||
return
|
||||
name = m.text("trueImageFileName" if state == "true" else "falseImageFileName")
|
||||
self.image_box(name, x, y, w, h, tags)
|
||||
|
||||
def _draw_NumericSelectImage(self, m, x, y, w, h, tags):
|
||||
self.image_box(f"{m.text('imageFileNamePrefix')}{m.int('in')}", x, y, w, h, tags)
|
||||
Reference in New Issue
Block a user