942ab5356f759b4e9d86ee462c6791d711ee8dcb
1 from copy
import deepcopy
3 from importlib
import import_module
4 from inspect
import isclass
6 from os
.path
import basename
7 from logging
import info
9 from panda3d
.core
import Texture
, TextNode
, LPoint3f
10 from direct
.gui
.OnscreenImage
import OnscreenImage
11 from direct
.gui
.DirectGui
import DirectButton
, DirectEntry
, \
12 YesNoDialog
, DirectOptionMenu
, DirectFrame
13 from direct
.gui
.DirectGuiGlobals
import FLAT
, NORMAL
14 from direct
.gui
.OnscreenText
import OnscreenText
15 from direct
.showbase
.DirectObject
import DirectObject
16 from pmachines
.items
.item
import Item
17 from pmachines
.items
.test_item
import PixelSpaceTestItem
, WorldSpaceTestItem
18 from pmachines
.editor
.scene_list
import SceneList
19 from pmachines
.editor
.inspector
import Inspector
, PixelSpaceInspector
, WorldSpaceInspector
20 from pmachines
.editor
.start_items
import StartItems
21 from ya2
.utils
.gfx
import Point
, DirectGuiMixin
22 from pmachines
.editor
.augmented_frame
import AugmentedDirectFrame
23 from pmachines
.gui
.options_page
import DirectOptionMenuTestable
26 class SceneEditor(DirectObject
):
28 def __init__(self
, json
, json_name
, context
, add_item
, items
, world
, mouse_plane_node
, pos_mgr
):
33 self
.__mouse
_plane
_node
= mouse_plane_node
34 self
.__pos
_mgr
= pos_mgr
37 self
.__json
= json
= {
46 self
.__inspector
= None
47 self
.__context
= context
48 self
.__add
_item
= add_item
49 self
._font
= base
.loader
.load_font(
50 'assets/fonts/Hanken-Book.ttf')
52 self
._font
.set_pixels_per_unit(60)
53 self
._font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
54 self
._font
.set_outline((0, 0, 0, 1), .8, .2)
57 'text_font': self
._font
,
58 'text_fg': (.9, .9, .9, 1),
60 'frameColor': (.4, .4, .4, .14),
61 'rolloverSound': loader
.load_sfx(
62 'assets/audio/sfx/rollover.ogg'),
63 'clickSound': loader
.load_sfx(
64 'assets/audio/sfx/click.ogg')}
65 tooltip_args
= self
._common
['text_font'], self
._common
['scale'], self
._common
['text_fg']
66 w
, h
, tw
, l
= 1.8, 1, 30, .36
67 self
._frm
= AugmentedDirectFrame(frameColor
=(.4, .4, .4, .06),
68 frameSize
=(0, w
, 0, h
),
69 parent
=base
.a2dBottomCenter
,
71 delta_drag
=LPoint3f(0, 0, h
),
72 collapse_pos
=(.06, 1, .94),
73 pos_mgr
=self
.__pos
_mgr
,
76 _('Filename'), pos
=(l
- .03, h
- .1), parent
=self
._frm
,
77 font
=self
._common
['text_font'],
78 scale
=self
._common
['scale'],
79 fg
=self
._common
['text_fg'],
80 align
=TextNode
.A_right
)
81 self
.__filenamename
_entry
= DirectEntry(
82 scale
=self
._common
['scale'],
86 frameColor
=self
._common
['frameColor'],
87 initialText
=json_name
,
89 text_fg
=self
._common
['text_fg'])
90 self
.__filenamename
_entry
.__class
__ = type('DirectEntryMixed', (DirectEntry
, DirectGuiMixin
), {})
91 self
.__filenamename
_entry
.set_tooltip(_('The name of the file without the extension'), *tooltip_args
)
92 self
.__pos
_mgr
['editor_scene_filename'] = self
.__filenamename
_entry
.pos_pixel()
94 _('Name'), pos
=(l
- .03, h
- .2), parent
=self
._frm
,
95 font
=self
._common
['text_font'],
96 scale
=self
._common
['scale'],
97 fg
=self
._common
['text_fg'],
98 align
=TextNode
.A_right
)
99 self
.__name
_entry
= DirectEntry(
100 scale
=self
._common
['scale'],
102 entryFont
=self
._font
,
104 frameColor
=self
._common
['frameColor'],
105 initialText
=json
['name'],
107 text_fg
=self
._common
['text_fg'])
108 self
.__name
_entry
.__class
__ = type('DirectEntryMixed', (DirectEntry
, DirectGuiMixin
), {})
109 self
.__name
_entry
.set_tooltip(_('The title of the scene'), *tooltip_args
)
110 self
.__pos
_mgr
['editor_scene_name'] = self
.__name
_entry
.pos_pixel()
112 _('Description'), pos
=(l
- .03, h
- .3), parent
=self
._frm
,
113 font
=self
._common
['text_font'],
114 scale
=self
._common
['scale'],
115 fg
=self
._common
['text_fg'],
116 align
=TextNode
.A_right
)
117 def add_line_break(txt
, entry
):
118 curpos
= entry
.node().getCursorPosition()
119 entry
.set(txt
[:curpos
]+ "\n" + txt
[curpos
:])
120 entry
.node().setCursorPosition(curpos
+1)
122 self
.__instructions
_entry
= DirectEntry(
123 scale
=self
._common
['scale'],
125 entryFont
=self
._font
,
129 frameColor
=self
._common
['frameColor'],
130 initialText
=json
['instructions'].replace('\\n', '\n'),
132 text_fg
=self
._common
['text_fg'])
133 self
.__instructions
_entry
['command'] = add_line_break
134 self
.__instructions
_entry
['extraArgs'] = [self
.__instructions
_entry
]
135 self
.__instructions
_entry
.__class
__ = type('DirectEntryMixed', (DirectEntry
, DirectGuiMixin
), {})
136 self
.__instructions
_entry
.set_tooltip(_('The description of the scene'), *tooltip_args
)
137 self
.__pos
_mgr
['editor_scene_instructions'] = self
.__instructions
_entry
.pos_pixel()
138 def load_images_btn(path
, col
):
141 (.6, .6, .6, 1), # ready
142 (1, 1, 1, 1), # press
143 (.8, .8, .8, 1), # rollover
149 (.4, .1, .1, .4)]}[col
]
150 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
151 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
153 image
=load_images_btn('exitRight', 'gray'), scale
=.05,
155 parent
=self
._frm
, command
=self
.__on
_close
, state
=NORMAL
, relief
=FLAT
,
157 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
158 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
159 b
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
160 self
.__pos
_mgr
['editor_close'] = b
.pos_pixel()
161 b
.set_tooltip(_('Close the scene editor'), *tooltip_args
)
163 image
=load_images_btn('save', 'gray'), scale
=.05,
165 parent
=self
._frm
, command
=self
.__on
_save
, state
=NORMAL
, relief
=FLAT
,
167 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
168 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
169 b
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
170 self
.__pos
_mgr
['editor_save'] = b
.pos_pixel()
171 b
.set_tooltip(_('Save the scene'), *tooltip_args
)
173 image
=load_images_btn('menuList', 'gray'), scale
=.05,
175 parent
=self
._frm
, command
=self
.__on
_scene
_list
, state
=NORMAL
, relief
=FLAT
,
177 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
178 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
179 b
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
180 self
.__pos
_mgr
['editor_sorting'] = b
.pos_pixel()
181 b
.set_tooltip(_('Set the sorting of the scenes'), *tooltip_args
)
182 item_modules
= glob('pmachines/items/*.py')
183 item_modules
= [basename(i
)[:-3] for i
in item_modules
]
184 self
.__new
_items
= {}
185 for item_module
in item_modules
:
186 mod_name
= 'pmachines.items.' + item_module
187 for member
in import_module(mod_name
).__dict
__.values():
188 if isclass(member
) and issubclass(member
, Item
) and \
190 self
.__new
_items
[member
.__name
__] = member
192 _('new item'), pos
=(.02, .46), parent
=self
._frm
,
193 font
=self
._common
['text_font'],
194 scale
=self
._common
['scale'],
195 fg
=self
._common
['text_fg'],
196 align
=TextNode
.A_left
)
197 items
= list(self
.__new
_items
.keys())
198 def new_item_test_set(comps
):
199 item_labels
= [f
'new_item_{i.lower()}' for i
in items
]
200 for i
in item_labels
:
201 if i
in self
.__pos
_mgr
:
202 del self
.__pos
_mgr
[i
]
203 for l
, b
in zip(item_labels
, comps
):
204 b
.__class
__ = type('DirectFrameMixed', (DirectFrame
, DirectGuiMixin
), {})
206 self
.__pos
_mgr
[l
] = (p
[0] + 5, p
[1])
207 b
= DirectOptionMenuTestable(
209 text
=_('new item'), pos
=(.02, 1, .4), items
=items
,
210 parent
=self
._frm
, command
=self
.__on
_new
_item
, state
=NORMAL
,
211 relief
=FLAT
, item_relief
=FLAT
,
212 frameColor
=fcols
[0], item_frameColor
=fcols
[0],
213 popupMenu_frameColor
=fcols
[0],
214 popupMarker_frameColor
=fcols
[0],
215 text_font
=self
._font
, text_fg
=(.9, .9, .9, 1),
216 highlightColor
=(.9, .9, .9, .9),
217 item_text_font
=self
._font
, item_text_fg
=(.9, .9, .9, 1),
218 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
219 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
220 b
.__class
__ = type('DirectOptionMenuMixed', (DirectOptionMenu
, DirectGuiMixin
), {})
221 b
._show
_cb
= new_item_test_set
222 b
.set_tooltip(_('Create a new item'), *tooltip_args
)
223 self
.__pos
_mgr
['editor_new_item'] = b
.pos_pixel()
225 image
=load_images_btn('start_items', 'gray'), scale
=.05,
227 parent
=self
._frm
, command
=self
.__on
_start
_items
, state
=NORMAL
, relief
=FLAT
,
229 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
230 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
231 b
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
232 self
.__pos
_mgr
['editor_start'] = b
.pos_pixel()
233 b
.set_tooltip(_('Initial items'), *tooltip_args
)
235 image
=load_images_btn('plus', 'gray'), scale
=.05,
237 parent
=self
._frm
, command
=self
.__on
_new
_scene
, state
=NORMAL
, relief
=FLAT
,
239 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
240 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
241 b
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
242 self
.__pos
_mgr
['editor_new'] = b
.pos_pixel()
243 b
.set_tooltip(_('New scene'), *tooltip_args
)
244 self
.__test
_items
= []
245 self
.__set
_test
_items
()
246 self
.__start
_items
= self
.__scene
_list
= None
247 messenger
.send('editor-start')
248 self
.accept('editor-item-click', self
.__on
_item
_click
)
249 self
.accept('editor-inspector-destroy', self
.__on
_inspector
_destroy
)
250 self
.accept('editor-start-items-save', self
.__on
_start
_items
_save
)
251 self
.accept('editor-start-items-destroy', self
.__on
_start
_items
_destroy
)
253 def __set_test_items(self
):
254 for pixel_space_item
in self
.__json
['test_items']['pixel_space']:
255 print(pixel_space_item
['id'], pixel_space_item
['position'])
256 pos_pixel
= pixel_space_item
['position']
257 win_res
= base
.win
.getXSize(), base
.win
.getYSize()
258 pos_win
= (pos_pixel
[0] / win_res
[0] * 2 - 1,
259 1 - pos_pixel
[1] / win_res
[1] * 2)
260 p_from
, p_to
= Point(pos_win
).from_to_points()
261 for hit
in self
.__world
.ray_test_all(p_from
, p_to
).get_hits():
262 if hit
.get_node() == self
.__mouse
_plane
_node
:
263 pos
= hit
.get_hit_pos()
264 self
.__set
_test
_item
(pos
, pixel_space_item
, PixelSpaceTestItem
)
265 for world_space_item
in self
.__json
['test_items']['world_space']:
266 print(world_space_item
['id'], world_space_item
['position'])
267 self
.__set
_test
_item
(world_space_item
['position'], world_space_item
, WorldSpaceTestItem
)
269 def __set_test_item(self
, pos
, json
, item_class
):
270 test_item
= item_class(
271 self
.__context
.world
,
272 self
.__context
.plane_node
,
273 self
.__context
.cb_inst
,
274 self
.__context
.curr_bottom
,
275 self
.__context
.repos
,
277 pos
=(pos
[0], 0, pos
[-1]),
279 self
.__test
_items
+= [test_item
]
281 def __on_close(self
):
282 self
.__json
['name'] = self
.__name
_entry
.get()
283 self
.__json
['instructions'] = self
.__instructions
_entry
.get()
284 if self
.__compute
_hash
() == self
.__json
['version']:
287 self
.__dialog
= YesNoDialog(dialogName
='Unsaved changes',
288 text
=_('You have unsaved changes. Really quit?'),
289 command
=self
.__actually
_close
)
290 self
.__dialog
['frameColor'] = (.4, .4, .4, .14)
291 self
.__dialog
['relief'] = FLAT
292 self
.__dialog
.component('text0')['fg'] = (.9, .9, .9, 1)
293 self
.__dialog
.component('text0')['font'] = self
._font
294 for b
in self
.__dialog
.buttonList
:
295 b
['frameColor'] = (.4, .4, .4, .14)
296 b
.component('text0')['fg'] = (.9, .9, .9, 1)
297 b
.component('text0')['font'] = self
._font
298 b
.component('text1')['fg'] = (.9, .1, .1, 1)
299 b
.component('text1')['font'] = self
._font
300 b
.component('text2')['fg'] = (.9, .9, .1, 1)
301 b
.component('text2')['font'] = self
._font
303 def __on_new_item(self
, item
):
307 if item
in ['PixelSpaceTestItem', 'WorldSpaceTestItem']:
309 _item
= self
.__new
_items
[item
](
310 self
.__context
.world
,
311 self
.__context
.plane_node
,
312 self
.__context
.cb_inst
,
313 self
.__context
.curr_bottom
,
314 self
.__context
.repos
,
317 _item
._Item
__editing
= True
318 self
.__add
_item
(_item
)
319 item_json
['class'] = _item
.__class
__.__name
__
320 item_json
['position'] = list(_item
._np
.get_pos())
321 if item
== 'PixelSpaceTestItem':
322 del item_json
['class']
323 self
.__json
['test_items']['pixel_space'] += [item_json
]
324 elif item
== 'WorldSpaceTestItem':
325 del item_json
['class']
326 self
.__json
['test_items']['world_space'] += [item_json
]
328 self
.__json
['items'] += [item_json
]
330 def __actually_close(self
, arg
):
333 messenger
.send('editor-stop')
334 self
.__dialog
.cleanup()
338 self
.__json
['name'] = self
.__name
_entry
.get()
339 self
.__json
['instructions'] = self
.__instructions
_entry
.get()
340 self
.__json
['version'] = self
.__compute
_hash
()
341 json_name
= self
.__filenamename
_entry
.get()
342 with
open('assets/scenes/%s.json' % json_name
, 'w') as f
:
343 f
.write(dumps(self
.__json
, indent
=2, sort_keys
=True))
345 def __on_scene_list(self
):
346 self
.__scene
_list
= SceneList(self
.__pos
_mgr
)
348 def __load_img_btn(self
, path
, col
):
349 img
= OnscreenImage('assets/images/buttons/%s.dds' % path
)
350 img
.set_transparency(True)
355 def __compute_hash(self
):
356 new_dict
= deepcopy(self
.__json
)
357 del new_dict
['version']
358 new_dict_str
= str(dumps(new_dict
, indent
=2, sort_keys
=True))
359 h
= hashlib
.new('sha256')
360 h
.update(new_dict_str
.encode())
361 return h
.hexdigest()[:12]
363 def __on_item_click(self
, item
):
364 if self
.__inspector
and self
.__inspector
.item
== item
: return
366 self
.__inspector
.destroy()
367 if item
.__class
__ == PixelSpaceTestItem
:
368 self
.__inspector
= PixelSpaceInspector(item
, self
.__items
)
369 elif item
.__class
__ == WorldSpaceTestItem
:
370 self
.__inspector
= WorldSpaceInspector(item
, self
.__items
, self
.__pos
_mgr
)
372 self
.__inspector
= Inspector(item
, self
.__items
, self
.__pos
_mgr
)
374 def __on_inspector_destroy(self
):
375 self
.__inspector
= None
377 def __on_new_scene(self
):
379 messenger
.send('editor-stop')
380 messenger
.send('new_scene')
382 def __on_start_items(self
):
383 self
.__start
_items
= StartItems(self
.__json
['start_items'], self
.__pos
_mgr
)
385 def __on_start_items_save(self
, start_items
):
386 self
.__json
['start_items'] = start_items
389 def __on_start_items_destroy(self
):
390 self
.__start
_items
= None
393 def test_items(self
):
394 return self
.__test
_items
399 self
.__inspector
.destroy()
400 self
.ignore('editor-item-click')
401 self
.ignore('editor-inspector-destroy')
402 self
.ignore('editor-start-items-save')
403 self
.ignore('editor-start-items-destroy')
404 if self
.__dialog
: self
.__actually
_close
(False)
405 for t
in self
.__test
_items
: t
.destroy()
406 self
.__test
_items
= []
407 if self
.__start
_items
:
408 self
.__start
_items
.destroy()
409 if self
.__scene
_list
:
410 self
.__scene
_list
.destroy()