Merge branch 'mouse_interaction' into 'master'
Mouse interaction Closes #40 See merge request ynerant/squirrel-battle!45
This commit is contained in:
commit
81de0d8eb0
@ -5,6 +5,7 @@ import curses
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from squirrelbattle.display.texturepack import TexturePack
|
||||
from squirrelbattle.game import Game
|
||||
from squirrelbattle.tests.screen import FakePad
|
||||
|
||||
|
||||
@ -86,6 +87,13 @@ class Display:
|
||||
def display(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def handle_click(self, y: int, x: int, game: Game) -> None:
|
||||
"""
|
||||
A mouse click was performed on the coordinates (y, x) of the pad.
|
||||
Maybe it can do something.
|
||||
"""
|
||||
pass
|
||||
|
||||
@property
|
||||
def rows(self) -> int:
|
||||
return curses.LINES if self.screen else 42
|
||||
|
@ -2,7 +2,8 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import curses
|
||||
from squirrelbattle.display.display import VerticalSplit, HorizontalSplit
|
||||
from squirrelbattle.display.display import VerticalSplit, HorizontalSplit, \
|
||||
Display
|
||||
from squirrelbattle.display.mapdisplay import MapDisplay
|
||||
from squirrelbattle.display.messagedisplay import MessageDisplay
|
||||
from squirrelbattle.display.statsdisplay import StatsDisplay
|
||||
@ -10,7 +11,7 @@ from squirrelbattle.display.menudisplay import MainMenuDisplay, \
|
||||
PlayerInventoryDisplay, StoreInventoryDisplay, SettingsMenuDisplay
|
||||
from squirrelbattle.display.logsdisplay import LogsDisplay
|
||||
from squirrelbattle.display.texturepack import TexturePack
|
||||
from typing import Any
|
||||
from typing import Any, List
|
||||
from squirrelbattle.game import Game, GameMode
|
||||
from squirrelbattle.enums import DisplayActions
|
||||
|
||||
@ -39,11 +40,13 @@ class DisplayManager:
|
||||
self.storeinventorydisplay]
|
||||
self.update_game_components()
|
||||
|
||||
def handle_display_action(self, action: DisplayActions) -> None:
|
||||
def handle_display_action(self, action: DisplayActions, *params) -> None:
|
||||
if action == DisplayActions.REFRESH:
|
||||
self.refresh()
|
||||
elif action == DisplayActions.UPDATE:
|
||||
self.update_game_components()
|
||||
elif action == DisplayActions.MOUSE:
|
||||
self.handle_mouse_click(*params)
|
||||
|
||||
def update_game_components(self) -> None:
|
||||
for d in self.displays:
|
||||
@ -58,7 +61,21 @@ class DisplayManager:
|
||||
self.logsdisplay.update_logs(self.game.logs)
|
||||
self.messagedisplay.update_message(self.game.message)
|
||||
|
||||
def refresh(self) -> None:
|
||||
def handle_mouse_click(self, y: int, x: int) -> None:
|
||||
displays = self.refresh()
|
||||
display = None
|
||||
for d in displays:
|
||||
top_y, top_x, height, width = d.y, d.x, d.height, d.width
|
||||
if top_y <= y < top_y + height and top_x <= x < top_x + width:
|
||||
# The click coordinates correspond to the coordinates
|
||||
# of that display
|
||||
display = d
|
||||
if display:
|
||||
display.handle_click(y - display.y, x - display.x, self.game)
|
||||
|
||||
def refresh(self) -> List[Display]:
|
||||
displays = []
|
||||
|
||||
if self.game.state == GameMode.PLAY \
|
||||
or self.game.state == GameMode.INVENTORY \
|
||||
or self.game.state == GameMode.STORE:
|
||||
@ -74,18 +91,26 @@ class DisplayManager:
|
||||
self.rows // 5 - 1, self.cols * 4 // 5)
|
||||
self.hbar.refresh(self.rows * 4 // 5, 0, 1, self.cols * 4 // 5)
|
||||
self.vbar.refresh(0, self.cols * 4 // 5, self.rows, 1)
|
||||
|
||||
displays += [self.mapdisplay, self.statsdisplay, self.logsdisplay,
|
||||
self.hbar, self.vbar]
|
||||
|
||||
if self.game.state == GameMode.INVENTORY:
|
||||
self.playerinventorydisplay.refresh(
|
||||
self.rows // 10, self.cols // 2,
|
||||
8 * self.rows // 10, 2 * self.cols // 5)
|
||||
displays.append(self.playerinventorydisplay)
|
||||
elif self.game.state == GameMode.STORE:
|
||||
self.storeinventorydisplay.refresh(
|
||||
self.rows // 10, self.cols // 2,
|
||||
8 * self.rows // 10, 2 * self.cols // 5)
|
||||
displays.append(self.storeinventorydisplay)
|
||||
elif self.game.state == GameMode.MAINMENU:
|
||||
self.mainmenudisplay.refresh(0, 0, self.rows, self.cols)
|
||||
displays.append(self.mainmenudisplay)
|
||||
elif self.game.state == GameMode.SETTINGS:
|
||||
self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols)
|
||||
displays.append(self.settingsmenudisplay)
|
||||
|
||||
if self.game.message:
|
||||
height, width = 0, 0
|
||||
@ -94,9 +119,12 @@ class DisplayManager:
|
||||
width = max(width, len(line))
|
||||
y, x = (self.rows - height) // 2, (self.cols - width) // 2
|
||||
self.messagedisplay.refresh(y, x, height, width)
|
||||
displays.append(self.messagedisplay)
|
||||
|
||||
self.resize_window()
|
||||
|
||||
return displays
|
||||
|
||||
def resize_window(self) -> bool:
|
||||
"""
|
||||
If the window got resized, ensure that the screen size got updated.
|
||||
|
@ -1,10 +1,13 @@
|
||||
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import curses
|
||||
from typing import List
|
||||
|
||||
from squirrelbattle.menus import Menu, MainMenu
|
||||
from .display import Display, Box
|
||||
from ..enums import KeyValues
|
||||
from ..game import Game
|
||||
from ..resources import ResourceManager
|
||||
from ..translations import gettext as _
|
||||
|
||||
@ -44,6 +47,13 @@ class MenuDisplay(Display):
|
||||
self.height - 2 + self.y,
|
||||
self.width - 2 + self.x)
|
||||
|
||||
def handle_click(self, y: int, x: int, game: Game) -> None:
|
||||
"""
|
||||
We can select a menu item with the mouse.
|
||||
"""
|
||||
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 1))
|
||||
game.handle_key_pressed(KeyValues.ENTER)
|
||||
|
||||
@property
|
||||
def truewidth(self) -> int:
|
||||
return max([len(str(a)) for a in self.values])
|
||||
@ -108,6 +118,14 @@ class MainMenuDisplay(Display):
|
||||
menuy, menux, min(self.menudisplay.preferred_height,
|
||||
self.height - menuy), menuwidth)
|
||||
|
||||
def handle_click(self, y: int, x: int, game: Game) -> None:
|
||||
menuwidth = min(self.menudisplay.preferred_width, self.width)
|
||||
menuy, menux = len(self.title) + 8, self.width // 2 - menuwidth // 2 - 1
|
||||
menuheight = min(self.menudisplay.preferred_height, self.height - menuy)
|
||||
|
||||
if menuy <= y < menuy + menuheight and menux <= x < menux + menuwidth:
|
||||
self.menudisplay.handle_click(y - menuy, x - menux, game)
|
||||
|
||||
|
||||
class PlayerInventoryDisplay(MenuDisplay):
|
||||
message = _("== INVENTORY ==")
|
||||
@ -129,6 +147,13 @@ class PlayerInventoryDisplay(MenuDisplay):
|
||||
def trueheight(self) -> int:
|
||||
return 2 + super().trueheight
|
||||
|
||||
def handle_click(self, y: int, x: int, game: Game) -> None:
|
||||
"""
|
||||
We can select a menu item with the mouse.
|
||||
"""
|
||||
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 3))
|
||||
game.handle_key_pressed(KeyValues.ENTER)
|
||||
|
||||
|
||||
class StoreInventoryDisplay(MenuDisplay):
|
||||
message = _("== STALL ==")
|
||||
@ -150,3 +175,10 @@ class StoreInventoryDisplay(MenuDisplay):
|
||||
@property
|
||||
def trueheight(self) -> int:
|
||||
return 2 + super().trueheight
|
||||
|
||||
def handle_click(self, y: int, x: int, game: Game) -> None:
|
||||
"""
|
||||
We can select a menu item with the mouse.
|
||||
"""
|
||||
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 3))
|
||||
game.handle_key_pressed(KeyValues.ENTER)
|
||||
|
@ -31,7 +31,7 @@ class Item(Entity):
|
||||
"""
|
||||
if self.held:
|
||||
self.held_by.inventory.remove(self)
|
||||
self.map.add_entity(self)
|
||||
self.held_by.map.add_entity(self)
|
||||
self.move(self.held_by.y, self.held_by.x)
|
||||
self.held = False
|
||||
self.held_by = None
|
||||
|
@ -16,6 +16,7 @@ class DisplayActions(Enum):
|
||||
"""
|
||||
REFRESH = auto()
|
||||
UPDATE = auto()
|
||||
MOUSE = auto()
|
||||
|
||||
|
||||
class GameMode(Enum):
|
||||
@ -33,6 +34,7 @@ class KeyValues(Enum):
|
||||
"""
|
||||
Key values options used in the game
|
||||
"""
|
||||
MOUSE = auto()
|
||||
UP = auto()
|
||||
DOWN = auto()
|
||||
LEFT = auto()
|
||||
|
@ -4,6 +4,7 @@
|
||||
from json import JSONDecodeError
|
||||
from random import randint
|
||||
from typing import Any, Optional
|
||||
import curses
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
@ -15,7 +16,6 @@ from .resources import ResourceManager
|
||||
from .settings import Settings
|
||||
from . import menus
|
||||
from .translations import gettext as _, Translator
|
||||
from typing import Callable
|
||||
|
||||
|
||||
class Game:
|
||||
@ -26,7 +26,7 @@ class Game:
|
||||
player: Player
|
||||
screen: Any
|
||||
# display_actions is a display interface set by the bootstrapper
|
||||
display_actions: Callable[[DisplayActions], None]
|
||||
display_actions: callable
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
@ -71,8 +71,12 @@ class Game:
|
||||
screen.refresh()
|
||||
self.display_actions(DisplayActions.REFRESH)
|
||||
key = screen.getkey()
|
||||
self.handle_key_pressed(
|
||||
KeyValues.translate_key(key, self.settings), key)
|
||||
if key == "KEY_MOUSE":
|
||||
_ignored1, x, y, _ignored2, _ignored3 = curses.getmouse()
|
||||
self.display_actions(DisplayActions.MOUSE, y, x)
|
||||
else:
|
||||
self.handle_key_pressed(
|
||||
KeyValues.translate_key(key, self.settings), key)
|
||||
|
||||
def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str = '')\
|
||||
-> None:
|
||||
|
@ -20,6 +20,8 @@ class TermManager: # pragma: no cover
|
||||
curses.cbreak()
|
||||
# make cursor invisible
|
||||
curses.curs_set(False)
|
||||
# Catch mouse events
|
||||
curses.mousemask(True)
|
||||
# Enable colors
|
||||
curses.start_color()
|
||||
|
||||
|
@ -230,6 +230,33 @@ class TestGame(unittest.TestCase):
|
||||
self.game.handle_key_pressed(KeyValues.SPACE)
|
||||
self.assertEqual(self.game.state, GameMode.MAINMENU)
|
||||
|
||||
def test_mouse_click(self) -> None:
|
||||
"""
|
||||
Simulate mouse clicks.
|
||||
"""
|
||||
self.game.state = GameMode.MAINMENU
|
||||
|
||||
# Settings menu
|
||||
self.game.display_actions(DisplayActions.MOUSE, 25, 21)
|
||||
self.assertEqual(self.game.main_menu.position, 4)
|
||||
self.assertEqual(self.game.state, GameMode.SETTINGS)
|
||||
|
||||
bomb = Bomb()
|
||||
bomb.hold(self.game.player)
|
||||
bomb2 = Bomb()
|
||||
bomb2.hold(self.game.player)
|
||||
|
||||
self.game.state = GameMode.INVENTORY
|
||||
|
||||
# Click nowhere
|
||||
self.game.display_actions(DisplayActions.MOUSE, 0, 0)
|
||||
self.assertEqual(self.game.state, GameMode.INVENTORY)
|
||||
|
||||
# Click on the second item
|
||||
self.game.display_actions(DisplayActions.MOUSE, 8, 25)
|
||||
self.assertEqual(self.game.state, GameMode.INVENTORY)
|
||||
self.assertEqual(self.game.inventory_menu.position, 1)
|
||||
|
||||
def test_new_game(self) -> None:
|
||||
"""
|
||||
Ensure that the start button starts a new game.
|
||||
@ -512,9 +539,10 @@ class TestGame(unittest.TestCase):
|
||||
|
||||
# The second item is not a heart
|
||||
merchant.inventory[1] = Sword()
|
||||
# Buy the second item
|
||||
# Buy the second item by clicking on it
|
||||
item = self.game.store_menu.validate()
|
||||
self.assertIn(item, merchant.inventory)
|
||||
self.game.display_actions(DisplayActions.MOUSE, 8, 25)
|
||||
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||
self.assertIn(item, self.game.player.inventory)
|
||||
self.assertNotIn(item, merchant.inventory)
|
||||
|
Loading…
Reference in New Issue
Block a user