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'))
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._item_grid = ItemGridView(master, input_size) # Task 2.2 Crafting: Create widgets here # ... input_frame = tk.Frame(master) input_frame.pack(side=tk.TOP) self._input = ItemGridView(input_frame, input_size) self._input.pack(side=tk.LEFT) self._craft_button = tk.Button(input_frame, text="craft") self._craft_button.pack(side=tk.LEFT) self._output = ItemGridView(input_frame, (1, 1)) self._output.pack(side=tk.LEFT) self.BORDER = 100 # BORDER//2 + |item grid| + BORDER//2 self.CELL_LENGTH = 64 # pixel width of grid cell self.CELL_SPACING = 5 # pixel spacing between grid cells 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: Create 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.draw_cell((0, 0), stack, selected == key) pass else: # Task 2.2 Crafting: Draw input cells # ... self._input.draw_cell(key, stack, selected == key) pass 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 self._input.bind(event, lambda e: callback(self.xy_to_grid( (e.x, e.y)), e)) self._output.bind(event, lambda e: callback("output", e)) self._craft_button.bind(event, lambda e: callback("craft", e)) # 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) # # ... # Task 2.2 Crafting: You may add additional methods here # ... def set_button_method(self, newmethod): self._craft_button['command'] = newmethod def xy_to_grid(self, xy_position): """Returns the grid position of the cell that contains the 'xy_position' Parameters: xy_position (tuple<float, float>): (x, y) coordinates contained by some cell Return: (tuple<int, int>): The (row, column) grid position of the cell """ x, y = xy_position column = (x - self.BORDER // 2) // (self.CELL_LENGTH + self.CELL_SPACING) row = (y - self.BORDER // 2) // (self.CELL_LENGTH + self.CELL_SPACING) return row, column
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, mode="normal"): """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) # check mode if mode not in ["normal", "smelting"]: raise NotImplementedError("Not supported mode") # Task 2.2 Crafting: Create widgets here self._input = SelectableGrid(rows=input_size[0], columns=input_size[1]) self._input_view = ItemGridView(self, self._input.get_size()) self._output = SelectableGrid(rows=1, columns=1) if mode == "smelting": self._button = tk.Button(self, text="=>Smelt=>", height=1) im = Image.open('fire.jpg') self.img = ImageTk.PhotoImage(im) # img = tk.PhotoImage(file="fire.gif") self._label = tk.Label(self, image=self.img) self._label.pack(side=tk.LEFT) else: self._button = tk.Button(self, text="=>Craft=>", height=1) self._input_view.pack(side=tk.LEFT) self._button.pack(side=tk.LEFT) self._output_view = ItemGridView(self, (1, 1)) self._output_view.pack(side=tk.LEFT) self._input_view.render(self._input.items(), self._input.get_selected()) self._output_view.render(self._output.items(), self._output.get_selected()) 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: Create 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_view.draw_cell((0, 0), stack, selected == "output") else: # Task 2.2 Crafting: Draw input cells self._input_view.draw_cell(key, stack, True if selected == key else False) 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 self._input_view.bind_for_id(event, callback) self._output_view.bind(event, lambda e: callback("output", e)) self._button.bind(event, lambda e: callback("craft", e))