Skip to content

Commit

Permalink
Basic image support
Browse files Browse the repository at this point in the history
  • Loading branch information
mikee47 committed Feb 12, 2024
1 parent efe746e commit 777f2e1
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 79 deletions.
180 changes: 111 additions & 69 deletions Tools/ged/ged.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ def draw_text(self, rect, text, halign, valign):
y0 += (y1 - y2) // 2
self.canvas.coords(id, x0, y0)

def draw_image(self, rect, image):
x0, y0, x1, y1 = self.handler.tk_bounds(rect)
self.canvas.create_image(x0, y0, image=self.handler.tk_image(image), anchor=tk.NW, tags=self.tags)


def union(r1: Rect, r2: Rect):
x = min(r1.x, r2.x)
Expand All @@ -169,66 +173,39 @@ def get_handle_pos(r: Rect, elem: Element):
}.get(elem)


class FontAssets(list):
class ResourceList(list):
def __init__(self):
super().__init__()
self.clear()

def clear(self):
super().clear()
tk_def = FontAssets.tk_default().configure()
font = Font(name='default', family = tk_def['family'])
self.default = font
self.append(font)

def get(self, font_name, default=None):
def get(self, name, default=None):
try:
return next(font for font in self if font.name == font_name)
return next(r for r in self if r.name == name)
except StopIteration:
return default

@staticmethod
def tk_default():
return tkinter.font.nametofont('TkDefaultFont')

@staticmethod
def families():
# Not all fonts are listed by TK, so include the 'guaranteed supported' ones
font_families = list(tk.font.families())
tk_def = FontAssets.tk_default().configure()
font_families += ['Courier', 'Times', 'Helvetica', tk_def['family']]
font_families = list(set(font_families))
return sorted(font_families, key=str.lower)

def names(self):
return [font.name for font in self]
return [r.name for r in self]

def asdict(self):
return dict(
(font.name, {
'family': font_def.family,
'size': font_def.size,
}) for font in self)
return dict((r.name, r.asdict()) for r in self)

def load(self, font_defs):
def load(self, res_class, res_dict):
self.clear()
for name, font_def in font_defs.items():
font = Font(name=name, family=font_def['family'], size=font_def['size'])
self.append(font)

font_assets = None
for name, rdef in res_dict.items():
r = res_class(name=name)
for a, v in rdef.items():
setattr(r, a, v)
self.append(r)


class ImageAssets(list):
def __init__(self):
super().__init__()
self.clear()

def get(self, image_name, default=None):
try:
return next(font for font in self if font.name == font_name)
except StopIteration:
return default
class FontAssets(ResourceList):
def clear(self):
super().clear()
tk_def = FontAssets.tk_default().configure()
font = Font(name='default', family = tk_def['family'])
self.default = font
self.append(font)

@staticmethod
def tk_default():
Expand All @@ -243,21 +220,15 @@ def families():
font_families = list(set(font_families))
return sorted(font_families, key=str.lower)

def names(self):
return [font.name for font in self]
def load(self, res_dict):
super().load(Font, res_dict)

def asdict(self):
return dict(
(font.name, {
'family': font_def.family,
'size': font_def.size,
}) for font in self)
font_assets = None

def load(self, font_defs):
self.clear()
for name, font_def in font_defs.items():
font = Font(name=name, family=font_def['family'], size=font_def['size'])
self.append(font)

class ImageAssets(ResourceList):
def load(self, res_dict):
super().load(Image, res_dict)

image_assets = None

Expand Down Expand Up @@ -344,6 +315,9 @@ def tk_font(self, font_name: str):
font = font_assets.get(font_name, font_assets.default)
return tkinter.font.Font(family=font.family, size=-font.size*self.scale)

def tk_image(self, image_name: str):
return image_assets.get(image_name).get_tk_image(self.scale)

def clear(self):
self.display_list.clear()
self.sel_items.clear()
Expand Down Expand Up @@ -572,6 +546,7 @@ def add_item(itemtype):
'R': 'FilledRect',
'e': 'Ellipse',
'E': 'FilledEllipse',
'i': 'Image',
't': 'Text',
'b': 'Button',
}.get(c)
Expand All @@ -580,8 +555,9 @@ def add_item(itemtype):


class Editor:
def __init__(self, root, title):
def __init__(self, root, title, field_prefix):
self.frame = ttk.LabelFrame(root, text=title)
self.field_prefix = field_prefix
self.frame.columnconfigure(1, weight=1)
self.is_updating = False
self.on_value_changed = None
Expand All @@ -605,13 +581,11 @@ def add_control(self, ctrl, row):
def text_from_name(name: str):
words = re.findall(r'[A-Z]?[a-z]+|[A-Z]+(?=[A-Z]|$)', name)
return ' '.join(words).lower()
# return re.sub("([a-z])([A-Z])", "\g<0> \g<1>", name)
# return name.replace('_', ' ').capitalize()

