source: tools/area_editor.py@ 117:9f3557e4833a

Last change on this file since 117:9f3557e4833a was 117:9f3557e4833a, checked in by Neil Muller <drnlmuller@…>, 8 years ago

Add fill button

  • Property exe set to *
File size: 9.4 KB
Line 
1#!/usr/bin/env python
2
3# The basic area editor
4#
5# To edit an existing level, use
6# editor levelname
7#
8# To create a new level:
9#
10# editor levelname <xsize> <ysiz>
11# (size specified in pixels
12#
13
14import os
15import sys
16
17import pygame
18import pygame.locals as pgl
19
20from albow.root import RootWidget
21from albow.widget import Widget
22from albow.controls import Button
23
24sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
25
26from nagslang.constants import SCREEN
27from nagslang.level import Level, POLY_COLORS
28
29
30# layout constants
31MENU_BUTTON_HEIGHT = 35
32MENU_PAD = 6
33MENU_HALF_PAD = MENU_PAD // 2
34MENU_LEFT = SCREEN[0] + MENU_HALF_PAD
35MENU_WIDTH = 200 - MENU_PAD
36
37
38class EditorLevel(Level):
39
40 def __init__(self, name, x=800, y=600):
41 super(EditorLevel, self).__init__(name)
42 self.x = x
43 self.y = y
44
45 def round_point(self, pos):
46 return (10 * (pos[0] // 10), 10 * (pos[1] // 10))
47
48 def point_to_pymunk(self, pos):
49 # inverse of point_to_pygame
50 # (this is also the same as point_to_pygame, but a additional
51 # function for sanity later in pyweek).
52 return (pos[0], self.y - pos[1])
53
54 def add_point(self, poly_index, pos):
55 self.polygons.setdefault(poly_index, [])
56 if not self.polygons[poly_index]:
57 point = self.point_to_pymunk(self.round_point(pos))
58 self.polygons[poly_index].append(point)
59 else:
60 add_pos = self.fix_angle(poly_index, pos)
61 self.polygons[poly_index].append(add_pos)
62
63 def fix_angle(self, index, pos):
64 # Last point
65 point1 = self.point_to_pygame(self.polygons[index][-1])
66 pos = self.round_point(pos)
67 # We want the line (point1 to pos) to be an angle of
68 # 0, 45, 90, 135, 180, 225, 270, 305
69 # However, we only need to consider half the circle
70 # This is a hack to approximate the right thing
71 pos0 = (pos[0], point1[1])
72 pos90 = (point1[0], pos[1])
73 dist = max(abs(point1[0] - pos[0]), abs(point1[1] - pos[1]))
74 pos45 = (point1[0] + dist, point1[1] + dist)
75 pos135 = (point1[0] + dist, point1[1] - dist)
76 pos225 = (point1[0] - dist, point1[1] - dist)
77 pos305 = (point1[0] - dist, point1[1] + dist)
78 min_dist = 9999999
79 new_pos = point1
80 for cand in [pos0, pos90, pos45, pos135, pos225, pos305]:
81 dist = (pos[0] - cand[0]) ** 2 + (pos[1] - cand[1]) ** 2
82 if dist < min_dist:
83 new_pos = cand
84 min_dist = dist
85 return self.point_to_pymunk(new_pos)
86
87 def delete_point(self, index):
88 if index in self.polygons and len(self.polygons[index]) > 0:
89 self.polygons[index].pop()
90
91 def draw(self, surface, topleft, mouse_pos, mouse_poly, filled):
92 self._draw_background(True)
93 # Draw polygons as needed for the editor
94 if filled:
95 self._draw_exterior(True)
96 for index, polygon in self.polygons.items():
97 color = POLY_COLORS[index]
98 if len(polygon) > 1:
99 pointlist = [self.point_to_pygame(p) for p in polygon]
100 pygame.draw.lines(self._surface, color, False, pointlist, 2)
101 if index == mouse_poly and mouse_pos:
102 endpoint = self.fix_angle(index, mouse_pos)
103 pygame.draw.line(self._surface, color,
104 self.point_to_pygame(polygon[-1]),
105 self.point_to_pygame(endpoint))
106 surface_area = pygame.rect.Rect(topleft, SCREEN)
107 surface.blit(self._surface, (0, 0), surface_area)
108
109
110class LevelWidget(Widget):
111
112 def __init__(self, level):
113 super(LevelWidget, self).__init__(pygame.rect.Rect(0, 0,
114 SCREEN[0], SCREEN[1]))
115 self.level = level
116 self.pos = (0, 0)
117 self.filled_mode = False
118 self.mouse_pos = None
119 self.cur_poly = None
120
121 def _level_coordinates(self, pos):
122 # Move positions to level values
123 if not pos:
124 return (0, 0)
125 return pos[0] + self.pos[0], pos[1] + self.pos[1]
126
127 def _move_view(self, offset):
128 new_pos = [self.pos[0] + offset[0], self.pos[1] + offset[1]]
129 if new_pos[0] < 0:
130 new_pos[0] = self.pos[0]
131 elif new_pos[0] > self.level.x - SCREEN[0]:
132 new_pos[0] = self.pos[0]
133 if new_pos[1] < 0:
134 new_pos[1] = self.pos[1]
135 elif new_pos[1] > self.level.y - SCREEN[1]:
136 new_pos[1] = self.pos[1]
137 self.pos = tuple(new_pos)
138
139 def draw(self, surface):
140 if (self.cur_poly is not None and self.cur_poly in self.level.polygons
141 and len(self.level.polygons[self.cur_poly])):
142 # We have an active polygon
143 mouse_pos = self._level_coordinates(self.mouse_pos)
144 else:
145 mouse_pos = None
146 level.draw(surface, self.pos, mouse_pos, self.cur_poly,
147 self.filled_mode)
148
149 def change_poly(self, new_poly):
150 self.cur_poly = new_poly
151 if self.cur_poly is not None:
152 self.filled_mode = False
153
154 def key_down(self, ev):
155 if ev.key == pgl.K_LEFT:
156 self._move_view((-10, 0))
157 elif ev.key == pgl.K_RIGHT:
158 self._move_view((10, 0))
159 elif ev.key == pgl.K_UP:
160 self._move_view((0, -10))
161 elif ev.key == pgl.K_DOWN:
162 self._move_view((0, 10))
163 elif ev.key in (pgl.K_1, pgl.K_2, pgl.K_3, pgl.K_4, pgl.K_5, pgl.K_6):
164 self.change_poly(ev.key - pgl.K_0)
165 elif ev.key == pgl.K_0:
166 self.change_poly(None)
167 elif ev.key == pgl.K_d and self.cur_poly:
168 self.level.delete_point(self.cur_poly)
169 elif ev.key == pgl.K_f:
170 self.set_filled()
171
172 def set_filled(self):
173 if level.all_closed():
174 self.cur_poly = None
175 self.filled_mode = True
176 else:
177 print 'Not all polygons closed, so not filling'
178
179 def mouse_move(self, ev):
180 old_pos = self.mouse_pos
181 self.mouse_pos = ev.pos
182 if self.cur_poly and old_pos != self.mouse_pos:
183 self.invalidate()
184
185 def mouse_down(self, ev):
186 if self.cur_poly:
187 # Add a point
188 self.level.add_point(self.cur_poly,
189 self._level_coordinates(ev.pos))
190
191
192class PolyButton(Button):
193 """Button for coosing the correct polygon"""
194
195 def __init__(self, index, level_widget):
196 if index is not None:
197 text = "Draw: %s" % index
198 else:
199 text = 'Exit Draw Mode'
200 super(PolyButton, self).__init__(text)
201 self.index = index
202 self.level_widget = level_widget
203
204 def action(self):
205 self.level_widget.change_poly(self.index)
206
207
208class EditorApp(RootWidget):
209
210 def __init__(self, level, surface):
211 super(EditorApp, self).__init__(surface)
212 self.level = level
213 self.level_widget = LevelWidget(self.level)
214 self.add(self.level_widget)
215
216 # Add poly buttons
217 y = 15
218 for poly in range(1, 7):
219 but = PolyButton(poly, self.level_widget)
220 but.rect = pygame.rect.Rect(0, 0, MENU_WIDTH // 2 - MENU_PAD,
221 MENU_BUTTON_HEIGHT)
222 if poly % 2:
223 but.rect.move_ip(MENU_LEFT, y)
224 else:
225 but.rect.move_ip(MENU_LEFT + MENU_WIDTH // 2 - MENU_HALF_PAD,
226 y)
227 y += MENU_BUTTON_HEIGHT + MENU_PAD
228 self.add(but)
229
230 end_poly_but = PolyButton(None, self.level_widget)
231 end_poly_but.rect = pygame.rect.Rect(0, 0, MENU_WIDTH,
232 MENU_BUTTON_HEIGHT)
233 end_poly_but.rect.move_ip(MENU_LEFT, y)
234 self.add(end_poly_but)
235 y += MENU_BUTTON_HEIGHT + MENU_PAD
236
237 fill_but = Button('Fill exterior', action=self.level_widget.set_filled)
238 fill_but.rect = pygame.rect.Rect(0, 0, MENU_WIDTH, MENU_BUTTON_HEIGHT)
239 fill_but.rect.move_ip(MENU_LEFT, y)
240 self.add(fill_but)
241 y += MENU_BUTTON_HEIGHT + MENU_PAD
242
243 save_but = Button('Save Level', action=self.save)
244 save_but.rect = pygame.rect.Rect(0, 0, MENU_WIDTH, MENU_BUTTON_HEIGHT)
245 save_but.rect.move_ip(MENU_LEFT, y)
246 self.add(save_but)
247 y += MENU_BUTTON_HEIGHT + MENU_PAD
248
249 quit_but = Button('Quit', action=self.quit)
250 quit_but.rect = pygame.rect.Rect(0, 0, MENU_WIDTH, MENU_BUTTON_HEIGHT)
251 quit_but.rect.move_ip(MENU_LEFT, y)
252 self.add(quit_but)
253
254 def key_down(self, ev):
255 if ev.key == pgl.K_ESCAPE:
256 self.quit()
257 elif ev.key == pgl.K_s:
258 self.save()
259 else:
260 self.level_widget.key_down(ev)
261
262 def save(self):
263 level.save()
264
265 def mouse_move(self, ev):
266 self.level_widget.mouse_move(ev)
267
268
269if __name__ == "__main__":
270 if len(sys.argv) == 2:
271 level = EditorLevel(sys.argv[1])
272 level.load()
273 elif len(sys.argv) == 4:
274 level = EditorLevel(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))
275 else:
276 print 'Please supply a levelname or levelname and level size'
277 sys.exit()
278 pygame.display.init()
279 pygame.font.init()
280 pygame.display.set_mode((SCREEN[0] + MENU_WIDTH, SCREEN[1]),
281 pgl.SWSURFACE)
282 pygame.display.set_caption('Nagslang Area Editor')
283 pygame.key.set_repeat(200, 100)
284 app = EditorApp(level, pygame.display.get_surface())
285 app.run()
Note: See TracBrowser for help on using the repository browser.