mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2025-04-26 15:40:47 +00:00
275 lines
9.2 KiB
Python
275 lines
9.2 KiB
Python
#
|
|
# copyleft Elliot Alderson from F society
|
|
# copyleft Darlene Alderson from F society
|
|
#
|
|
# This file is part of PortaPack.
|
|
#
|
|
# 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 2, 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; see the file COPYING. If not, write to
|
|
# the Free Software Foundation, Inc., 51 Franklin Street,
|
|
# Boston, MA 02110-1301, USA.
|
|
#
|
|
import re
|
|
import tkinter as tk
|
|
from typing import Dict, List, Tuple
|
|
import argparse
|
|
from dataclasses import dataclass
|
|
|
|
"""
|
|
TODO: Multile class in one file seperation
|
|
TODO: Add more widget type
|
|
|
|
widget compatible guide:
|
|
|
|
1. add the preview color in the "widgets = {" table
|
|
(note that this color is only for easier to distinguish)
|
|
|
|
2. add the widget type and also regex in the "parsers" table
|
|
(note that your regex should apply all the possible constructor overload (re-impl))
|
|
|
|
"""
|
|
|
|
# pp hard coded vars
|
|
screen_width = 240
|
|
SCREEN_W = 240
|
|
screen_height = 320
|
|
CHARACTER_WIDTH = 8
|
|
LINE_HEIGHT = 16
|
|
topbar_offset = 16
|
|
# widgets actually drew after the top bar,
|
|
# for example if Y = 0 then Y on screen actually is 16
|
|
|
|
# scale factor
|
|
scale = 2
|
|
|
|
widgets = {
|
|
"Button": "lightgray",
|
|
"NewButton": "lightyellow",
|
|
"Text": "lightblue",
|
|
"Rectangle": "lightgreen",
|
|
"Image": "pink",
|
|
"ImageButton": "lightpink",
|
|
"NumberField": "peachpuff",
|
|
"ProgressBar": "lightcoral",
|
|
"Console": "wheat",
|
|
"Checkbox": "plum",
|
|
"Label": "lavender",
|
|
"TextField": "paleturquoise",
|
|
"OptionsField": "palegreen",
|
|
"VuMeter": "sandybrown",
|
|
"BigFrequency": "khaki"
|
|
}
|
|
|
|
@dataclass
|
|
class Widget:
|
|
name: str
|
|
widget_type: str
|
|
x: int
|
|
y: int
|
|
width: int
|
|
height: int
|
|
text: str = ""
|
|
|
|
class WidgetParser:
|
|
def __init__(self):
|
|
|
|
self.parsers: Dict[str, re.Pattern] = {
|
|
'Button': re.compile( # TODO: fix those that using language helper
|
|
r'Button\s+(\w+)\s*\{\s*\{([^}]+)\},\s*"([^"]+)"\s*\};',
|
|
re.MULTILINE
|
|
),
|
|
'NewButton': re.compile(
|
|
r'NewButton\s+(\w+)\s*\{\s*(?:\{([^}]+)\}|{}),\s*(?:"([^"]*)"|\{\}),\s*(?:[^,}]+(?:,\s*[^}]+)*|\{\})\};',
|
|
re.MULTILINE
|
|
),
|
|
'Text': re.compile(
|
|
r'Text\s+(\w+)\s*\{\s*\{([^}]+)\}(?:\s*,\s*"([^"]*)")?\s*\};',
|
|
re.MULTILINE
|
|
),
|
|
'ProgressBar': re.compile(
|
|
r'ProgressBar\s+(\w+)\s*\{\s*\{([^}]+)\}\s*\};',
|
|
re.MULTILINE
|
|
),
|
|
'Console': re.compile(
|
|
r'Console\s+(\w+)\s*\{\s*\{([^}]+)\}\s*\};',
|
|
re.MULTILINE
|
|
),
|
|
'Label': re.compile(
|
|
r'Label\s+(\w+)\s*\{\s*\{([^}]+)\},\s*"([^"]+)"\s*\};',
|
|
re.MULTILINE
|
|
),
|
|
'VuMeter': re.compile(
|
|
r'VuMeter\s+(\w+)\s*\{\s*\{([^}]+)\},\s*\d+,\s*(?:true|false)\s*\};',
|
|
re.MULTILINE
|
|
),
|
|
'BigFrequency': re.compile(
|
|
r'BigFrequency\s+(\w+)\s*\{\s*\{([^}]+)\},\s*\d+\s*\};',
|
|
re.MULTILINE
|
|
)
|
|
}
|
|
|
|
def parse_coordinates(self, coord_str: str) -> Tuple[int, int, int, int]:
|
|
"""Parse coordinate string like '2 * 8, 8 * 16, 8 * 8, 3 * 16'"""
|
|
if not coord_str or coord_str.isspace():
|
|
return (0, 0, 0, 0) # for empty constructor fallback
|
|
|
|
# split and evaluate each coordinate expression
|
|
coords = [eval(x.strip()) for x in coord_str.split(',')]
|
|
|
|
# ensure we have exactly 4 coordinates, pad with zeros if needed
|
|
while len(coords) < 4:
|
|
coords.append(0)
|
|
|
|
# only take the first 4 coordinates if there are more
|
|
return tuple(coords[:4])
|
|
|
|
def parse_file(self, filepath: str) -> List[Widget]:
|
|
with open(filepath, 'r') as f:
|
|
content = f.read()
|
|
|
|
widgets = []
|
|
for widget_type, pattern in self.parsers.items():
|
|
matches = pattern.finditer(content)
|
|
for match in matches:
|
|
name = match.group(1) # Widget name is always in group 1
|
|
coords = self.parse_coordinates(match.group(2) if match.group(2) else "")
|
|
|
|
# Only try to get text if the pattern has 3 or more groups
|
|
text = ""
|
|
try:
|
|
if match.lastindex >= 3:
|
|
text = match.group(3) if match.group(3) else ""
|
|
except IndexError:
|
|
pass
|
|
|
|
widgets.append(Widget(
|
|
name=name,
|
|
widget_type=widget_type,
|
|
x=coords[0],
|
|
y=coords[1],
|
|
width=coords[2],
|
|
height=coords[3],
|
|
text=text
|
|
))
|
|
|
|
return widgets
|
|
|
|
class WidgetPreview(tk.Tk):
|
|
def __init__(self, widgets: List[Widget]):
|
|
super().__init__()
|
|
self.title("Widget Preview")
|
|
|
|
self.canvas = tk.Canvas(self, width=screen_width * scale, height=screen_height * scale, bg='white')
|
|
self.canvas.pack(padx=10, pady=10)
|
|
|
|
self.all_text_elements = []
|
|
self.draw_widgets(widgets)
|
|
|
|
def draw_widgets(self, widgets: List[Widget]):
|
|
draw_top_bar(self)
|
|
for widget in widgets:
|
|
self.draw_widget(widget)
|
|
|
|
def draw_widget(self, widget: Widget):
|
|
print(f"Drawing widget: {widget.name} ({widget.widget_type})")
|
|
|
|
x1 = widget.x * scale
|
|
y1 = (widget.y + topbar_offset) * scale
|
|
x2 = x1 + (widget.width * scale)
|
|
y2 = y1 + (widget.height * scale)
|
|
|
|
print(f"Coordinates: ({x1}, {y1}), ({x2}, {y2})")
|
|
|
|
if widget.widget_type == "VuMeter":
|
|
segment_height = widget.height / 8 * scale
|
|
for i in range(8):
|
|
self.canvas.create_rectangle(
|
|
x1, y1 + (i * segment_height),
|
|
x2, y1 + ((i+1) * segment_height),
|
|
fill=widgets[widget.widget_type],
|
|
outline="gray"
|
|
)
|
|
elif widget.widget_type == "BigFrequency":
|
|
# Draw 7-segment style display
|
|
self.canvas.create_rectangle(
|
|
x1, y1, x2, y2,
|
|
fill=widgets[widget.widget_type],
|
|
outline="gray"
|
|
)
|
|
self.canvas.create_text(
|
|
(x1 + x2) / 2,
|
|
(y1 + y2) / 2,
|
|
text="433.92" # placeholder text
|
|
)
|
|
|
|
else:
|
|
# defualt rendering
|
|
rect_id = self.canvas.create_rectangle(
|
|
x1, y1, x2, y2,
|
|
fill=widgets[widget.widget_type]
|
|
)
|
|
|
|
type_text_id = self.canvas.create_text(
|
|
(x1 + x2) // 2,
|
|
(y1 + y2) // 2,
|
|
text=widget.widget_type
|
|
)
|
|
|
|
detail_text_id = self.canvas.create_text(
|
|
(x1 + x2) // 2,
|
|
(y1 + y2) // 2,
|
|
text=f"{widget.widget_type}|{widget.name}|{widget.text}",
|
|
state='hidden'
|
|
)
|
|
|
|
widget_texts = {
|
|
'type': type_text_id,
|
|
'detail': detail_text_id
|
|
}
|
|
self.all_text_elements.append(widget_texts)
|
|
|
|
# hover handlers
|
|
def on_enter(event):
|
|
for texts in self.all_text_elements:
|
|
self.canvas.itemconfig(texts['type'], state='hidden')
|
|
self.canvas.itemconfig(texts['detail'], state='hidden')
|
|
self.canvas.itemconfig(detail_text_id, state='normal')
|
|
self.canvas.tag_raise(detail_text_id)
|
|
|
|
def on_leave(event):
|
|
for texts in self.all_text_elements:
|
|
self.canvas.itemconfig(texts['type'], state='normal')
|
|
self.canvas.tag_raise(texts['type'])
|
|
self.canvas.itemconfig(texts['detail'], state='hidden')
|
|
|
|
for item_id in [rect_id, type_text_id, detail_text_id]:
|
|
self.canvas.tag_bind(item_id, '<Enter>', on_enter)
|
|
self.canvas.tag_bind(item_id, '<Leave>', on_leave)
|
|
|
|
def draw_top_bar(self):
|
|
self.canvas.create_rectangle(0, 0, screen_width * scale, topbar_offset * scale, fill='lightblue')
|
|
self.canvas.create_text(screen_width * scale // 2, topbar_offset * scale // 2, text='I\'m Top Bar, hover mouse on items to check details', fill='black')
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Preview UI widgets from hpp files')
|
|
parser.add_argument('file', help='Path to the hpp file')
|
|
args = parser.parse_args()
|
|
|
|
widget_parser = WidgetParser()
|
|
widgets = widget_parser.parse_file(args.file)
|
|
|
|
app = WidgetPreview(widgets)
|
|
app.mainloop()
|
|
|
|
if __name__ == "__main__":
|
|
main() |