tile_selector_widget.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. # Flexlay - A Generic 2D Game Editor
  2. # Copyright (C) 2014 Ingo Ruhnke <grumbel@gmail.com>
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. from PyQt5.QtCore import QSize, Qt, QEvent
  17. from PyQt5.QtGui import QPainter, QMouseEvent, QPaintEvent
  18. from PyQt5.QtWidgets import QWidget
  19. from flexlay.color import Color
  20. from flexlay.graphic_context import GraphicContext
  21. from flexlay.math import Point, Pointf, Rect, Rectf, Sizef
  22. from flexlay.tile_brush import TileBrush
  23. from flexlay.tileset import Tileset
  24. from flexlay.tool_context import ToolContext
  25. class TileSelectorWidget(QWidget):
  26. def __init__(self, viewport: QWidget) -> None:
  27. super().__init__()
  28. self.viewport = viewport
  29. self.index = 0
  30. self.has_focus = False
  31. self.mouse_over_tile = -1
  32. self.region_select = False
  33. self.current_pos = Point(0, 0)
  34. self.region_select_start = Point(0, 0)
  35. self.mouse_pos = Point(0, 0)
  36. self.scale = 1.0
  37. self.tileset = Tileset(32)
  38. self.tiles: list[int] = []
  39. self.setMouseTracking(True)
  40. def get_selection(self) -> Rect:
  41. selection = Rect(self.current_pos.x, self.current_pos.y,
  42. self.region_select_start.x, self.region_select_start.y)
  43. selection.normalize()
  44. selection.right += 1
  45. selection.bottom += 1
  46. selection.left = min(max(0, selection.left), self.columns)
  47. selection.right = min(max(0, selection.right), self.columns)
  48. selection.top = max(0, selection.top)
  49. return selection
  50. def mousePressEvent(self, event: QMouseEvent) -> None:
  51. assert ToolContext.current is not None
  52. if event.button() == Qt.LeftButton:
  53. brush = TileBrush(1, 1)
  54. brush.set_opaque()
  55. if 0 <= self.mouse_over_tile < len(self.tiles):
  56. brush.put(0, 0, self.tiles[self.mouse_over_tile])
  57. else:
  58. brush.put(0, 0, 0)
  59. ToolContext.current.tile_brush = brush
  60. elif event.button() == Qt.RightButton:
  61. self.region_select = True
  62. self.region_select_start = self.current_pos
  63. self.grabMouse()
  64. self.repaint()
  65. def mouseReleaseEvent(self, event: QMouseEvent) -> None:
  66. if event.button() == Qt.RightButton:
  67. self.releaseMouse()
  68. self.region_select = False
  69. selection = self.get_selection()
  70. # selection.bottom = min(max(0, selection.right), self.columns)
  71. brush = TileBrush(selection.width, selection.height)
  72. brush.set_transparent()
  73. for y in range(0, selection.height):
  74. for x in range(0, selection.width):
  75. tile = (selection.top + y) * self.columns + (selection.left + x)
  76. if 0 <= tile < len(self.tiles):
  77. brush.put(x, y, self.tiles[tile])
  78. else:
  79. brush.put(x, y, 0)
  80. assert ToolContext.current is not None
  81. ToolContext.current.tile_brush = brush
  82. self.repaint()
  83. def mouseMoveEvent(self, event: QMouseEvent) -> None:
  84. pos = self.get_mouse_tile_pos(Pointf.from_qt(event.pos()))
  85. self.current_pos = pos
  86. self.mouse_over_tile = pos.y * self.columns + pos.x
  87. self.repaint()
  88. def get_mouse_tile_pos(self, mouse_pos: Pointf) -> Point:
  89. x = int(mouse_pos.x / self.cell_size)
  90. y = int(mouse_pos.y / self.cell_size)
  91. if x >= self.columns:
  92. x = self.columns - 1
  93. return Point(x, y)
  94. def paintEvent(self, event: QPaintEvent) -> None:
  95. painter = QPainter(self)
  96. gc = GraphicContext(painter)
  97. assert ToolContext.current is not None
  98. brush = ToolContext.current.tile_brush
  99. start_row = event.rect().top() // self.cell_size
  100. end_row = (event.rect().bottom() + self.cell_size - 1) // self.cell_size
  101. end_index = min(end_row * self.columns, len(self.tiles))
  102. # Draw tiles
  103. for i in range(start_row * self.columns, end_index):
  104. x = i % self.columns
  105. y = i // self.columns
  106. tile = self.tileset.create(self.tiles[i])
  107. rect = Rectf.from_ps(Pointf(x * self.cell_size,
  108. y * self.cell_size),
  109. Sizef(self.cell_size,
  110. self.cell_size))
  111. if tile:
  112. sprite = tile.get_sprite()
  113. sprite.set_scale(self.scale, self.scale)
  114. sprite.draw(int(x * self.cell_size),
  115. int(y * self.cell_size), gc)
  116. # Use grid in the tileselector
  117. gc.draw_rect(rect, Color(0, 0, 0, 128))
  118. # mark the currently selected tile
  119. if brush.width == 1 and brush.height == 1 and brush.at(0, 0) == self.tiles[i]:
  120. gc.fill_rect(rect, Color(0, 0, 255, 100))
  121. elif self.mouse_over_tile == i and self.has_focus:
  122. gc.fill_rect(rect, Color(0, 0, 255, 20))
  123. # draw rectangle selection
  124. if self.region_select:
  125. rect = self.get_selection().to_f()
  126. rect.top *= self.cell_size
  127. rect.bottom *= self.cell_size
  128. rect.left *= self.cell_size
  129. rect.right *= self.cell_size
  130. gc.fill_rect(rect, Color(0, 0, 255, 100))
  131. def enterEvent(self, event: QEvent) -> None:
  132. self.has_focus = True
  133. self.repaint()
  134. def leaveEvent(self, event: QEvent) -> None:
  135. self.has_focus = False
  136. self.repaint()
  137. def resizeEvent(self, event: QEvent) -> None:
  138. self.update_minimum_size()
  139. def update_minimum_size(self) -> None:
  140. min_rows = (len(self.tiles) + self.columns - 1) // self.columns
  141. size = QSize(self.tileset.get_tile_size() * self.columns,
  142. self.tileset.get_tile_size() * min_rows)
  143. self.setMinimumSize(size)
  144. @property
  145. def columns(self) -> int:
  146. return int(self.viewport.width() / self.cell_size)
  147. def set_scale(self, s: float) -> None:
  148. self.scale = s
  149. self.repaint()
  150. def set_tiles(self, tiles: list[int]) -> None:
  151. self.tiles = tiles
  152. self.update_minimum_size()
  153. def get_tiles(self) -> list[int]:
  154. return self.tiles
  155. def set_tileset(self, tileset: Tileset) -> None:
  156. self.tileset = tileset
  157. self.update_minimum_size()
  158. @property
  159. def cell_size(self) -> int:
  160. return int(self.tileset.get_tile_size() * self.scale)
  161. # EOF #