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
10 from direct
.gui
.OnscreenImage
import OnscreenImage
11 from direct
.gui
.DirectGui
import DirectButton
, DirectFrame
, DirectEntry
, \
12 YesNoDialog
, DirectOptionMenu
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
.editor
.scene_list
import SceneList
18 from pmachines
.editor
.inspector
import Inspector
21 class SceneEditor(DirectObject
):
23 def __init__(self
, json
, json_name
, context
, add_item
, items
):
27 self
.__json
_name
= json_name
28 self
.__inspector
= None
29 self
.__context
= context
30 self
.__add
_item
= add_item
31 self
._font
= base
.loader
.load_font(
32 'assets/fonts/Hanken-Book.ttf')
34 self
._font
.set_pixels_per_unit(60)
35 self
._font
.set_minfilter(Texture
.FTLinearMipmapLinear
)
36 self
._font
.set_outline((0, 0, 0, 1), .8, .2)
39 'text_font': self
._font
,
40 'text_fg': (.9, .9, .9, 1),
42 'frameColor': (.4, .4, .4, .14),
43 'rolloverSound': loader
.load_sfx(
44 'assets/audio/sfx/rollover.ogg'),
45 'clickSound': loader
.load_sfx(
46 'assets/audio/sfx/click.ogg')}
47 w
, h
, tw
, l
= 1.8, .9, 30, .36
48 self
._frm
= DirectFrame(frameColor
=(.4, .4, .4, .06),
49 frameSize
=(0, w
, 0, h
),
50 parent
=base
.a2dBottomCenter
,
53 _('Name'), pos
=(l
- .03, h
- .1), parent
=self
._frm
,
54 font
=self
._common
['text_font'],
55 scale
=self
._common
['scale'],
56 fg
=self
._common
['text_fg'],
57 align
=TextNode
.A_right
)
58 self
.__name
_entry
= DirectEntry(
59 scale
=self
._common
['scale'],
63 frameColor
=self
._common
['frameColor'],
64 initialText
=json
['name'],
66 text_fg
=self
._common
['text_fg'])
68 _('Description'), pos
=(l
- .03, h
- .2), parent
=self
._frm
,
69 font
=self
._common
['text_font'],
70 scale
=self
._common
['scale'],
71 fg
=self
._common
['text_fg'],
72 align
=TextNode
.A_right
)
73 def add_line_break(txt
, entry
):
74 curpos
= entry
.node().getCursorPosition()
75 entry
.set(txt
[:curpos
]+ "\n" + txt
[curpos
:])
76 entry
.node().setCursorPosition(curpos
+1)
78 self
.__instructions
_entry
= DirectEntry(
79 scale
=self
._common
['scale'],
85 frameColor
=self
._common
['frameColor'],
86 initialText
=json
['instructions'].replace('\\n', '\n'),
88 text_fg
=self
._common
['text_fg'])
89 self
.__instructions
_entry
['command'] = add_line_break
90 self
.__instructions
_entry
['extraArgs'] = [self
.__instructions
_entry
]
91 def load_images_btn(path
, col
):
94 (.6, .6, .6, 1), # ready
96 (.8, .8, .8, 1), # rollover
102 (.4, .1, .1, .4)]}[col
]
103 return [self
.__load
_img
_btn
(path
, col
) for col
in colors
]
104 fcols
= (.4, .4, .4, .14), (.3, .3, .3, .05)
106 image
=load_images_btn('exitRight', 'gray'), scale
=.05,
108 parent
=self
._frm
, command
=self
.__on
_close
, state
=NORMAL
, relief
=FLAT
,
110 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
111 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
113 image
=load_images_btn('save', 'gray'), scale
=.05,
115 parent
=self
._frm
, command
=self
.__on
_save
, state
=NORMAL
, relief
=FLAT
,
117 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
118 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
120 image
=load_images_btn('menuList', 'gray'), scale
=.05,
122 parent
=self
._frm
, command
=self
.__on
_scene
_list
, state
=NORMAL
, relief
=FLAT
,
124 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
125 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
126 item_modules
= glob('pmachines/items/*.py')
127 item_modules
= [basename(i
)[:-3] for i
in item_modules
]
128 self
.__new
_items
= {}
129 for item_module
in item_modules
:
130 mod_name
= 'pmachines.items.' + item_module
131 for member
in import_module(mod_name
).__dict
__.values():
132 if isclass(member
) and issubclass(member
, Item
) and \
134 self
.__new
_items
[member
.__name
__] = member
136 _('new item'), pos
=(.02, .46), parent
=self
._frm
,
137 font
=self
._common
['text_font'],
138 scale
=self
._common
['scale'],
139 fg
=self
._common
['text_fg'],
140 align
=TextNode
.A_left
)
143 text
=_('new item'), pos
=(.02, 1, .4), items
=list(self
.__new
_items
.keys()),
144 parent
=self
._frm
, command
=self
.__on
_new
_item
, state
=NORMAL
,
145 relief
=FLAT
, item_relief
=FLAT
,
146 frameColor
=fcols
[0], item_frameColor
=fcols
[0],
147 popupMenu_frameColor
=fcols
[0],
148 popupMarker_frameColor
=fcols
[0],
149 text_font
=self
._font
, text_fg
=(.9, .9, .9, 1),
150 highlightColor
=(.9, .9, .9, .9),
151 item_text_font
=self
._font
, item_text_fg
=(.9, .9, .9, 1),
152 rolloverSound
=loader
.load_sfx('assets/audio/sfx/rollover.ogg'),
153 clickSound
=loader
.load_sfx('assets/audio/sfx/click.ogg'))
154 messenger
.send('editor-start')
155 self
.accept('editor-item-click', self
.__on
_item
_click
)
156 self
.accept('editor-inspector-destroy', self
.__on
_inspector
_destroy
)
158 def __on_close(self
):
159 self
.__json
['name'] = self
.__name
_entry
.get()
160 self
.__json
['instructions'] = self
.__instructions
_entry
.get()
161 if self
.__compute
_hash
() == self
.__json
['version']:
164 self
.__dialog
= YesNoDialog(dialogName
='Unsaved changes',
165 text
=_('You have unsaved changes. Really quit?'),
166 command
=self
.__actually
_close
)
167 self
.__dialog
['frameColor'] = (.4, .4, .4, .14)
168 self
.__dialog
['relief'] = FLAT
169 self
.__dialog
.component('text0')['fg'] = (.9, .9, .9, 1)
170 self
.__dialog
.component('text0')['font'] = self
._font
171 for b
in self
.__dialog
.buttonList
:
172 b
['frameColor'] = (.4, .4, .4, .14)
173 b
.component('text0')['fg'] = (.9, .9, .9, 1)
174 b
.component('text0')['font'] = self
._font
175 b
.component('text1')['fg'] = (.9, .1, .1, 1)
176 b
.component('text1')['font'] = self
._font
177 b
.component('text2')['fg'] = (.9, .9, .1, 1)
178 b
.component('text2')['font'] = self
._font
180 def __on_new_item(self
, item
):
183 _item
= self
.__new
_items
[item
](
184 self
.__context
.world
,
185 self
.__context
.plane_node
,
186 self
.__context
.cb_inst
,
187 self
.__context
.curr_bottom
,
188 self
.__context
.repos
,
190 _item
._Item
__editing
= True
191 self
.__add
_item
(_item
)
192 item_json
['class'] = _item
.__class
__.__name
__
193 item_json
['position'] = list(_item
._np
.get_pos())
194 self
.__json
['items'] += [item_json
]
196 def __actually_close(self
, arg
):
199 messenger
.send('editor-stop')
200 self
.__dialog
.cleanup()
203 self
.__json
['name'] = self
.__name
_entry
.get()
204 self
.__json
['instructions'] = self
.__instructions
_entry
.get()
205 self
.__json
['version'] = self
.__compute
_hash
()
206 with
open('assets/scenes/%s.json' % self
.__json
_name
, 'w') as f
:
207 f
.write(dumps(self
.__json
, indent
=2, sort_keys
=True))
209 def __on_scene_list(self
):
212 def __load_img_btn(self
, path
, col
):
213 img
= OnscreenImage('assets/images/buttons/%s.dds' % path
)
214 img
.set_transparency(True)
219 def __compute_hash(self
):
220 new_dict
= deepcopy(self
.__json
)
221 del new_dict
['version']
222 new_dict_str
= str(dumps(new_dict
, indent
=2, sort_keys
=True))
223 h
= hashlib
.new('sha256')
224 h
.update(new_dict_str
.encode())
225 return h
.hexdigest()[:12]
227 def __on_item_click(self
, item
):
228 if self
.__inspector
and self
.__inspector
.item
== item
: return
230 self
.__inspector
.destroy()
231 self
.__inspector
= Inspector(item
, self
.__items
)
233 def __on_inspector_destroy(self
):
234 self
.__inspector
= None