Replace the plain-text shell dropdown/list with an icon picker
Both places the app asks for a shell (the Add Strike dialog's Adw.ComboRow, and the firing panel's per-target shell popover) used to show just the bare enum name in a plain list, nothing conveying what the shell actually is. New icons.py builds a shared MenuButton + popover from the game's own shell icon set (assets/icons/shells/, already named to match Shell.name exactly): each row shows the shell's icon, name, description, and blast radius, and the button face updates to match whichever one gets picked. Verified via a GTK smoke test that the button and its popover build and open without crashing.
This commit is contained in:
parent
5077cf4d14
commit
2b73a96c7a
@ -24,7 +24,7 @@ gi.require_version("Gdk", "4.0")
|
||||
from gi.repository import Adw, Gdk, Gio, GLib, Gtk # noqa: E402
|
||||
from PIL import Image # noqa: E402
|
||||
|
||||
from . import ballistics, ocr, solver # noqa: E402
|
||||
from . import ballistics, icons, ocr, solver # noqa: E402
|
||||
from .coord_dialog import CoordDialog # noqa: E402
|
||||
from .firing_panel import FiringPanel # noqa: E402
|
||||
from .grid_widget import GridCanvas # noqa: E402
|
||||
@ -972,12 +972,14 @@ class MainWindow(Adw.ApplicationWindow):
|
||||
|
||||
outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=16,
|
||||
margin_top=16, margin_bottom=16, margin_start=16, margin_end=16)
|
||||
group = Adw.PreferencesGroup(title="Shell (blast radius)")
|
||||
shells = list(Shell)
|
||||
shell_row = Adw.ComboRow(title="Shell", model=Gtk.StringList.new([s.name for s in shells]))
|
||||
shell_row.set_selected(shells.index(Shell.HCHE))
|
||||
group.add(shell_row)
|
||||
outer.append(group)
|
||||
label = Gtk.Label(label="Shell (blast radius)", xalign=0)
|
||||
label.add_css_class("heading")
|
||||
outer.append(label)
|
||||
|
||||
chosen = [Shell.HCHE]
|
||||
shell_btn = icons.build_shell_button(chosen[0], lambda s: chosen.__setitem__(0, s))
|
||||
shell_btn.set_halign(Gtk.Align.START)
|
||||
outer.append(shell_btn)
|
||||
|
||||
next_btn = Gtk.Button(label="Next: click the map to place it")
|
||||
next_btn.add_css_class("suggested-action")
|
||||
@ -985,7 +987,7 @@ class MainWindow(Adw.ApplicationWindow):
|
||||
next_btn.set_halign(Gtk.Align.CENTER)
|
||||
|
||||
def on_next(_b):
|
||||
chosen_shell = shells[shell_row.get_selected()]
|
||||
chosen_shell = chosen[0]
|
||||
dialog.close()
|
||||
self.canvas.start_placement(
|
||||
lambda coord: self._add_strike_at(coord, chosen_shell),
|
||||
|
||||
@ -29,7 +29,7 @@ gi.require_version("Gtk", "4.0")
|
||||
gi.require_version("Gdk", "4.0")
|
||||
from gi.repository import Gdk, GObject, Gtk # noqa: E402
|
||||
|
||||
from . import ballistics
|
||||
from . import ballistics, icons
|
||||
from .models import Board, Target, TargetType
|
||||
from .shells import Shell
|
||||
|
||||
@ -381,21 +381,8 @@ class FiringPanel(Gtk.Box):
|
||||
charges: int, elev_value_label: Gtk.Label) -> Gtk.Widget:
|
||||
row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4)
|
||||
|
||||
shell_btn = Gtk.MenuButton(label=target.effective_shell.name)
|
||||
shell_btn.add_css_class("flat")
|
||||
shell_btn = icons.build_shell_button(target.effective_shell, lambda s: self._pick_shell(target, s))
|
||||
shell_btn.set_tooltip_text("Change shell (blast radius)")
|
||||
popover = Gtk.Popover()
|
||||
shell_list = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2,
|
||||
margin_top=6, margin_bottom=6, margin_start=6, margin_end=6)
|
||||
for s in Shell:
|
||||
radius = f"{s.blast_radius_km}km" if s.blast_radius_km is not None else "unknown radius"
|
||||
btn = Gtk.Button(label=f"{s.name}: {s.description} ({radius})")
|
||||
btn.add_css_class("flat")
|
||||
btn.get_child().set_xalign(0)
|
||||
btn.connect("clicked", lambda _b, s=s: self._pick_shell(target, s, popover))
|
||||
shell_list.append(btn)
|
||||
popover.set_child(shell_list)
|
||||
shell_btn.set_popover(popover)
|
||||
row.append(shell_btn)
|
||||
|
||||
segments: list[Gtk.Button] = []
|
||||
@ -437,7 +424,6 @@ class FiringPanel(Gtk.Box):
|
||||
target.alive = not target.alive
|
||||
self.on_change()
|
||||
|
||||
def _pick_shell(self, target: Target, shell: Shell, popover: Gtk.Popover) -> None:
|
||||
def _pick_shell(self, target: Target, shell: Shell) -> None:
|
||||
target.shell = shell
|
||||
popover.popdown()
|
||||
self.on_change()
|
||||
|
||||
98
src/fenigma/icons.py
Normal file
98
src/fenigma/icons.py
Normal file
@ -0,0 +1,98 @@
|
||||
"""Bundled game-icon lookup and the shared shell picker built from them.
|
||||
|
||||
Icon files live in the repo's assets/icons/ (see its README.md for where
|
||||
they came from and which source-filename typos got corrected on copy),
|
||||
outside the src/ package, resolved relative to this file rather than the
|
||||
process's cwd so it works no matter where the app was launched from.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("GdkPixbuf", "2.0")
|
||||
gi.require_version("Gtk", "4.0")
|
||||
from gi.repository import GdkPixbuf, Gtk # noqa: E402
|
||||
|
||||
from .shells import Shell
|
||||
|
||||
_ICONS_DIR = Path(__file__).resolve().parent.parent.parent / "assets" / "icons"
|
||||
|
||||
|
||||
def shell_icon_path(shell_name: str) -> Path:
|
||||
return _ICONS_DIR / "shells" / f"{shell_name}.png"
|
||||
|
||||
|
||||
def shell_icon_image(shell_name: str, width: int = 64) -> Gtk.Image:
|
||||
"""A Gtk.Image for a Shell enum member's icon, scaled to `width` px
|
||||
wide (the source art is a fixed 2:1 rectangle, height follows).
|
||||
Falls back to a generic missing-image icon rather than raising, a
|
||||
gap in the icon set shouldn't crash the shell picker."""
|
||||
path = shell_icon_path(shell_name)
|
||||
if path.exists():
|
||||
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(str(path), width, width // 2, False)
|
||||
return Gtk.Image.new_from_pixbuf(pixbuf)
|
||||
image = Gtk.Image.new_from_icon_name("image-missing-symbolic")
|
||||
image.set_pixel_size(width // 2)
|
||||
return image
|
||||
|
||||
|
||||
def _shell_row(s: Shell) -> Gtk.Widget:
|
||||
radius = f"{s.blast_radius_km}km blast radius" if s.blast_radius_km is not None else "blast radius unknown"
|
||||
content = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
||||
content.append(shell_icon_image(s.name, width=56))
|
||||
label_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0, hexpand=True)
|
||||
name_label = Gtk.Label(label=s.name, xalign=0)
|
||||
name_label.add_css_class("heading")
|
||||
desc_label = Gtk.Label(label=f"{s.description} · {radius}", xalign=0, wrap=True)
|
||||
desc_label.add_css_class("caption")
|
||||
desc_label.add_css_class("dim-label")
|
||||
label_box.append(name_label)
|
||||
label_box.append(desc_label)
|
||||
content.append(label_box)
|
||||
return content
|
||||
|
||||
|
||||
def build_shell_popover(on_pick) -> Gtk.Popover:
|
||||
"""Popover listing every Shell with its icon, name, description, and
|
||||
blast radius, replacing a plain text dropdown/list with something
|
||||
that actually shows what each shell looks like. Calls `on_pick(shell)`
|
||||
and closes itself when a row is clicked."""
|
||||
popover = Gtk.Popover()
|
||||
scroller = Gtk.ScrolledWindow(max_content_height=420, propagate_natural_height=True)
|
||||
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2,
|
||||
margin_top=6, margin_bottom=6, margin_start=6, margin_end=6)
|
||||
for s in Shell:
|
||||
row = Gtk.Button(child=_shell_row(s))
|
||||
row.add_css_class("flat")
|
||||
row.connect("clicked", lambda _b, s=s: (popover.popdown(), on_pick(s)))
|
||||
box.append(row)
|
||||
scroller.set_child(box)
|
||||
popover.set_child(scroller)
|
||||
return popover
|
||||
|
||||
|
||||
def build_shell_button(selected: Shell, on_pick) -> Gtk.MenuButton:
|
||||
"""A flat MenuButton showing the currently selected shell's icon and
|
||||
name, opening build_shell_popover() to change it. `on_pick(shell)`
|
||||
fires on selection, after this button's own face has already been
|
||||
updated to match, the caller only needs to react to the new value
|
||||
(persist it, refresh dependents), not maintain the button's display."""
|
||||
btn = Gtk.MenuButton()
|
||||
btn.add_css_class("flat")
|
||||
|
||||
def render(s: Shell) -> None:
|
||||
content = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
|
||||
content.append(shell_icon_image(s.name, width=28))
|
||||
content.append(Gtk.Label(label=s.name))
|
||||
btn.set_child(content)
|
||||
|
||||
def handle_pick(s: Shell) -> None:
|
||||
render(s)
|
||||
on_pick(s)
|
||||
|
||||
render(selected)
|
||||
btn.set_popover(build_shell_popover(handle_pick))
|
||||
return btn
|
||||
Loading…
Reference in New Issue
Block a user