ya2 · news · projects · code · about

new item
[pmachines.git] / pmachines / editor / scene.py
1 from copy import deepcopy
2 from json import dumps
3 from importlib import import_module
4 from inspect import isclass
5 from glob import glob
6 from os.path import basename
7 from logging import info
8 import hashlib
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
19
20
21 class SceneEditor(DirectObject):
22
23 def __init__(self, json, json_name, context, add_item, items):
24 super().__init__()
25 self.__items = items
26 self.__json = json
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')
33 self._font.clear()
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)
37 self._common = {
38 'scale': .046,
39 'text_font': self._font,
40 'text_fg': (.9, .9, .9, 1),
41 'relief': FLAT,
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,
51 pos=(-w/2, 0, 0))
52 OnscreenText(
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'],
60 pos=(l, 1, h - .1),
61 entryFont=self._font,
62 width=tw,
63 frameColor=self._common['frameColor'],
64 initialText=json['name'],
65 parent=self._frm,
66 text_fg=self._common['text_fg'])
67 OnscreenText(
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)
77 entry['focus']=1
78 self.__instructions_entry = DirectEntry(
79 scale=self._common['scale'],
80 pos=(l, 1, h - .2),
81 entryFont=self._font,
82 width=tw,
83 numLines=12,
84 cursorKeys=True,
85 frameColor=self._common['frameColor'],
86 initialText=json['instructions'].replace('\\n', '\n'),
87 parent=self._frm,
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):
92 colors = {
93 'gray': [
94 (.6, .6, .6, 1), # ready
95 (1, 1, 1, 1), # press
96 (.8, .8, .8, 1), # rollover
97 (.4, .4, .4, .4)],
98 'green': [
99 (.1, .68, .1, 1),
100 (.1, 1, .1, 1),
101 (.1, .84, .1, 1),
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)
105 DirectButton(
106 image=load_images_btn('exitRight', 'gray'), scale=.05,
107 pos=(.06, 1, .06),
108 parent=self._frm, command=self.__on_close, state=NORMAL, relief=FLAT,
109 frameColor=fcols[0],
110 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
111 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
112 DirectButton(
113 image=load_images_btn('save', 'gray'), scale=.05,
114 pos=(.06, 1, .18),
115 parent=self._frm, command=self.__on_save, state=NORMAL, relief=FLAT,
116 frameColor=fcols[0],
117 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
118 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
119 DirectButton(
120 image=load_images_btn('menuList', 'gray'), scale=.05,
121 pos=(.06, 1, .30),
122 parent=self._frm, command=self.__on_scene_list, state=NORMAL, relief=FLAT,
123 frameColor=fcols[0],
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 \
133 member != Item:
134 self.__new_items[member.__name__] = member
135 OnscreenText(
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)
141 DirectOptionMenu(
142 scale=.05,
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)
157
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']:
162 self._frm.destroy()
163 else:
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
179
180 def __on_new_item(self, item):
181 info(f'new {item}')
182 item_json = {}
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,
189 item_json)
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]
195
196 def __actually_close(self, arg):
197 if arg:
198 self._frm.destroy()
199 messenger.send('editor-stop')
200 self.__dialog.cleanup()
201
202 def __on_save(self):
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))
208
209 def __on_scene_list(self):
210 SceneList()
211
212 def __load_img_btn(self, path, col):
213 img = OnscreenImage('assets/images/buttons/%s.dds' % path)
214 img.set_transparency(True)
215 img.set_color(col)
216 img.detach_node()
217 return img
218
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]
226
227 def __on_item_click(self, item):
228 if self.__inspector and self.__inspector.item == item: return
229 if self.__inspector:
230 self.__inspector.destroy()
231 self.__inspector = Inspector(item, self.__items)
232
233 def __on_inspector_destroy(self):
234 self.__inspector = None