ya2 · news · projects · code · about

4a5a1d9ffd07970e808b8e7f753744ce661c793f
[pmachines.git] / pmachines / editor / scene.py
1 from copy import deepcopy
2 from json import dumps
3 import hashlib
4 from panda3d.core import Texture, TextNode
5 from direct.gui.OnscreenImage import OnscreenImage
6 from direct.gui.DirectGui import DirectButton, DirectFrame, DirectEntry, YesNoDialog
7 from direct.gui.DirectGuiGlobals import FLAT, NORMAL
8 from direct.gui.OnscreenText import OnscreenText
9 from direct.showbase.DirectObject import DirectObject
10 from pmachines.editor.scene_list import SceneList
11 from pmachines.editor.inspector import Inspector
12
13
14 class SceneEditor(DirectObject):
15
16 def __init__(self, json, json_name):
17 super().__init__()
18 self.__json = json
19 self.__json_name = json_name
20 self.__inspector = None
21 self._font = base.loader.load_font(
22 'assets/fonts/Hanken-Book.ttf')
23 self._font.clear()
24 self._font.set_pixels_per_unit(60)
25 self._font.set_minfilter(Texture.FTLinearMipmapLinear)
26 self._font.set_outline((0, 0, 0, 1), .8, .2)
27 self._common = {
28 'scale': .046,
29 'text_font': self._font,
30 'text_fg': (.9, .9, .9, 1),
31 'relief': FLAT,
32 'frameColor': (.4, .4, .4, .14),
33 'rolloverSound': loader.load_sfx(
34 'assets/audio/sfx/rollover.ogg'),
35 'clickSound': loader.load_sfx(
36 'assets/audio/sfx/click.ogg')}
37 w, h, tw, l = 1.8, .9, 30, .36
38 self._frm = DirectFrame(frameColor=(.4, .4, .4, .06),
39 frameSize=(0, w, 0, h),
40 parent=base.a2dBottomCenter,
41 pos=(-w/2, 0, 0))
42 OnscreenText(
43 _('Name'), pos=(l - .03, h - .1), parent=self._frm,
44 font=self._common['text_font'],
45 scale=self._common['scale'],
46 fg=self._common['text_fg'],
47 align=TextNode.A_right)
48 self.__name_entry = DirectEntry(
49 scale=self._common['scale'],
50 pos=(l, 1, h - .1),
51 entryFont=self._font,
52 width=tw,
53 frameColor=self._common['frameColor'],
54 initialText=json['name'],
55 parent=self._frm,
56 text_fg=self._common['text_fg'])
57 OnscreenText(
58 _('Description'), pos=(l - .03, h - .2), parent=self._frm,
59 font=self._common['text_font'],
60 scale=self._common['scale'],
61 fg=self._common['text_fg'],
62 align=TextNode.A_right)
63 def add_line_break(txt, entry):
64 curpos = entry.node().getCursorPosition()
65 entry.set(txt[:curpos]+ "\n" + txt[curpos:])
66 entry.node().setCursorPosition(curpos+1)
67 entry['focus']=1
68 self.__instructions_entry = DirectEntry(
69 scale=self._common['scale'],
70 pos=(l, 1, h - .2),
71 entryFont=self._font,
72 width=tw,
73 numLines=12,
74 cursorKeys=True,
75 frameColor=self._common['frameColor'],
76 initialText=json['instructions'].replace('\\n', '\n'),
77 parent=self._frm,
78 text_fg=self._common['text_fg'])
79 self.__instructions_entry['command'] = add_line_break
80 self.__instructions_entry['extraArgs'] = [self.__instructions_entry]
81 def load_images_btn(path, col):
82 colors = {
83 'gray': [
84 (.6, .6, .6, 1), # ready
85 (1, 1, 1, 1), # press
86 (.8, .8, .8, 1), # rollover
87 (.4, .4, .4, .4)],
88 'green': [
89 (.1, .68, .1, 1),
90 (.1, 1, .1, 1),
91 (.1, .84, .1, 1),
92 (.4, .1, .1, .4)]}[col]
93 return [self.__load_img_btn(path, col) for col in colors]
94 fcols = (.4, .4, .4, .14), (.3, .3, .3, .05)
95 DirectButton(
96 image=load_images_btn('exitRight', 'gray'), scale=.05,
97 pos=(.06, 1, .06),
98 parent=self._frm, command=self.__on_close, state=NORMAL, relief=FLAT,
99 frameColor=fcols[0],
100 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
101 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
102 DirectButton(
103 image=load_images_btn('save', 'gray'), scale=.05,
104 pos=(.06, 1, .18),
105 parent=self._frm, command=self.__on_save, state=NORMAL, relief=FLAT,
106 frameColor=fcols[0],
107 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
108 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
109 DirectButton(
110 image=load_images_btn('menuList', 'gray'), scale=.05,
111 pos=(.06, 1, .30),
112 parent=self._frm, command=self.__on_scene_list, state=NORMAL, relief=FLAT,
113 frameColor=fcols[0],
114 rolloverSound=loader.load_sfx('assets/audio/sfx/rollover.ogg'),
115 clickSound=loader.load_sfx('assets/audio/sfx/click.ogg'))
116 messenger.send('editor-start')
117 self.accept('editor-item-click-l', self.__on_item_click_l)
118 self.accept('editor-inspector-destroy', self.__on_inspector_destroy)
119
120 def __on_close(self):
121 self.__json['name'] = self.__name_entry.get()
122 self.__json['instructions'] = self.__instructions_entry.get()
123 if self.__compute_hash() == self.__json['version']:
124 self._frm.destroy()
125 else:
126 self.__dialog = YesNoDialog(dialogName='Unsaved changes',
127 text=_('You have unsaved changes. Really quit?'),
128 command=self.__actually_close)
129 self.__dialog['frameColor'] = (.4, .4, .4, .14)
130 self.__dialog['relief'] = FLAT
131 self.__dialog.component('text0')['fg'] = (.9, .9, .9, 1)
132 self.__dialog.component('text0')['font'] = self._font
133 for b in self.__dialog.buttonList:
134 b['frameColor'] = (.4, .4, .4, .14)
135 b.component('text0')['fg'] = (.9, .9, .9, 1)
136 b.component('text0')['font'] = self._font
137 b.component('text1')['fg'] = (.9, .1, .1, 1)
138 b.component('text1')['font'] = self._font
139 b.component('text2')['fg'] = (.9, .9, .1, 1)
140 b.component('text2')['font'] = self._font
141
142 def __actually_close(self, arg):
143 if arg:
144 self._frm.destroy()
145 messenger.send('editor-stop')
146 self.__dialog.cleanup()
147
148 def __on_save(self):
149 self.__json['name'] = self.__name_entry.get()
150 self.__json['instructions'] = self.__instructions_entry.get()
151 self.__json['version'] = self.__compute_hash()
152 with open('assets/scenes/%s.json' % self.__json_name, 'w') as f:
153 f.write(dumps(self.__json, indent=2, sort_keys=True))
154
155 def __on_scene_list(self):
156 SceneList()
157
158 def __load_img_btn(self, path, col):
159 img = OnscreenImage('assets/images/buttons/%s.dds' % path)
160 img.set_transparency(True)
161 img.set_color(col)
162 img.detach_node()
163 return img
164
165 def __compute_hash(self):
166 new_dict = deepcopy(self.__json)
167 del new_dict['version']
168 new_dict_str = str(dumps(new_dict, indent=2, sort_keys=True))
169 h = hashlib.new('sha256')
170 h.update(new_dict_str.encode())
171 return h.hexdigest()[:12]
172
173 def __on_item_click_l(self, item):
174 if self.__inspector and self.__inspector.item == item: return
175 if self.__inspector:
176 self.__inspector.destroy()
177 self.__inspector = Inspector(item)
178
179 def __on_inspector_destroy(self):
180 self.__inspector = None