Higher abstraction level on addmsg, fixes #43
This commit is contained in:
		| @@ -2,7 +2,7 @@ | |||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
| import curses | import curses | ||||||
| from typing import Any, Optional, Union | from typing import Any, Optional, Tuple, Union | ||||||
|  |  | ||||||
| from squirrelbattle.display.texturepack import TexturePack | from squirrelbattle.display.texturepack import TexturePack | ||||||
| from squirrelbattle.game import Game | from squirrelbattle.game import Game | ||||||
| @@ -16,6 +16,9 @@ class Display: | |||||||
|     height: int |     height: int | ||||||
|     pad: Any |     pad: Any | ||||||
|  |  | ||||||
|  |     _color_pairs = {(curses.COLOR_WHITE, curses.COLOR_BLACK): 0} | ||||||
|  |     _colors_rgb = {} | ||||||
|  |  | ||||||
|     def __init__(self, screen: Any, pack: Optional[TexturePack] = None): |     def __init__(self, screen: Any, pack: Optional[TexturePack] = None): | ||||||
|         self.screen = screen |         self.screen = screen | ||||||
|         self.pack = pack or TexturePack.get_pack("ascii") |         self.pack = pack or TexturePack.get_pack("ascii") | ||||||
| @@ -31,15 +34,84 @@ class Display: | |||||||
|         lines = [line[:width] for line in lines] |         lines = [line[:width] for line in lines] | ||||||
|         return "\n".join(lines) |         return "\n".join(lines) | ||||||
|  |  | ||||||
|     def addstr(self, pad: Any, y: int, x: int, msg: str, *options) -> None: |     def translate_color(self, color: Union[int, Tuple[int, int, int]]) -> int: | ||||||
|  |         """ | ||||||
|  |         Translate a tuple (R, G, B) into a curses color index. | ||||||
|  |         If we have already a color index, then nothing is processed. | ||||||
|  |         If this is a tuple, we construct a new color index if non-existing | ||||||
|  |         and we return this index. | ||||||
|  |         The values of R, G and B must be between 0 and 1000, and not | ||||||
|  |         between 0 and 255. | ||||||
|  |         """ | ||||||
|  |         if isinstance(color, tuple): | ||||||
|  |             # The color is a tuple (R, G, B), that is potentially unknown. | ||||||
|  |             # We translate it into a curses color number. | ||||||
|  |             if color not in self._colors_rgb: | ||||||
|  |                 # The color does not exist, we create it. | ||||||
|  |                 color_nb = len(self._colors_rgb) + 8 | ||||||
|  |                 self.init_color(color_nb, color[0], color[1], color[2]) | ||||||
|  |                 self._colors_rgb[color] = color_nb | ||||||
|  |             color = self._colors_rgb[color] | ||||||
|  |         return color | ||||||
|  |  | ||||||
|  |     def addstr(self, pad: Any, y: int, x: int, msg: str, | ||||||
|  |                fg_color: Union[int, Tuple[int, int, int]] = curses.COLOR_WHITE, | ||||||
|  |                bg_color: Union[int, Tuple[int, int, int]] = curses.COLOR_BLACK, | ||||||
|  |                *, altcharset: bool = False, blink: bool = False, | ||||||
|  |                bold: bool = False, dim: bool = False, invis: bool = False, | ||||||
|  |                italic: bool = False, normal: bool = False, | ||||||
|  |                protect: bool = False, reverse: bool = False, | ||||||
|  |                standout: bool = False, underline: bool = False, | ||||||
|  |                horizontal: bool = False, left: bool = False, | ||||||
|  |                low: bool = False, right: bool = False, top: bool = False, | ||||||
|  |                vertical: bool = False, chartext: bool = False) -> None: | ||||||
|         """ |         """ | ||||||
|         Display a message onto the pad. |         Display a message onto the pad. | ||||||
|         If the message is too large, it is truncated vertically and horizontally |         If the message is too large, it is truncated vertically and horizontally | ||||||
|  |         The text can be bold, italic, blinking, ... if the good parameters are | ||||||
|  |         given. These parameters are translated into curses attributes. | ||||||
|  |         The foreground and background colors can be given as curses constants | ||||||
|  |         (curses.COLOR_*), or by giving a tuple (R, G, B) that corresponds to | ||||||
|  |         the color. R, G, B must be between 0 and 1000, and not 0 and 255. | ||||||
|         """ |         """ | ||||||
|         height, width = pad.getmaxyx() |         height, width = pad.getmaxyx() | ||||||
|  |         # Truncate message if it is too large | ||||||
|         msg = self.truncate(msg, height - y, width - x - 1) |         msg = self.truncate(msg, height - y, width - x - 1) | ||||||
|         if msg.replace("\n", "") and x >= 0 and y >= 0: |         if msg.replace("\n", "") and x >= 0 and y >= 0: | ||||||
|             return pad.addstr(y, x, msg, *options) |             fg_color = self.translate_color(fg_color) | ||||||
|  |             bg_color = self.translate_color(bg_color) | ||||||
|  |  | ||||||
|  |             # Get the pair number for the tuple (fg, bg) | ||||||
|  |             # If it does not exist, create it and give a new unique id. | ||||||
|  |             if (fg_color, bg_color) in self._color_pairs: | ||||||
|  |                 pair_nb = self._color_pairs[(fg_color, bg_color)] | ||||||
|  |             else: | ||||||
|  |                 pair_nb = len(self._color_pairs) | ||||||
|  |                 self.init_pair(pair_nb, fg_color, bg_color) | ||||||
|  |                 self._color_pairs[(fg_color, bg_color)] = pair_nb | ||||||
|  |  | ||||||
|  |             # Compute curses attributes from the parameters | ||||||
|  |             attr = self.color_pair(pair_nb) | ||||||
|  |             attr |= curses.A_ALTCHARSET if altcharset else 0 | ||||||
|  |             attr |= curses.A_BLINK if blink else 0 | ||||||
|  |             attr |= curses.A_BOLD if bold else 0 | ||||||
|  |             attr |= curses.A_DIM if dim else 0 | ||||||
|  |             attr |= curses.A_INVIS if invis else 0 | ||||||
|  |             attr |= curses.A_ITALIC if italic else 0 | ||||||
|  |             attr |= curses.A_NORMAL if normal else 0 | ||||||
|  |             attr |= curses.A_PROTECT if protect else 0 | ||||||
|  |             attr |= curses.A_REVERSE if reverse else 0 | ||||||
|  |             attr |= curses.A_STANDOUT if standout else 0 | ||||||
|  |             attr |= curses.A_UNDERLINE if underline else 0 | ||||||
|  |             attr |= curses.A_HORIZONTAL if horizontal else 0 | ||||||
|  |             attr |= curses.A_LEFT if left else 0 | ||||||
|  |             attr |= curses.A_LOW if low else 0 | ||||||
|  |             attr |= curses.A_RIGHT if right else 0 | ||||||
|  |             attr |= curses.A_TOP if top else 0 | ||||||
|  |             attr |= curses.A_VERTICAL if vertical else 0 | ||||||
|  |             attr |= curses.A_CHARTEXT if chartext else 0 | ||||||
|  |  | ||||||
|  |             return pad.addstr(y, x, msg, attr) | ||||||
|  |  | ||||||
|     def init_pair(self, number: int, foreground: int, background: int) -> None: |     def init_pair(self, number: int, foreground: int, background: int) -> None: | ||||||
|         return curses.init_pair(number, foreground, background) \ |         return curses.init_pair(number, foreground, background) \ | ||||||
| @@ -156,17 +228,13 @@ class Box(Display): | |||||||
|         self.pad = self.newpad(self.rows, self.cols) |         self.pad = self.newpad(self.rows, self.cols) | ||||||
|         self.fg_border_color = fg_border_color or curses.COLOR_WHITE |         self.fg_border_color = fg_border_color or curses.COLOR_WHITE | ||||||
|  |  | ||||||
|         pair_number = 4 + self.fg_border_color |  | ||||||
|         self.init_pair(pair_number, self.fg_border_color, curses.COLOR_BLACK) |  | ||||||
|         self.pair = self.color_pair(pair_number) |  | ||||||
|  |  | ||||||
|     def display(self) -> None: |     def display(self) -> None: | ||||||
|         self.addstr(self.pad, 0, 0, "┏" + "━" * (self.width - 2) + "┓", |         self.addstr(self.pad, 0, 0, "┏" + "━" * (self.width - 2) + "┓", | ||||||
|                     self.pair) |                     self.fg_border_color) | ||||||
|         for i in range(1, self.height - 1): |         for i in range(1, self.height - 1): | ||||||
|             self.addstr(self.pad, i, 0, "┃", self.pair) |             self.addstr(self.pad, i, 0, "┃", self.fg_border_color) | ||||||
|             self.addstr(self.pad, i, self.width - 1, "┃", self.pair) |             self.addstr(self.pad, i, self.width - 1, "┃", self.fg_border_color) | ||||||
|         self.addstr(self.pad, self.height - 1, 0, |         self.addstr(self.pad, self.height - 1, 0, | ||||||
|                     "┗" + "━" * (self.width - 2) + "┛", self.pair) |                     "┗" + "━" * (self.width - 2) + "┛", self.fg_border_color) | ||||||
|         self.refresh_pad(self.pad, 0, 0, self.y, self.x, |         self.refresh_pad(self.pad, 0, 0, self.y, self.x, | ||||||
|                          self.y + self.height - 1, self.x + self.width - 1) |                          self.y + self.height - 1, self.x + self.width - 1) | ||||||
|   | |||||||
| @@ -15,15 +15,14 @@ class MapDisplay(Display): | |||||||
|         self.pad = self.newpad(m.height, self.pack.tile_width * m.width + 1) |         self.pad = self.newpad(m.height, self.pack.tile_width * m.width + 1) | ||||||
|  |  | ||||||
|     def update_pad(self) -> None: |     def update_pad(self) -> None: | ||||||
|         self.init_pair(1, self.pack.tile_fg_color, self.pack.tile_bg_color) |  | ||||||
|         self.init_pair(2, self.pack.entity_fg_color, self.pack.entity_bg_color) |  | ||||||
|         self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack), |         self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack), | ||||||
|                     self.color_pair(1)) |                     self.pack.tile_fg_color, self.pack.tile_bg_color) | ||||||
|         for e in self.map.entities: |         for e in self.map.entities: | ||||||
|             self.addstr(self.pad, e.y, self.pack.tile_width * e.x, |             self.addstr(self.pad, e.y, self.pack.tile_width * e.x, | ||||||
|                         self.pack[e.name.upper()], self.color_pair(2)) |                         self.pack[e.name.upper()], | ||||||
|  |                         self.pack.entity_fg_color, self.pack.entity_bg_color) | ||||||
|  |  | ||||||
|         # Display Path map for deubg purposes |         # Display Path map for debug purposes | ||||||
|         # from squirrelbattle.entities.player import Player |         # from squirrelbattle.entities.player import Player | ||||||
|         # players = [ p for p in self.map.entities if isinstance(p,Player) ] |         # players = [ p for p in self.map.entities if isinstance(p,Player) ] | ||||||
|         # player = players[0] if len(players) > 0 else None |         # player = players[0] if len(players) > 0 else None | ||||||
| @@ -42,7 +41,8 @@ class MapDisplay(Display): | |||||||
|         #                 else: |         #                 else: | ||||||
|         #                     character = '←' |         #                     character = '←' | ||||||
|         #                 self.addstr(self.pad, y, self.pack.tile_width * x, |         #                 self.addstr(self.pad, y, self.pack.tile_width * x, | ||||||
|         #                     character, self.color_pair(1)) |         #                     character, self.pack.tile_fg_color, | ||||||
|  |         #                     self.pack.tile_bg_color) | ||||||
|  |  | ||||||
|     def display(self) -> None: |     def display(self) -> None: | ||||||
|         y, x = self.map.currenty, self.pack.tile_width * self.map.currentx |         y, x = self.map.currenty, self.pack.tile_width * self.map.currentx | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
| import curses | import curses | ||||||
|  | from random import randint | ||||||
| from typing import List | from typing import List | ||||||
|  |  | ||||||
| from squirrelbattle.menus import Menu, MainMenu | from squirrelbattle.menus import Menu, MainMenu | ||||||
| @@ -30,9 +31,9 @@ class MenuDisplay(Display): | |||||||
|  |  | ||||||
|     def update_pad(self) -> None: |     def update_pad(self) -> None: | ||||||
|         for i in range(self.trueheight): |         for i in range(self.trueheight): | ||||||
|             self.addstr(self.pad, i, 0, "  " + self.values[i]) |             self.addstr(self.pad, i, 0, "   " + self.values[i]) | ||||||
|         # set a marker on the selected line |         # set a marker on the selected line | ||||||
|         self.addstr(self.pad, self.menu.position, 0, ">") |         self.addstr(self.pad, self.menu.position, 0, " >") | ||||||
|  |  | ||||||
|     def display(self) -> None: |     def display(self) -> None: | ||||||
|         cornery = 0 if self.height - 2 >= self.menu.position - 1 \ |         cornery = 0 if self.height - 2 >= self.menu.position - 1 \ | ||||||
| @@ -43,7 +44,7 @@ class MenuDisplay(Display): | |||||||
|         self.menubox.refresh(self.y, self.x, self.height, self.width) |         self.menubox.refresh(self.y, self.x, self.height, self.width) | ||||||
|         self.pad.erase() |         self.pad.erase() | ||||||
|         self.update_pad() |         self.update_pad() | ||||||
|         self.refresh_pad(self.pad, cornery, 0, self.y + 1, self.x + 2, |         self.refresh_pad(self.pad, cornery, 0, self.y + 1, self.x + 1, | ||||||
|                          self.height - 2 + self.y, |                          self.height - 2 + self.y, | ||||||
|                          self.width - 2 + self.x) |                          self.width - 2 + self.x) | ||||||
|  |  | ||||||
| @@ -102,8 +103,7 @@ class MainMenuDisplay(Display): | |||||||
|         self.pad = self.newpad(max(self.rows, len(self.title) + 30), |         self.pad = self.newpad(max(self.rows, len(self.title) + 30), | ||||||
|                                max(len(self.title[0]) + 5, self.cols)) |                                max(len(self.title[0]) + 5, self.cols)) | ||||||
|  |  | ||||||
|         self.init_color(42, 1000, 1000, 1000) |         self.fg_color = curses.COLOR_WHITE | ||||||
|         self.init_pair(42, 42, curses.COLOR_BLACK) |  | ||||||
|  |  | ||||||
|         self.menudisplay = MenuDisplay(self.screen, self.pack) |         self.menudisplay = MenuDisplay(self.screen, self.pack) | ||||||
|         self.menudisplay.update_menu(self.menu) |         self.menudisplay.update_menu(self.menu) | ||||||
| @@ -112,7 +112,7 @@ class MainMenuDisplay(Display): | |||||||
|         for i in range(len(self.title)): |         for i in range(len(self.title)): | ||||||
|             self.addstr(self.pad, 4 + i, max(self.width // 2 |             self.addstr(self.pad, 4 + i, max(self.width // 2 | ||||||
|                         - len(self.title[0]) // 2 - 1, 0), self.title[i], |                         - len(self.title[0]) // 2 - 1, 0), self.title[i], | ||||||
|                         self.color_pair(42)) |                         self.fg_color) | ||||||
|         self.refresh_pad(self.pad, 0, 0, self.y, self.x, |         self.refresh_pad(self.pad, 0, 0, self.y, self.x, | ||||||
|                          self.height + self.y - 1, |                          self.height + self.y - 1, | ||||||
|                          self.width + self.x - 1) |                          self.width + self.x - 1) | ||||||
| @@ -131,9 +131,7 @@ class MainMenuDisplay(Display): | |||||||
|             self.menudisplay.handle_click(y - menuy, x - menux, game) |             self.menudisplay.handle_click(y - menuy, x - menux, game) | ||||||
|  |  | ||||||
|         if y <= len(self.title): |         if y <= len(self.title): | ||||||
|             from random import randint |             self.fg_color = randint(0, 1000), randint(0, 1000), randint(0, 1000) | ||||||
|             self.init_color(42, randint(0, 1000), randint(0, 1000), |  | ||||||
|                               randint(0, 1000)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class PlayerInventoryDisplay(MenuDisplay): | class PlayerInventoryDisplay(MenuDisplay): | ||||||
| @@ -141,7 +139,7 @@ class PlayerInventoryDisplay(MenuDisplay): | |||||||
|  |  | ||||||
|     def update_pad(self) -> None: |     def update_pad(self) -> None: | ||||||
|         self.addstr(self.pad, 0, (self.width - len(self.message)) // 2, |         self.addstr(self.pad, 0, (self.width - len(self.message)) // 2, | ||||||
|                     self.message, curses.A_BOLD | curses.A_ITALIC) |                     self.message, bold=True, italic=True) | ||||||
|         for i, item in enumerate(self.menu.values): |         for i, item in enumerate(self.menu.values): | ||||||
|             rep = self.pack[item.name.upper()] |             rep = self.pack[item.name.upper()] | ||||||
|             selection = f"[{rep}]" if i == self.menu.position else f" {rep} " |             selection = f"[{rep}]" if i == self.menu.position else f" {rep} " | ||||||
| @@ -169,7 +167,7 @@ class StoreInventoryDisplay(MenuDisplay): | |||||||
|  |  | ||||||
|     def update_pad(self) -> None: |     def update_pad(self) -> None: | ||||||
|         self.addstr(self.pad, 0, (self.width - len(self.message)) // 2, |         self.addstr(self.pad, 0, (self.width - len(self.message)) // 2, | ||||||
|                     self.message, curses.A_BOLD | curses.A_ITALIC) |                     self.message, bold=True, italic=True) | ||||||
|         for i, item in enumerate(self.menu.values): |         for i, item in enumerate(self.menu.values): | ||||||
|             rep = self.pack[item.name.upper()] |             rep = self.pack[item.name.upper()] | ||||||
|             selection = f"[{rep}]" if i == self.menu.position else f" {rep} " |             selection = f"[{rep}]" if i == self.menu.position else f" {rep} " | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ class MessageDisplay(Display): | |||||||
|                          self.height + 2, self.width + 4) |                          self.height + 2, self.width + 4) | ||||||
|         self.box.display() |         self.box.display() | ||||||
|         self.pad.erase() |         self.pad.erase() | ||||||
|         self.addstr(self.pad, 0, 0, self.message, curses.A_BOLD) |         self.addstr(self.pad, 0, 0, self.message, bold=True) | ||||||
|         self.refresh_pad(self.pad, 0, 0, self.y, self.x, |         self.refresh_pad(self.pad, 0, 0, self.y, self.x, | ||||||
|                          self.height + self.y - 1, |                          self.height + self.y - 1, | ||||||
|                          self.width + self.x - 1) |                          self.width + self.x - 1) | ||||||
|   | |||||||
| @@ -14,7 +14,6 @@ class StatsDisplay(Display): | |||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs): | ||||||
|         super().__init__(*args, **kwargs) |         super().__init__(*args, **kwargs) | ||||||
|         self.pad = self.newpad(self.rows, self.cols) |         self.pad = self.newpad(self.rows, self.cols) | ||||||
|         self.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK) |  | ||||||
|  |  | ||||||
|     def update_player(self, p: Player) -> None: |     def update_player(self, p: Player) -> None: | ||||||
|         self.player = p |         self.player = p | ||||||
| @@ -50,9 +49,8 @@ class StatsDisplay(Display): | |||||||
|                                     f"x{self.player.hazel}") |                                     f"x{self.player.hazel}") | ||||||
|  |  | ||||||
|         if self.player.dead: |         if self.player.dead: | ||||||
|             self.addstr(self.pad, 11, 0, _("YOU ARE DEAD"), |             self.addstr(self.pad, 11, 0, _("YOU ARE DEAD"), curses.COLOR_RED, | ||||||
|                         curses.A_BOLD | curses.A_BLINK | curses.A_STANDOUT |                         bold=True, blink=True, standout=True) | ||||||
|                         | self.color_pair(3)) |  | ||||||
|  |  | ||||||
|     def display(self) -> None: |     def display(self) -> None: | ||||||
|         self.pad.erase() |         self.pad.erase() | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user