From 5d0d4b7d2720503f4ab2d861831e60955d8a14f2 Mon Sep 17 00:00:00 2001 From: Keeper <65701532+fmxs@users.noreply.github.com> Date: Sat, 18 Apr 2026 11:00:56 +0800 Subject: [PATCH] add: right-click of changing skin for desktop pet (#99) * fix: skin icon size * fix: skin icon size * fix: skin icon size * fix: skin icon size * fix: skin icon size * fix: skin icon size * add: right click logic of changing skin --- frontends/desktop_pet_v2.pyw | 50 ++++++++++++++++++++++++++++++ frontends/skins/boy/skin.json | 4 +-- frontends/skins/dinosaur/skin.json | 2 +- frontends/skins/doux/skin.json | 2 +- frontends/skins/line/skin.json | 2 +- frontends/skins/mort/skin.json | 2 +- frontends/skins/tard/skin.json | 2 +- 7 files changed, 57 insertions(+), 7 deletions(-) diff --git a/frontends/desktop_pet_v2.pyw b/frontends/desktop_pet_v2.pyw index 0f082a7..744f513 100644 --- a/frontends/desktop_pet_v2.pyw +++ b/frontends/desktop_pet_v2.pyw @@ -308,6 +308,7 @@ if sys.platform == 'darwin': # Load skin self.load_skin(skin_name) + self.available_skins = SkinLoader.list_skins() # Get screen size from AppKit import NSScreen, NSWindowCollectionBehaviorCanJoinAllSpaces, NSWindowCollectionBehaviorStationary @@ -393,6 +394,29 @@ if sys.platform == 'darwin': """Accept first mouse click""" return True + def rightMouseDown_(self, event): + from AppKit import NSMenu, NSMenuItem, NSApp + + menu = NSMenu.alloc().init() + pet = self.window().delegate() # Assuming the window’s delegate is MacPet instance + + for skin_name in pet.available_skins: # preload this in MacPet.__init__ + item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_( + skin_name, + 'changeSkin:', + '' + ) + item.setTarget_(pet) + item.setRepresentedObject_(skin_name) + menu.addItem_(item) + + menu.addItem_(NSMenuItem.separatorItem()) + quit_item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Quit', 'terminate:', '') + menu.addItem_(quit_item) + + NSApp.activateIgnoringOtherApps_(True) + NSMenu.popUpContextMenu_withEvent_forView_(menu, event, self) + # Create draggable view self.content_view = DraggableImageView.alloc().initWithFrame_( NSMakeRect(0, 0, self.display_width, self.display_height) @@ -576,6 +600,13 @@ if sys.platform == 'darwin': def run(self): """Run the application""" AppHelper.runEventLoop() + + def changeSkin_(self, sender): + skin_name = sender.representedObject() + print(f"Changing skin to: {skin_name}") + self.load_skin(skin_name) + self.current_state = 'idle' + self.frame_idx = 0 # ============================================================================ # Windows Implementation - tkinter with transparentcolor @@ -615,6 +646,7 @@ else: self.label.bind('', lambda e: setattr(self, '_d', (e.x, e.y))) self.label.bind('', self._drag) self.label.bind('', lambda e: (self.root.destroy(), os._exit(0))) + self.label.bind('', self._on_right_click) # Animation state self.current_state = 'idle' @@ -754,6 +786,24 @@ else: def run(self): """Run the application (already in mainloop)""" pass + + def _on_right_click(self, event): + # Build a dynamic menu of all available skins + menu = tk.Menu(self.root, tearoff=0) + for skin_name in SkinLoader.list_skins(): + menu.add_command( + label=skin_name, + command=lambda name=skin_name: self._change_skin(name) + ) + menu.add_separator() + menu.add_command(label="Quit", command=lambda: (self.root.destroy(), os._exit(0))) + menu.tk_popup(event.x_root, event.y_root) + + def _change_skin(self, skin_name): + print(f"Changing skin to: {skin_name}") + self.load_skin(skin_name) + self.current_state = 'idle' + self.frame_idx = 0 if __name__ == '__main__': # Singleton: if port already in use, another instance is running diff --git a/frontends/skins/boy/skin.json b/frontends/skins/boy/skin.json index 498a182..9a116cb 100644 --- a/frontends/skins/boy/skin.json +++ b/frontends/skins/boy/skin.json @@ -7,8 +7,8 @@ "style": "pixel", "format": "sprite", "size": { - "width": 40, - "height": 61 + "width": 80, + "height": 122 }, "animations": { "idle": { diff --git a/frontends/skins/dinosaur/skin.json b/frontends/skins/dinosaur/skin.json index 8d41f44..c961b0e 100644 --- a/frontends/skins/dinosaur/skin.json +++ b/frontends/skins/dinosaur/skin.json @@ -6,7 +6,7 @@ "description": "像素风小恐龙 Dinosaur", "style": "pixel", "format": "sprite", - "size": { "width": 64, "height": 64 }, + "size": { "width": 128, "height": 128 }, "animations": { "idle": { "file": "skin.png", diff --git a/frontends/skins/doux/skin.json b/frontends/skins/doux/skin.json index 4b77cdd..f084745 100644 --- a/frontends/skins/doux/skin.json +++ b/frontends/skins/doux/skin.json @@ -7,7 +7,7 @@ "description": "像素风小恐龙 Doux", "style": "pixel", "format": "sprite", - "size": { "width": 48, "height": 48 }, + "size": { "width": 128, "height": 128 }, "animations": { "idle": { "file": "skin.png", diff --git a/frontends/skins/line/skin.json b/frontends/skins/line/skin.json index 0b42e10..09d1fdc 100644 --- a/frontends/skins/line/skin.json +++ b/frontends/skins/line/skin.json @@ -6,7 +6,7 @@ "description": "Line 角色皮肤", "style": "pixel", "format": "sprite", - "size": { "width": 39, "height": 46 }, + "size": { "width": 128, "height": 128 }, "animations": { "idle": { "file": "skin.png", diff --git a/frontends/skins/mort/skin.json b/frontends/skins/mort/skin.json index 96483eb..f3a031c 100644 --- a/frontends/skins/mort/skin.json +++ b/frontends/skins/mort/skin.json @@ -7,7 +7,7 @@ "description": "像素风小恐龙 Mort", "style": "pixel", "format": "sprite", - "size": { "width": 48, "height": 48 }, + "size": { "width": 128, "height": 128 }, "animations": { "idle": { "file": "skin.png", diff --git a/frontends/skins/tard/skin.json b/frontends/skins/tard/skin.json index 3cb064a..92c6304 100644 --- a/frontends/skins/tard/skin.json +++ b/frontends/skins/tard/skin.json @@ -7,7 +7,7 @@ "description": "像素风小恐龙 Tard", "style": "pixel", "format": "sprite", - "size": { "width": 48, "height": 48 }, + "size": { "width": 128, "height": 128 }, "animations": { "idle": { "file": "skin.png",