class GridCrafterView(tk.Frame): """A tkinter widget used to display crafting with a grid as input and a single cell as output""" def __init__(self, master, input_size): """Constructor Parameters: master (tk.Frame | tk.Toplevel | tk.Tk): Tkinter parent widget input_size (tuple<int, int>): The (row, column) size of the grid crafter's input grid """ super().__init__(master) # Task 2.2 Crafting: Create widgets here # ... # create input/output/button wedgets self._input_widget = ItemGridView(self, input_size) self._crafting_button = tk.Button(self, text=" => Craft => ") self._output_widget = ItemGridView(self, (1, 1)) self._input_widget.pack(side=tk.LEFT) self._crafting_button.pack(side=tk.LEFT) self._output_widget.pack(side=tk.TOP) def render(self, key_stack_pairs, selected): """Renders the stacks at appropriate cells, as determined by 'key_stack_pairs' Parameters: key_stack_pairs (tuple<*, Stack>): (key, stack) pairs, where each stack should be drawn at the cell corresponding to key selected (*): The key that is currently selected, or None if no key is selected """ # Task 2.2 Crafting: Render widgets here # ... print(f"{selected} is selected") for key, stack in key_stack_pairs: print(f"Redrawing {stack} at {key}") if key == "output": # Task 2.2 Crafting: Draw output cell # ... self._output_widget.draw_cell((0, 0), stack, key == selected) else: # Task 2.2 Crafting: Draw input cells # ... self._input_widget.draw_cell(key, stack, key == selected) def bind_for_id(self, event, callback): """Binds callback to tkinter mouse event Callback accept parameters: callback(key, event), where - key (*) is the key of the cell clicked, etc. - mouse_event (tk.MouseEvent) is the original mouse event from tkinter """ if event not in TK_MOUSE_EVENTS: return # Task 2.2 Crafting: Bind to tkinter widgets here # When a cell is clicked, we need to call the callback. Tkinter's bind does # this for us, but not exactly how we want. Tkinter bound callbacks have a single # parameter, the mouse event containing useful information about the click (i.e. # the x & y coordinates) # # However, x & y coordinates aren't that useful. The class controlling this widget # (i.e. CraftingWindow) only needs to know which cell was clicked. It's not # concerned with where it was clicked, just that it was. This is so it can easily # interact with the crafter model (i.e. GridCrafter) and move stacks around or # select/deselect them. # # To integrate with CraftingWindow, you will need to transform the callback # provided to tk.bind, exactly as is done in ItemGridView.bind_for_id, except # the first argument may not necessarily be a (row, column) position, but # simply an arbitrary key (for basic 2x2 crafting, the 5 keys are: # "output", (0, 0), (0, 1), (1, 0), (1, 1) # # ... self._input_widget.bind( event, lambda e: callback(self._input_widget.xy_to_grid((e.x, e.y)), e)) self._output_widget.bind(event, lambda e: callback("output", e)) self._crafting_button.bind(event, lambda e: callback("craft", e))
class GridCrafterView(tk.Frame): """A tkinter widget used to display crafting with a grid as input and a single cell as output""" def __init__(self, master, input_size): """Constructor Parameters: master (tk.Frame | tk.Toplevel | tk.Tk): Tkinter parent widget input_size (tuple<int, int>): The (row, column) size of the grid crafter's input grid """ super().__init__(master) self._master = master self._crafting_type = 'basic' # Task 2.2 Crafting: Create widgets here # ... print(input_size) if input_size == (2, 1): self._crafting_type = 'furnace' # Frame for the smelting item, fuel and fire image self._furnace_frame = tk.Frame(self) # Create one grid for the item to be smelted self._smelt_grid = Grid(rows=1, columns=1) self._smelt_grid_view = ItemGridView(self._furnace_frame, (1, 1)) self._smelt_grid_view.pack(side=tk.TOP, expand=True) # Flame self._flame_img = ImageTk.PhotoImage(Image.open('flame.png')) self._flame_label = tk.Label(self._furnace_frame, image=self._flame_img) self._flame_label.pack(side=tk.TOP) # Create on grid below the fire for the fuel item self._fuel_grid = Grid(rows=1, columns=1) self._fuel_grid_view = ItemGridView(self._furnace_frame, (1, 1)) self._fuel_grid_view.pack(side=tk.TOP, expand=True) # Render and pack self._smelt_grid_view.render(self._smelt_grid.items(), None) self._fuel_grid_view.render(self._fuel_grid.items(), None) self._furnace_frame.pack(side=tk.LEFT, expand=True) else: # Basic crafting grid for 2x2 or crafting table self._input_grid = Grid(rows=input_size[0], columns=input_size[1]) self._input_grid_view = ItemGridView(self, self._input_grid.get_size()) self._input_grid_view.pack(side=tk.LEFT, expand=True) self._input_grid_view.render(self._input_grid.items(), None) # Create output cell and craft button, the same for all cases self._output_cell = Grid(rows=1, columns=1) self._output_cell_view = ItemGridView(self, self._output_cell.get_size()) self._output_cell_view.pack(side=tk.RIGHT, expand=True) self._craft_button = tk.Button(self, text='=>Craft=>') self._craft_button.pack(side=tk.LEFT) self._output_cell_view.render(self._output_cell.items(), None) def render(self, key_stack_pairs, selected): """Renders the stacks at appropriate cells, as determined by 'key_stack_pairs' Parameters: key_stack_pairs (tuple<*, Stack>): (key, stack) pairs, where each stack should be drawn at the cell corresponding to key selected (*): The key that is currently selected, or None if no key is selected """ # Task 2.2 Crafting: Render widgets here # ... #print(f"{selected} is selected") #print(key_stack_pairs) for key, stack in key_stack_pairs: #print(f"Redrawing {stack} at {key}") if key == "output": # Task 2.2 Crafting: Draw output cell # ... self._output_cell_view.draw_cell((0, 0), stack, active=selected == key) elif self._crafting_type == 'furnace' and key == (0, 0): self._smelt_grid_view.draw_cell((0, 0), stack, active=selected == key) elif self._crafting_type == 'furnace' and key == (1, 0): self._fuel_grid_view.draw_cell((0, 0), stack, active=selected == key) elif self._crafting_type == 'basic': # Task 2.2 Crafting: Draw input cells # ... self._input_grid_view.draw_cell(key, stack, active=selected == key) def bind_for_id(self, event, callback): """Binds callback to tkinter mouse event Callback accept parameters: callback(key, event), where - key (*) is the key of the cell clicked, etc. - mouse_event (tk.MouseEvent) is the original mouse event from tkinter """ if event not in TK_MOUSE_EVENTS: return # Task 2.2 Crafting: Bind to tkinter widgets here # When a cell is clicked, we need to call the callback. Tkinter's bind does # this for us, but not exactly how we want. Tkinter bound callbacks have a single # parameter, the mouse event containing useful information about the click (i.e. # the x & y coordinates) # # However, x & y coordinates aren't that useful. The class controlling this widget # (i.e. CraftingWindow) only needs to know which cell was clicked. It's not # concerned with where it was clicked, just that it was. This is so it can easily # interact with the crafter model (i.e. GridCrafter) and move stacks around or # select/deselect them. # # To integrate with CraftingWindow, you will need to transform the callback # provided to tk.bind, exactly as is done in ItemGridView.bind_for_id, except # the first argument may not necessarily be a (row, column) position, but # simply an arbitrary key (for basic 2x2 crafting, the 5 keys are: # "output", (0, 0), (0, 1), (1, 0), (1, 1) # # ... if self._crafting_type == 'basic': self._input_grid_view.bind( event, lambda e: callback( self._input_grid_view.xy_to_grid((e.x, e.y)), e)) elif self._crafting_type == 'furnace': self._smelt_grid_view.bind(event, lambda e: callback((0, 0), e)) self._fuel_grid_view.bind(event, lambda e: callback((1, 0), e)) self._output_cell_view.bind(event, lambda e: callback("output", e)) self._craft_button.bind(event, lambda e: callback('craft', 'craft'))