d67203f43f6907d2e1879d8656812a7ab5b70480
1 from copy
import deepcopy
3 from logging
import info
5 from panda3d
.core
import Texture
, TextNode
, LPoint3f
6 from direct
.gui
.OnscreenImage
import OnscreenImage
7 from direct
.gui
.DirectGui
import DirectButton
, DirectEntry
, \
8 YesNoDialog
, DirectOptionMenu
, DirectFrame
9 from direct
.gui
.DirectGuiGlobals
import FLAT
, NORMAL
10 from direct
.gui
.OnscreenText
import OnscreenText
11 from direct
.showbase
.DirectObject
import DirectObject
12 from pmachines
.items
.test_item
import PixelSpaceTestItem
, WorldSpaceTestItem
13 from pmachines
.editor
.scene_list
import SceneList
14 from pmachines
.editor
.inspector
import Inspector
, PixelSpaceInspector
, WorldSpaceInspector
15 from pmachines
.editor
.start_items
import StartItems
16 from ya2
.utils
.gfx
import Point
, DirectGuiMixin
17 from pmachines
.editor
.augmented_frame
import AugmentedDirectFrame
18 from ya2
.utils
.gui
.base_page
import DirectOptionMenuTestable
19 from pmachines
.items
.basketball
import Basketball
20 from pmachines
.items
.box
import Box
21 from pmachines
.items
.domino
import Domino
22 from pmachines
.items
.shelf
import Shelf
23 from pmachines
.items
.teetertooter
import TeeterTooter
24 from pmachines
.items
.item
import FixedStrategy
, StillStrategy
25 from pmachines
.items
.domino
import UpStrategy
, DownStrategy
26 from pmachines
.items
.box
import HitStrategy
29 class SceneEditor(DirectObject
):
31 def __init__(self
, json
, json_name
, context
, add_item
, items
, world
, mouse_plane_node
, pos_mgr
):
36 self
.__mouse
_plane
_node
= mouse_plane_node
37 self
.__pos
_mgr
= pos_mgr
40 self
.__json
= json
= {
49 self
.__inspector
= None
50 self
.__context
= context
51 self
.__add
_item
= add_item
52 self
._font
= base
.loader
.load_font(
53 'assets/fonts/Hanken-Book.ttf')
55 self
._font
.set_pixels_per_unit(60)
56 self
._font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
57 self
._font
.set_outline((0, 0, 0, 1), .8, .2)
58 self
.__item
_classes
= [Basketball
, Box
, Domino
, Shelf
, TeeterTooter
]
59 self
.__item
_strategy
_classes
= [FixedStrategy
, StillStrategy
, HitStrategy
, UpStrategy
, DownStrategy
]
62 'text_font': self
._font
,
63 'text_fg': (.9, .9, .9, 1),
65 'frameColor': (.4, .4, .4, .14),
66 'rolloverSound': loader
.load_sfx(
67 'assets/audio/sfx/rollover.ogg'),
68 'clickSound': loader
.load_sfx(
69 'assets/audio/sfx/click.ogg')}
70 tooltip_args
= self
._common
['text_font'], self
._common
['scale'], self
._common
['text_fg']
71 w
, h
, tw
, l
= 1.8, 1, 30, .36
72 self
._frm
= AugmentedDirectFrame(frameColor
=(.4, .4, .4, .06),
73 frameSize
=(0, w
, 0, h
),
74 parent
=base
.a2dBottomCenter
,
76 delta_drag
=LPoint3f(0, 0, h
),
77 collapse_pos
=(.06, 1, .94),
78 pos_mgr
=self
.__pos
_mgr
,
81 _('Filename'), pos
=(l
- .03, h
- .1), parent
=self
._frm
,
82 font
=self
._common
['text_font'],
83 scale
=self
._common
['scale'],
84 fg
=self
._common
['text_fg'],
85 align
=TextNode
.A_right
)
86 self
.__filenamename
_entry
= DirectEntry(
87 scale
=self
._common
['scale'],
91 frameColor
=self
._common
['frameColor'],
92 initialText
=json_name
,
94 text_fg
=self
._common
['text_fg'])
95 self
.__filenamename
_entry
['text_fg'] = (1, 0, 0, 1)
96 self
.__filenamename
_entry
.__class
__ = type('DirectEntryMixed', (DirectEntry
, DirectGuiMixin
), {})
97 self
.__filenamename
_entry
.set_tooltip(_('The name of the file without the extension'), *tooltip_args
)
98 self
.__pos
_mgr
['editor_scene_filename'] = self
.__filenamename
_entry
.pos_pixel()
100 _('Name'), pos
=(l
- .03, h
- .2), parent
=self
._frm
,
101 font
=self
._common
['text_font'],
102 scale
=self
._common
['scale'],
103 fg
=self
._common
['text_fg'],
104 align
=TextNode
.A_right
)
105 self
.__name
_entry
= DirectEntry(
106 scale
=self
._common
['scale'],
108 entryFont
=self
._font
,
110 frameColor
=self
._common
['frameColor'],
111 initialText
=json
['name'],
113 text_fg
=self
._common
['text_fg'])
114 self
.__name
_entry
.__class
__ = type('DirectEntryMixed', (DirectEntry
, DirectGuiMixin
), {})
115 self
.__name
_entry
.set_tooltip(_('The title of the scene'), *tooltip_args
)
116 self
.__pos
_mgr
['editor_scene_name'] = self
.__name
_entry
.pos_pixel()
118 _('Description'), pos
=(l
- .03, h
- .3), parent
=self
._frm
,
119 font
=self
._common
['text_font'],
120 scale
=self
._common
['scale'],
121 fg
=self
._common
['text_fg'],
122 align
=TextNode
.A_right
)
123 def add_line_break(txt
, entry
):
124 curpos
= entry
.node().getCursorPosition()
125 entry
.set(txt
[:curpos
]+ "\n" + txt
[curpos
:])
126 entry
.node().setCursorPosition(curpos
+1)
128 self
.__instructions
_entry
= DirectEntry(
129 scale
=self
._common
['scale'],
131 entryFont
=self
._font
,
135 frameColor
=self
._common
['frameColor'],
136 initialText
=json
['instructions'].replace('\\n', '\n'),
138 text_fg
=self
._common
['text_fg'])
139 self
.__instructions
_entry
['command'] = add_line_break
140 self
.__instructions
_entry
['extraArgs'] = [self
.__instructions
_entry
]
141 self
.__instructions
_entry
.__class
__ = type('DirectEntryMixed', (DirectEntry
, DirectGuiMixin
), {})
142 self
.__instructions
_entry
.set_tooltip(_('The description of the scene'), *tooltip_args
)
143 self
.__pos
_mgr
['editor_scene_instructions'] = self
.__instructions
_entry
.pos_pixel()
144 def load_images_btn(path
, col
):
147 (.6, .6, .6, 1), # ready
148 (1, 1, 1, 1), # press
149 (.8, .8, .8, 1), # rollover
155 (.4, .1, .1, .4)]}[col
]
156 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
157 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
159 image
=load_images_btn('exitRight', 'gray'), scale
=.05,
161 parent
=self
._frm
, command
=self
.__on
_close
, state
=NORMAL
, relief
=FLAT
,
163 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
164 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
165 b
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
166 self
.__pos
_mgr
['editor_close'] = b
.pos_pixel()
167 b
.set_tooltip(_('Close the scene editor'), *tooltip_args
)
169 image
=load_images_btn('save', 'gray'), scale
=.05,
171 parent
=self
._frm
, command
=self
.__on
_save
, state
=NORMAL
, relief
=FLAT
,
173 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
174 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
175 b
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
176 self
.__pos
_mgr
['editor_save'] = b
.pos_pixel()
177 b
.set_tooltip(_('Save the scene'), *tooltip_args
)
179 image
=load_images_btn('menuList', 'gray'), scale
=.05,
181 parent
=self
._frm
, command
=self
.__on
_scene
_list
, state
=NORMAL
, relief
=FLAT
,
183 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
184 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
185 b
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
186 self
.__pos
_mgr
['editor_sorting'] = b
.pos_pixel()
187 b
.set_tooltip(_('Set the sorting of the scenes'), *tooltip_args
)
188 # item_modules = glob('pmachines/items/*.py')
189 # item_modules = [basename(i)[:-3] for i in item_modules]
190 self
.__new
_items
= {}
191 # for item_module in item_modules:
192 # mod_name = 'pmachines.items.' + item_module
193 # for member in import_module(mod_name).__dict__.values():
194 # if isclass(member) and issubclass(member, Item) and \
196 # self.__new_items[member.__name__] = member
197 for i
in self
.__item
_classes
: self
.__new
_items
[i
.__name
__] = i
199 _('new item'), pos
=(.02, .46), parent
=self
._frm
,
200 font
=self
._common
['text_font'],
201 scale
=self
._common
['scale'],
202 fg
=self
._common
['text_fg'],
203 align
=TextNode
.A_left
)
204 items
= list(self
.__new
_items
.keys())
205 def new_item_test_set(comps
):
206 item_labels
= [f
'new_item_{i.lower()}' for i
in items
]
207 for i
in item_labels
:
208 if i
in self
.__pos
_mgr
:
209 del self
.__pos
_mgr
[i
]
210 for l
, b
in zip(item_labels
, comps
):
211 b
.__class
__ = type('DirectFrameMixed', (DirectFrame
, DirectGuiMixin
), {})
213 self
.__pos
_mgr
[l
] = (p
[0] + 5, p
[1])
214 b
= DirectOptionMenuTestable(
216 text
=_('new item'), pos
=(.02, 1, .4), items
=items
,
217 parent
=self
._frm
, command
=self
.__on
_new
_item
, state
=NORMAL
,
218 relief
=FLAT
, item_relief
=FLAT
,
219 frameColor
=fcols
[0], item_frameColor
=fcols
[0],
220 popupMenu_frameColor
=fcols
[0],
221 popupMarker_frameColor
=fcols
[0],
222 text_font
=self
._font
, text_fg
=(.9, .9, .9, 1),
223 highlightColor
=(.9, .9, .9, .9),
224 item_text_font
=self
._font
, item_text_fg
=(.9, .9, .9, 1),
225 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
226 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
227 b
.__class
__ = type('DirectOptionMenuMixed', (DirectOptionMenu
, DirectGuiMixin
), {})
228 b
._show
_cb
= new_item_test_set
229 b
.set_tooltip(_('Create a new item'), *tooltip_args
)
230 self
.__pos
_mgr
['editor_new_item'] = b
.pos_pixel()
232 image
=load_images_btn('start_items', 'gray'), scale
=.05,
234 parent
=self
._frm
, command
=self
.__on
_start
_items
, state
=NORMAL
, relief
=FLAT
,
236 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
237 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
238 b
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
239 self
.__pos
_mgr
['editor_start'] = b
.pos_pixel()
240 b
.set_tooltip(_('Initial items'), *tooltip_args
)
242 image
=load_images_btn('plus', 'gray'), scale
=.05,
244 parent
=self
._frm
, command
=self
.__on
_new
_scene
, state
=NORMAL
, relief
=FLAT
,
246 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
247 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
248 b
.__class
__ = type('DirectButtonMixed', (DirectButton
, DirectGuiMixin
), {})
249 self
.__pos
_mgr
['editor_new'] = b
.pos_pixel()
250 b
.set_tooltip(_('New scene'), *tooltip_args
)
251 self
.__test
_items
= []
252 self
.__set
_test
_items
()
253 self
.__start
_items
= self
.__scene
_list
= None
254 messenger
.send('editor-start')
255 self
.accept('editor-item-click', self
.__on
_item
_click
)
256 self
.accept('editor-inspector-destroy', self
.__on
_inspector
_destroy
)
257 self
.accept('editor-start-items-save', self
.__on
_start
_items
_save
)
258 self
.accept('editor-start-items-destroy', self
.__on
_start
_items
_destroy
)
260 def __set_test_items(self
):
261 for pixel_space_item
in self
.__json
['test_items']['pixel_space']:
262 print(pixel_space_item
['id'], pixel_space_item
['position'])
263 pos_pixel
= pixel_space_item
['position']
264 win_res
= base
.win
.getXSize(), base
.win
.getYSize()
265 pos_win
= (pos_pixel
[0] / win_res
[0] * 2 - 1,
266 1 - pos_pixel
[1] / win_res
[1] * 2)
267 p_from
, p_to
= Point(pos_win
).from_to_points()
268 for hit
in self
.__world
.ray_test_all(p_from
, p_to
).get_hits():
269 if hit
.get_node() == self
.__mouse
_plane
_node
:
270 pos
= hit
.get_hit_pos()
271 self
.__set
_test
_item
(pos
, pixel_space_item
, PixelSpaceTestItem
)
272 for world_space_item
in self
.__json
['test_items']['world_space']:
273 print(world_space_item
['id'], world_space_item
['position'])
274 self
.__set
_test
_item
(world_space_item
['position'], world_space_item
, WorldSpaceTestItem
)
276 def __set_test_item(self
, pos
, json
, item_class
):
277 test_item
= item_class(
278 self
.__context
.world
,
279 self
.__context
.plane_node
,
280 self
.__context
.cb_inst
,
281 self
.__context
.curr_bottom
,
282 self
.__context
.repos
,
284 pos
=(pos
[0], 0, pos
[-1]),
286 self
.__test
_items
+= [test_item
]
288 def __on_close(self
):
289 self
.__json
['name'] = self
.__name
_entry
.get()
290 self
.__json
['instructions'] = self
.__instructions
_entry
.get()
291 if self
.__compute
_hash
() == self
.__json
['version']:
294 self
.__dialog
= YesNoDialog(dialogName
='Unsaved changes',
295 text
=_('You have unsaved changes. Really quit?'),
296 command
=self
.__actually
_close
)
297 self
.__dialog
['frameColor'] = (.4, .4, .4, .14)
298 self
.__dialog
['relief'] = FLAT
299 self
.__dialog
.component('text0')['fg'] = (.9, .9, .9, 1)
300 self
.__dialog
.component('text0')['font'] = self
._font
301 for b
in self
.__dialog
.buttonList
:
302 b
['frameColor'] = (.4, .4, .4, .14)
303 b
.component('text0')['fg'] = (.9, .9, .9, 1)
304 b
.component('text0')['font'] = self
._font
305 b
.component('text1')['fg'] = (.9, .1, .1, 1)
306 b
.component('text1')['font'] = self
._font
307 b
.component('text2')['fg'] = (.9, .9, .1, 1)
308 b
.component('text2')['font'] = self
._font
310 def __on_new_item(self
, item
):
314 if item
in ['PixelSpaceTestItem', 'WorldSpaceTestItem']:
316 _item
= self
.__new
_items
[item
](
317 self
.__context
.world
,
318 self
.__context
.plane_node
,
319 self
.__context
.cb_inst
,
320 self
.__context
.curr_bottom
,
321 self
.__context
.repos
,
324 _item
._Item
__editing
= True
325 self
.__add
_item
(_item
)
326 item_json
['class'] = _item
.__class
__.__name
__
327 item_json
['position'] = list(_item
._np
.get_pos())
328 if item
== 'PixelSpaceTestItem':
329 del item_json
['class']
330 self
.__json
['test_items']['pixel_space'] += [item_json
]
331 elif item
== 'WorldSpaceTestItem':
332 del item_json
['class']
333 self
.__json
['test_items']['world_space'] += [item_json
]
335 self
.__json
['items'] += [item_json
]
337 def __actually_close(self
, arg
):
340 messenger
.send('editor-stop')
341 self
.__dialog
.cleanup()
345 self
.__json
['name'] = self
.__name
_entry
.get()
346 self
.__json
['instructions'] = self
.__instructions
_entry
.get()
347 self
.__json
['version'] = self
.__compute
_hash
()
348 json_name
= self
.__filenamename
_entry
.get()
349 with
open('assets/scenes/%s.json' % json_name
, 'w') as f
:
350 f
.write(dumps(self
.__json
, indent
=2, sort_keys
=True))
352 def __on_scene_list(self
):
353 self
.__scene
_list
= SceneList(self
.__pos
_mgr
)
355 def __load_img_btn(self
, path
, col
):
356 img
= OnscreenImage('assets/images/buttons/%s.dds' % path
)
357 img
.set_transparency(True)
362 def __compute_hash(self
):
363 new_dict
= deepcopy(self
.__json
)
364 del new_dict
['version']
365 new_dict_str
= str(dumps(new_dict
, indent
=2, sort_keys
=True))
366 h
= hashlib
.new('sha256')
367 h
.update(new_dict_str
.encode())
368 return h
.hexdigest()[:12]
370 def __on_item_click(self
, item
):
371 if self
.__inspector
and self
.__inspector
.item
== item
: return
373 self
.__inspector
.destroy()
374 if item
.__class
__ == PixelSpaceTestItem
:
375 self
.__inspector
= PixelSpaceInspector(item
, self
.__items
)
376 elif item
.__class
__ == WorldSpaceTestItem
:
377 self
.__inspector
= WorldSpaceInspector(item
, self
.__items
, self
.__pos
_mgr
)
379 self
.__inspector
= Inspector(item
, self
.__items
, self
.__pos
_mgr
, self
.__item
_strategy
_classes
)
381 def __on_inspector_destroy(self
):
382 self
.__inspector
= None
384 def __on_new_scene(self
):
386 messenger
.send('editor-stop')
387 messenger
.send('new_scene')
389 def __on_start_items(self
):
390 self
.__start
_items
= StartItems(self
.__json
['start_items'], self
.__pos
_mgr
, self
.__item
_classes
, self
.__item
_strategy
_classes
)
392 def __on_start_items_save(self
, start_items
):
393 self
.__json
['start_items'] = start_items
396 def __on_start_items_destroy(self
):
397 self
.__start
_items
= None
400 def test_items(self
):
401 return self
.__test
_items
406 self
.__inspector
.destroy()
407 self
.ignore('editor-item-click')
408 self
.ignore('editor-inspector-destroy')
409 self
.ignore('editor-start-items-save')
410 self
.ignore('editor-start-items-destroy')
411 if self
.__dialog
: self
.__actually
_close
(False)
412 for t
in self
.__test
_items
: t
.destroy()
413 self
.__test
_items
= []
414 if self
.__start
_items
:
415 self
.__start
_items
.destroy()
416 if self
.__scene
_list
:
417 self
.__scene
_list
.destroy()