ya2 · news · projects · code · about

housekeeping: ya2.utils.gui
[pmachines.git] / ya2 / utils / gui / base_page.py
1 from dataclasses import dataclass
2 from panda3d.core import TextNode
3 from direct.gui.OnscreenText import OnscreenText
4 from direct.gui.DirectGui import DirectButton, DirectOptionMenu, DirectSlider, DirectCheckButton
5 from direct.gui.DirectGuiGlobals import FLAT
6 from ya2.utils.audio import AudioTools
7 from ya2.utils.gfx import DirectGuiMixin, GfxTools
8 from ya2.utils.gui.gui import GuiTools
9
10
11 class DirectOptionMenuTestable(DirectOptionMenu):
12
13 def __init__(self, parent=None, **kw):
14 DirectOptionMenu.__init__(self, parent, **kw)
15 self.initialiseoptions(DirectOptionMenuTestable)
16
17 def showPopupMenu(self, event=None):
18 #super().showPopupMenu(event) # it does not work with mixins
19 DirectOptionMenu.showPopupMenu(self, event)
20 self._show_cb([self.component(c) for c in self.components()])
21
22
23 @dataclass
24 class BaseInfo:
25 id: ... = ''
26 text: ... = None
27 pos: ... = None
28 scale: ... = None
29 wordwrap: ... = None
30 texture: ... = None
31 text_scale: ... = None
32 font: ... = None
33 color: ... = None
34
35 def to_p3d_args(self):
36 d = {}
37 d['text'] = self.text
38 d['pos'] = self.pos or (0, 1, 0)
39 d['scale'] = self.scale or .12
40 d['font'] = self.font or GuiTools.load_font('assets/fonts/Hanken-Book.ttf')
41 d['fg'] = self.color or (.9, .9, .9, 1)
42 return d
43
44
45 @dataclass
46 class ButtonInfo(BaseInfo):
47 command: ... = None
48 command_args: ... = None
49 size: ... = None
50 text_color: ... = None
51 relief: ... = None
52 rollover_sound: ... = None
53 click_sound: ... = None
54
55 def to_p3d_args(self):
56 d = super().to_p3d_args()
57 d['frameSize'] = self.size or (-4.8, 4.8, -.6, 1.2)
58 d['command'] = self.command
59 d['extraArgs'] = self.command_args or []
60 d['text_fg'] = self.text_color or d.pop('fg')
61 if 'fg' in d: del d['fg']
62 d['text_font'] = self.font or d.pop('font')
63 d['frameColor'] = self.color or (.4, .4, .4, .14)
64 d['relief'] = self.relief or FLAT
65 d['rolloverSound'] = self.rollover_sound or AudioTools.load_sfx('assets/audio/sfx/rollover.ogg')
66 d['clickSound'] = self.click_sound or AudioTools.load_sfx('assets/audio/sfx/click.ogg')
67 d['pos'] = (self.pos[0], 1, self.pos[1]) if len(self.pos) == 2 else self.pos
68 d['text_wordwrap'] = self.wordwrap
69 d['frameTexture'] = self.texture
70 d['text_scale'] = self.text_scale or 1
71 return d
72
73 def __build_gui_args(self):
74 base = {
75 'scale': .12,
76 'text_font': GuiTools.load_font('assets/fonts/Hanken-Book.ttf'),
77 'text_fg': (.9, .9, .9, 1),
78 'relief': FLAT,
79 'frameColor': (.4, .4, .4, .14),
80 'rolloverSound': AudioTools.load_sfx('assets/audio/sfx/rollover.ogg'),
81 'clickSound': AudioTools.load_sfx('assets/audio/sfx/click.ogg')}
82 button = {'size': (-4.8, 4.8, -.6, 1.2)} | base
83 return button
84
85
86 @dataclass
87 class SliderInfo(ButtonInfo):
88 range: ... = None
89 scale: ... = None
90 value: ... = None
91
92 def to_p3d_args(self):
93 d = super().to_p3d_args()
94 d['range'] = self.range or (0, 1)
95 d['thumb_frameColor'] = (.4, .4, .4, .4)
96 d['thumb_scale'] = 1.6
97 d['thumb_relief'] = d['relief']
98 d['scale'] = self.scale or .4
99 d['value'] = self.value or 0
100 del d['rolloverSound']
101 del d['clickSound']
102 del d['frameSize']
103 return d
104
105
106 @dataclass
107 class OptionMenuInfo(ButtonInfo):
108 items: ... = None
109 initial_item: ... = None
110 item_color: ... = None
111 popup_color: ... = None
112 item_relief: ... = None
113 item_text_font: ... = None
114 item_text_fg: ... = None
115 highlight_color: ... = None
116 text_align: ... = None
117
118 def to_p3d_args(self):
119 d = super().to_p3d_args()
120 d['items'] = self.items
121 d['initialitem'] = self.initial_item
122 d['item_frameColor'] = d['frameColor']
123 d['popupMarker_frameColor'] = d['frameColor']
124 d['popupMarker_relief'] = d['relief']
125 d['item_relief'] = d['relief']
126 d['item_text_font'] = d['text_font']
127 d['item_text_fg'] = d['text_fg']
128 d['textMayChange'] = 1
129 h = d['frameColor']
130 h = (h[0] + .2, h[1] + .2, h[2] + .2, h[3] + .2)
131 d['highlightColor'] = self.highlight_color or h
132 d['text_align'] = TextNode.A_center
133 d['frameSize'] = (f:=d['frameSize'])[0], f[1] - .56, f[2], f[3]
134 return d
135
136
137 @dataclass
138 class CheckButtonInfo(ButtonInfo):
139 value: ... = None
140
141 def to_p3d_args(self):
142 d = super().to_p3d_args()
143 h = d['frameColor']
144 h = (h[0] + .2, h[1] + .2, h[2] + .2, h[3] + .2)
145 d['indicator_frameColor'] = h
146 d['indicator_relief'] = d['relief']
147 d['indicatorValue'] = self.value or 0
148 return d
149
150
151 @dataclass
152 class TextInfo(BaseInfo):
153 align: ... = None
154
155 def to_p3d_args(self):
156 d = super().to_p3d_args()
157 if self.align:
158 a = {'left': TextNode.A_left,
159 'right': TextNode.A_right,
160 'center': TextNode.A_center}[self.align]
161 d['align'] = a
162 return d
163
164
165 class BasePage:
166
167 def __init__(self, page_info):
168 self.__widgets = []
169 self._page_info = page_info
170 for k in list(self._page_info.test_positions.keys()):
171 del self._page_info.test_positions[k]
172 self._build_widgets()
173
174 def _add_text(self, text_info):
175 t = OnscreenText(
176 **text_info.to_p3d_args())
177 self.__widgets += [t]
178 return t
179
180 def _add_button(self, button_args, vertically_centered=False):
181 b = DirectButton(**button_args.to_p3d_args())
182 b.__class__ = type('DirectButtonMixed', (DirectButton, DirectGuiMixin), {})
183 self._page_info.test_positions[button_args.id] = b.pos_pixel()
184 self.__widgets += [b]
185 if vertically_centered:
186 for j in range(4):
187 tnode = b.component('text%s' % j).textNode
188 height = - tnode.getLineHeight() / 2
189 height += (tnode.get_height() - tnode.get_line_height()) / 2
190 b.component('text%s' % j).set_pos(0, 0, height)
191 return b
192
193 def _add_check_button(self, button_args):
194 b = DirectCheckButton(**button_args.to_p3d_args())
195 b.__class__ = type('DirectCheckButtonMixed', (DirectCheckButton, DirectGuiMixin), {})
196 self.__widgets += [b]
197 self._page_info.test_positions[button_args.id] = b.pos_pixel()
198 return b
199
200 def _add_option_menu(self, option_args, cb):
201 o = DirectOptionMenuTestable(**option_args.to_p3d_args())
202 o.__class__ = type('DirectOptionMenuMixed', (DirectOptionMenu, DirectGuiMixin), {})
203 o._show_cb = cb
204 o.popupMenu['frameColor'] = option_args.to_p3d_args()['frameColor']
205 o.popupMenu['relief'] = option_args.to_p3d_args()['relief']
206 self.__widgets += [o]
207 self._page_info.test_positions[option_args.id] = o.pos_pixel()
208 return o
209
210 def _add_slider(self, slider_args):
211 s = DirectSlider(**slider_args.to_p3d_args())
212 s.__class__ = type('DirectSliderMixed', (DirectSlider, DirectGuiMixin), {})
213 self._page_info.test_positions['volume'] = s.pos_pixel()
214 np_left = GfxTools.build_empty_node('left_slider')
215 np_left.set_pos(s.get_net_transform().get_pos())
216 np_left.set_x(np_left.get_x() - s.get_scale()[0])
217 lpos = np_left.pos_as_widget() # try with pos2d_pixel and remove pos_as_widget if ok
218 self.__widgets += [s]
219 self._page_info.test_positions['volume_0'] = lpos
220 return s
221
222 def _set_page(self, page_name):
223 self._page_info.set_page(page_name)
224 self.destroy()
225
226 def on_back(self):
227 self._set_page('main')
228
229 def _rearrange_buttons_width(self):
230 max_width = 0
231 for w in self.__widgets:
232 t = w.component('text0')
233 ul = t.textNode.get_upper_left_3d()
234 lr = t.textNode.get_lower_right_3d()
235 max_width = max(lr[0] - ul[0], max_width)
236 for w in self.__widgets:
237 m_w = max_width / 2 + .8
238 w['frameSize'] = -m_w, m_w, w['frameSize'][2], w['frameSize'][3]
239
240 def destroy(self):
241 for w in self.__widgets: w.destroy()
242 self._page_info = None
243 self.__widgets = []