def add_field(self, name: str, ctrl, var_type=tk.StringVar):
row = len(self.fields)
self.add_label(self.text_from_name(name), row)
var = var_type(name=name)
var = var_type(name=self.field_prefix+name)
ctrl.configure(textvariable=var)
var.trace_add('write', self.tk_value_changed)
self.add_control(ctrl, row)
Expand All @@ -635,7 +609,7 @@ def add_check_fields(self, title, names: list):
self.add_control(frame, row)
frame.grid(column=0, columnspan=2)
for name in names:
var = tk.BooleanVar(name=name)
var = tk.BooleanVar(name=self.field_prefix+name)
var.trace_add('write', self.tk_value_changed)
ctrl = ttk.Checkbutton(frame, variable=var, text=self.text_from_name(name))
ctrl.pack(side=tk.LEFT)
Expand All @@ -646,12 +620,13 @@ def tk_value_changed(self, name1, name2, op):
if self.is_updating:
return
"""See 'trace add variable' in TCL docs"""
var, _ = self.fields[name1]
name = name1[len(self.field_prefix):]
var, _ = self.fields[name]
if var is None:
return
try:
# print(f'value_changed:"{name1}", "{name2}", "{op}", "{var.get()}"')
self.value_changed(name1, var.get())
self.value_changed(name, var.get())
except tk.TclError: # Can happen whilst typing if conversion fails, empty strings, etc.
pass

Expand All @@ -671,14 +646,14 @@ def get_value(self, name):

class ProjectEditor(Editor):
def __init__(self, root):
super().__init__(root, 'Project')
super().__init__(root, 'Project', 'proj-')
for name in ['width', 'height', 'scale', 'grid_alignment']:
self.add_entry_field(name, tk.IntVar)


class PropertyEditor(Editor):
def __init__(self, root):
super().__init__(root, 'Properties')
super().__init__(root, 'Properties', 'prop-')

def set_field(self, name=str, values=list, options=list, callback=None):
value_list = values + [o for o in options if o not in values]
Expand All @@ -701,15 +676,14 @@ def handler(evt, callback=callback, var=var):

class FontEditor(Editor):
def __init__(self, root):
super().__init__(root, 'Font')
super().__init__(root, 'Font', 'font-')
self.add_combo_field('name')
self.add_combo_field('family', font_assets.families())
self.add_entry_field('size', tk.IntVar)
self.add_check_fields('Style', ['normal', 'italic', 'bold', 'boldItalic'])
self.update()

def value_changed(self, name, value):
print(f'value_changed: "{name}", "{value}"')
if name == 'name':
font = font_assets.get(value)
if font:
Expand Down Expand Up @@ -745,6 +719,55 @@ def select(self, font):
self.is_updating = False


class ImageEditor(Editor):
def __init__(self, root):
super().__init__(root, 'Image', 'img-')
self.add_combo_field('name')
self.add_combo_field('source')
self.add_combo_field('format')
self.add_entry_field('width', tk.IntVar)
self.add_entry_field('height', tk.IntVar)
self.update()

def value_changed(self, name, value):
if name == 'name':
image = image_assets.get(value)
if image:
self.select(image)
return
image_name = self.get_value('name')
# print(f'value_changed: "{name}", "{value}", "{font_name}"')
image = image_assets.get(image_name)
if image is None:
return
setattr(image, name, value)
self.update()
super().value_changed(name, value)

def update(self):
for image in image_assets:
image.get_tk_image()
image = self.get_value('name')
image_names = image_assets.names()
_, c = self.fields['name']
c.configure(values=image_names)
if not image and image_assets:
image = image_assets[0]
self.select(image)

def select(self, image):
if isinstance(image, str):
image = image_assets.get(image)
if not image:
return
self.is_updating = True
try:
for k, v in dataclasses.asdict(image).items():
self.set_value(k, v)
finally:
self.is_updating = False


def run():
PROJECT_EXT = '.ged'
PROJECT_FILTER = [('GED Project', '*' + PROJECT_EXT)]
Expand Down Expand Up @@ -792,6 +815,8 @@ def fileClear():
handler.clear()
font_assets.clear()
font_editor.update()
image_assets.clear()
image_editor.update()

def fileAddRandom(count=10):
display_list = []
Expand Down Expand Up @@ -831,6 +856,8 @@ def fileLoad():
data = json_load(filename)
font_assets.load(data['fonts'])
font_editor.update()
image_assets.load(data['images'])
image_editor.update()
handler.display_list = []
display_list = dl_deserialise(data['layout'])
handler.clear()
Expand All @@ -844,6 +871,7 @@ def fileSave():
filename += PROJECT_EXT
data = {
'fonts': font_assets.asdict(),
'images': image_assets.asdict(),
'layout': dl_serialise(handler.display_list),
}
json_save(data, filename)
Expand Down Expand Up @@ -938,6 +966,8 @@ def sel_changed(full_change: bool):
values = list(values)
if name == 'font':
options = font_assets.names()
elif name == 'image':
options = image_assets.names()
elif isinstance(values[0], Color):
options = sorted(colormap.keys())
def select_color(var):
Expand All @@ -961,6 +991,18 @@ def font_value_changed(name, value):
font_editor.on_value_changed = font_value_changed
font_editor.frame.pack(side=tk.TOP, expand=True, fill=tk.X, ipady=4)

# Images
global image_assets
image_assets = ImageAssets()
image_editor = ImageEditor(edit_frame)
def image_value_changed(name, value):
print(f'image_value_changed("{name}", "{value}")')
handler.redraw()
image_editor.on_value_changed = image_value_changed
image_editor.frame.pack(side=tk.TOP, expand=True, fill=tk.X, ipady=4)

print('image_assets', image_assets.names())

#
tk.mainloop()

Expand Down
9 changes: 2 additions & 7 deletions Tools/ged/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,8 @@ class GImage(GItem):
image: str = ''

def draw(self, c):
c.color = str(self.color)
c.line_width = self.line_width

if self.radius > 1:
c.draw_rounded_rect(self, self.radius)
else:
c.draw_rect(self)
if self.image:
c.draw_image(self, self.image)


@dataclass
Expand Down
Loading

0 comments on commit 777f2e1

Please sign in to comment.