import tkinter as tk from tkinter import ttk from PIL import Image, ImageTk from icon_loader import generate_seed_image from seed_profile import GrowthStage LIME = "#39FF14" BLACK = "#000000" class Tooltip: def __init__(self, widget, text): self.widget = widget self.text = text self.tip_window = None widget.bind("", self.show) widget.bind("", self.hide) def show(self, event=None): if self.tip_window: return x = self.widget.winfo_pointerx() + 10 y = self.widget.winfo_pointery() + 10 self.tip_window = tw = tk.Toplevel(self.widget) tw.wm_overrideredirect(True) tw.configure(bg=BLACK) label = tk.Label( tw, text=self.text, bg=BLACK, fg=LIME, justify=tk.LEFT, relief=tk.SOLID, borderwidth=1, highlightbackground=LIME, highlightthickness=1, padx=4, pady=2, font=("Consolas", 8) ) label.pack() tw.wm_geometry(f"+{x}+{y}") def hide(self, event=None): if self.tip_window: self.tip_window.destroy() self.tip_window = None class ScrollableFrame(ttk.Frame): def __init__(self, container, *args, **kwargs): super().__init__(container, *args, **kwargs) # Create a canvas and a vertical scrollbar for scrolling it self.canvas = tk.Canvas(self, borderwidth=0, highlightthickness=0) self.vscrollbar = ttk.Scrollbar(self, orient="vertical", command=self.canvas.yview) self.canvas.configure(yscrollcommand=self.vscrollbar.set) self.vscrollbar.pack(side="right", fill="y") self.canvas.pack(side="left", fill="both", expand=True) # This frame will be scrolled inside the canvas self.scrollable_frame = ttk.Frame(self.canvas) self.scrollable_frame.bind( "", lambda e: self.canvas.configure( scrollregion=self.canvas.bbox("all") ) ) # Create a window inside the canvas to hold the scrollable frame self.canvas_frame = self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw") # Bind events to make width resize correctly self.scrollable_frame.bind("", self._on_frame_configure) self.canvas.bind("", self._on_canvas_configure) # Optional: bind mouse wheel to scroll self.canvas.bind_all("", self._on_mousewheel) def _on_frame_configure(self, event): # Update scrollregion and match width of the canvas window to the frame self.canvas.configure(scrollregion=self.canvas.bbox("all")) self.canvas.itemconfig(self.canvas_frame, width=self.canvas.winfo_width()) def _on_canvas_configure(self, event): # Resize the inner frame's width to match canvas width self.canvas.itemconfig(self.canvas_frame, width=event.width) def _on_mousewheel(self, event): # Scroll vertically self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") class InventoryGUI: def __init__(self, root, catalog, breeder, starter_loader): self.root = root self.catalog = catalog self.breeder = breeder self.starter_loader = starter_loader self.selected_seeds = [] self.image_cache = {} self._configure_styles() self.root.title("Seed Inventory") self.root.configure(bg=BLACK) self._build_controls() self._build_selected_panel() self._build_inventory_grid() self.refresh_inventory() # Dynamic panel height for breeding pit self._update_breeding_pit_height() self.root.bind("", lambda e: self._update_breeding_pit_height()) def _configure_styles(self): style = ttk.Style() style.theme_use('default') style.configure("TFrame", background=BLACK) style.configure("TLabel", background=BLACK, foreground=LIME) style.configure("TButton", background=BLACK, foreground=LIME, borderwidth=1) style.map("TButton", background=[('active', BLACK)], foreground=[('active', LIME)]) style.configure("ParentA.TFrame", background="#003300") # Dark green style.configure("ParentB.TFrame", background="#003366") # Dark blue-green def _build_controls(self): control_frame = ttk.Frame(self.root) control_frame.pack(fill=tk.X, pady=4) ttk.Button(control_frame, text="Add Starter Seeds", command=self.add_starter_seeds).pack(side=tk.LEFT, padx=4) ttk.Button(control_frame, text="Clear Inventory", command=self.clear_inventory).pack(side=tk.LEFT, padx=4) self.breed_btn = ttk.Button(control_frame, text="Breed Selected", command=self.breed_selected) self.breed_btn.pack(side=tk.LEFT, padx=4) self.breed_btn.state(["disabled"]) self.clone_btn = ttk.Button(control_frame, text="Clone Selected", command=self.clone_selected) self.clone_btn.pack(side=tk.LEFT, padx=4) self.clone_btn.state(["disabled"]) def _build_selected_panel(self): self.selected_frame = ttk.Frame(self.root) self.selected_frame.pack(fill=tk.BOTH, expand=False, pady=4) self.parent_a_panel = ttk.Frame(self.selected_frame, padding=6, relief=tk.RIDGE, borderwidth=2) self.parent_b_panel = ttk.Frame(self.selected_frame, padding=6, relief=tk.RIDGE, borderwidth=2) self.parent_a_panel.pack(side=tk.LEFT, expand=True, fill=tk.BOTH, padx=10) self.parent_b_panel.pack(side=tk.LEFT, expand=True, fill=tk.BOTH, padx=10) self.parent_a_panel.pack_propagate(False) self.parent_b_panel.pack_propagate(False) def _build_inventory_grid(self): self.canvas = tk.Canvas(self.root, bg=BLACK, highlightthickness=0) self.scrollbar = ttk.Scrollbar(self.root, orient="vertical", command=self.canvas.yview) self.canvas.configure(yscrollcommand=self.scrollbar.set) self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.grid_frame = ttk.Frame(self.canvas) self.canvas.create_window((0, 0), window=self.grid_frame, anchor="nw") self.grid_frame.bind("", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))) self.canvas.bind_all("", self._on_mousewheel) def _on_mousewheel(self, event): self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") def _update_breeding_pit_height(self): total_height = self.root.winfo_height() if total_height <= 1: total_height = 800 # fallback desired_height = int(total_height * 0.5) # 50% self.parent_a_panel.config(height=desired_height) self.parent_b_panel.config(height=desired_height) def refresh_inventory(self): for widget in self.grid_frame.winfo_children(): widget.destroy() seeds = self.catalog.get_all_seeds() cols = 5 for idx, seed in enumerate(seeds): self._render_card(seed).grid(row=idx // cols, column=idx % cols, padx=8, pady=8) self._refresh_selected_panel() # Enable/disable buttons based on attempts and selection can_breed = len(self.selected_seeds) == 2 and all(seed.can_attempt() for seed in self.selected_seeds) can_clone = len(self.selected_seeds) == 1 and self.selected_seeds[0].can_attempt() self.breed_btn.state(["!disabled"] if can_breed else ["disabled"]) self.clone_btn.state(["!disabled"] if can_clone else ["disabled"]) def _render_card(self, seed): frame = ttk.Frame(self.grid_frame, relief=tk.SOLID, borderwidth=2) if seed in self.selected_seeds: idx = self.selected_seeds.index(seed) frame.config(style="ParentA.TFrame" if idx == 0 else "ParentB.TFrame") else: frame.config(style="TFrame") cache_key = ( seed.seed_id, seed.growth_stage.name, seed.color_rgb, tuple(sorted(seed.traits.items())) ) if cache_key not in self.image_cache: self.image_cache[cache_key] = generate_seed_image(seed, seed.growth_stage.name).resize((96, 96), resample=Image.NEAREST) pil_img = self.image_cache[cache_key] tk_img = ImageTk.PhotoImage(pil_img) label = tk.Label(frame, image=tk_img, bg=BLACK) label.image = tk_img label.pack() label.bind("", lambda e, s=seed: self.toggle_seed_selection(s)) tooltip_text = f"{seed.strain_name}\nType: {seed.traits.get('Type', 'Unknown')}\n" for trait in ["THC", "CBD", "Yield", "GrowTime", "ColorScheme"]: val = seed.traits.get(trait, "-") stab = seed.stabilities.get(trait, "-") tooltip_text += f"{trait}: {val} (Stability: {stab})\n" tooltip_text += "\nAlleles:\n" for trait in ["THC", "CBD", "Yield", "GrowTime", "ColorScheme"]: alleles = seed.alleles.get(trait) if alleles: tooltip_text += f"{trait}: {alleles}\n" Tooltip(label, tooltip_text) name_label = ttk.Label(frame, text=seed.strain_name, font=("Consolas", 9)) name_label.pack() if seed.growth_stage != GrowthStage.MATURE: grow_btn = ttk.Button(frame, text="Grow", command=lambda s=seed: self.advance_stage(s)) grow_btn.pack(pady=2) else: status = ttk.Label(frame, text="MATURE") status.pack(pady=2) return frame def _refresh_selected_panel(self): for panel in [self.parent_a_panel, self.parent_b_panel]: # Clear previous content for widget in panel.winfo_children(): widget.destroy() if len(self.selected_seeds) == 0: return panels = [self.parent_a_panel, self.parent_b_panel] for idx, seed in enumerate(self.selected_seeds): if idx >= len(panels): break panel = panels[idx] # Create scrollable frame inside parent panel scrollable_container = ScrollableFrame(panel) scrollable_container.pack(fill="both", expand=True, padx=10, pady=10) # Now add your info panel inside scrollable_container.scrollable_frame self._display_seed_info_panel(scrollable_container.scrollable_frame, seed) self._draw_lineage_tree_canvas(scrollable_container.scrollable_frame, seed) def _display_seed_info_panel(self, panel, seed): container = ttk.Frame(panel) container.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) info_frame = ttk.Frame(container) info_frame.pack(side=tk.LEFT, fill=tk.Y) size = 96 cache_key = (seed.seed_id, GrowthStage.MATURE.name, size) if cache_key not in self.image_cache: pil_img = generate_seed_image(seed, GrowthStage.MATURE.name).resize((size, size), resample=Image.NEAREST) self.image_cache[cache_key] = pil_img else: pil_img = self.image_cache[cache_key] tk_img = ImageTk.PhotoImage(pil_img) img_label = tk.Label(info_frame, image=tk_img, bg=BLACK) img_label.image = tk_img img_label.pack(pady=5) ttk.Label(info_frame, text=seed.strain_name, font=("Consolas", 10, "bold")).pack(anchor=tk.W, pady=(0, 5)) for trait in ["Type", "THC", "CBD", "Yield", "GrowTime"]: val = seed.traits.get(trait, "-") stab = seed.stabilities.get(trait, "-") ttk.Label(info_frame, text=f"{trait}: {val} (Stability: {stab})", font=("Consolas", 9)).pack(anchor=tk.W, pady=1) ttk.Label(info_frame, text="Alleles:", font=("Consolas", 9, "underline")).pack(anchor=tk.W, pady=(6, 3)) for trait in ["THC", "CBD", "Yield", "GrowTime"]: alleles = seed.alleles.get(trait) if alleles: ttk.Label(info_frame, text=f"{trait}: {alleles}", font=("Consolas", 9)).pack(anchor=tk.W, pady=1) def _draw_lineage_tree_canvas(self, panel, seed): if hasattr(panel, '_lineage_canvas'): panel._lineage_canvas.destroy() canvas_width = 250 canvas_height = 160 canvas = tk.Canvas(panel, width=canvas_width, height=canvas_height, bg=BLACK, highlightthickness=0) canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10) panel._lineage_canvas = canvas parent_coords = [(60, 30), (180, 30)] grandparent_coords = [(20, 110), (100, 110), (160, 110), (240, 110)] lineage_ids = seed.lineage if seed.lineage else [None, None] parents = [self.catalog.get_seed_by_id(pid) for pid in lineage_ids] for i, p in enumerate(parents): if p is None: continue self._draw_icon_on_canvas(canvas, p, parent_coords[i], 48) grandparents = [] for p in parents: if p and p.lineage: grandparents.extend([self.catalog.get_seed_by_id(pid) for pid in p.lineage]) for i, gp in enumerate(grandparents): if gp is None: continue self._draw_icon_on_canvas(canvas, gp, grandparent_coords[i], 32) canvas.create_line(parent_coords[0][0]+24, parent_coords[0][1]+48, grandparent_coords[0][0]+16, grandparent_coords[0][1], fill=LIME, width=2) canvas.create_line(parent_coords[0][0]+24, parent_coords[0][1]+48, grandparent_coords[1][0]+16, grandparent_coords[1][1], fill=LIME, width=2) canvas.create_line(parent_coords[1][0]+24, parent_coords[1][1]+48, grandparent_coords[2][0]+16, grandparent_coords[2][1], fill=LIME, width=2) canvas.create_line(parent_coords[1][0]+24, parent_coords[1][1]+48, grandparent_coords[3][0]+16, grandparent_coords[3][1], fill=LIME, width=2) def _draw_icon_on_canvas(self, canvas, seed, position, size): cache_key = (seed.seed_id, GrowthStage.MATURE.name, size) if cache_key not in self.image_cache: pil_img = generate_seed_image(seed, GrowthStage.MATURE.name).resize((size, size), resample=Image.NEAREST) self.image_cache[cache_key] = pil_img else: pil_img = self.image_cache[cache_key] tk_img = ImageTk.PhotoImage(pil_img) if not hasattr(canvas, '_image_refs'): canvas._image_refs = [] canvas._image_refs.append(tk_img) canvas.create_image(position[0], position[1], image=tk_img, anchor='nw') def toggle_seed_selection(self, seed): if seed in self.selected_seeds: self.selected_seeds.remove(seed) elif len(self.selected_seeds) < 2: self.selected_seeds.append(seed) self.refresh_inventory() def advance_stage(self, seed): seed.advance_growth_stage() self.refresh_inventory() def breed_selected(self): if len(self.selected_seeds) == 2: try: offspring = self.breeder.breed(self.selected_seeds[0], self.selected_seeds[1], self.starter_loader.name_generator) self.catalog.add_seed(offspring) self.selected_seeds.clear() self.refresh_inventory() except Exception as e: print(f"Breeding failed: {e}") def clone_selected(self): if len(self.selected_seeds) != 1: return try: clone_seed = self.breeder.clone(self.selected_seeds[0], self.starter_loader.name_generator) self.catalog.add_seed(clone_seed) self.refresh_inventory() except Exception as e: print(f"Clone failed: {e}") def add_starter_seeds(self): self.starter_loader.load_starter_seeds() self.refresh_inventory() def clear_inventory(self): self.catalog.clear() self.selected_seeds.clear() self.refresh_inventory()