-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathDirectBoxSizer.py
More file actions
338 lines (297 loc) · 12.8 KB
/
Copy pathDirectBoxSizer.py
File metadata and controls
338 lines (297 loc) · 12.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
"""This module contains the DirectBoxSizer class."""
__all__ = ['DirectBoxSizer']
from panda3d.core import *
from direct.gui import DirectGuiGlobals as DGG
from direct.gui.DirectFrame import DirectFrame
from . import DirectGuiHelper as DGH
DGG.HORIZONTAL_INVERTED = 'horizontal_inverted'
class DirectItemContainer():
def __init__(self, element, **kw):
self.element = element
self.updateFunc = None
if "updateFunc" in kw:
self.updateFunc = kw.get("updateFunc")
class DirectBoxSizer(DirectFrame):
"""
A frame to add multiple other directGui elements to that will then be
automatically be placed stacked next to each other.
"""
# Horizontal
A_Center = 0b1
A_Left = 0b10
A_Right = 0b100
# Vertical
A_Middle = 0b1000
A_Top = 0b10000
A_Bottom = 0b100000
def __init__(self, parent = None, **kw):
self.skipInitRefresh = True
optiondefs = (
# Define type of DirectGuiWidget
('items', [], self.refresh),
('pgFunc', PGItem, None),
('numStates', 1, None),
('state', DGG.NORMAL, None),
('borderWidth', (0, 0), self.setBorderWidth),
('orientation', DGG.HORIZONTAL, self.refresh),
('itemMargin', (0,0,0,0), self.refresh),
('itemAlign', self.A_Left|self.A_Top, self.refresh),
('autoUpdateFrameSize', True, None),
('suppressMouse', 0, None),
)
# Merge keyword options with default options
self.defineoptions(kw, optiondefs)
# Initialize superclasses
DirectFrame.__init__(self, parent)
# Call option initialization functions
self.initialiseoptions(DirectBoxSizer)
self.skipInitRefresh = False
# initialize once at the end
self.refresh()
def addItem(self, element, **kw):
"""
Adds the given item to this panel stack
"""
element.reparentTo(self)
container = DirectItemContainer(element, **kw)
self["items"].append(container)
if "skipRefresh" in kw:
return
self.refresh()
def removeItem(self, element, refresh=True):
"""
Remove this item from the panel
"""
for item in self["items"]:
if element == item.element:
self["items"].remove(item)
if refresh:
self.refresh()
return 1
return 0
def removeAllItems(self, refresh=True, removeNodes=False):
"""
Remove all items from the panel
"""
for item in list(self["items"]):
self["items"].remove(item)
if removeNodes:
item.element.removeNode()
if refresh:
self.refresh()
def getRemainingSpace(self):
"""
Gives the space that is left when all items have been placed in the
sizer.
"""
if not hasattr(self, "bounds"): return 0
if self['orientation'] == DGG.HORIZONTAL \
or self['orientation'] == DGG.HORIZONTAL_INVERTED:
# Horizontal
width = self.__get_items_width()
return DGH.getRealWidth(self) / self.getScale().x - width
elif self['orientation'] == DGG.VERTICAL \
or self['orientation'] == DGG.VERTICAL_INVERTED:
height = self.__get_items_height()
return DGH.getRealHeight(self) / self.getScale().z - height
def refresh(self):
"""
Recalculate the position of every item in this panel and set the frame-
size of the panel accordingly if auto update is enabled.
"""
# do a normal refresh to handle all normal directGUI widgets
self._refresh()
# DirectAutoSizers might have been updated by a window resize, so we have to refresh again after that
self.doMethodLater(0, self._refresh, "refresh", extraArgs=[])
def _refresh(self):
"""
Recalculate the position of every item in this panel and set the frame-
size of the panel accordingly if auto update is enabled.
"""
if self.skipInitRefresh: return
# sanity check so we don't get here to early
if not hasattr(self, "bounds") and not self["autoUpdateFrameSize"]: return
if not hasattr(self, "_optionInfo"): return
if len(self["items"]) == 0: return
for item in self["items"]:
item.element.frameInitialiseFunc()
self.__refresh_frame_size()
#
# Update Item Positions
#
if self['orientation'] == DGG.HORIZONTAL:
# Horizontal - Left to Right placement
self.__refresh_horizontal_ltr()
elif self['orientation'] == DGG.HORIZONTAL_INVERTED:
# Horizontal - Right to Left
self.__refresh_horizontal_rtl()
elif self['orientation'] == DGG.VERTICAL:
# Vertical - Top to Bottom
self.__refresh_vertical_ttb()
elif self['orientation'] == DGG.VERTICAL_INVERTED:
# Vertical - Bottom to Top
self.__refresh_vertical_btt()
else:
raise ValueError('Invalid value for orientation: %s' % (self['orientation']))
for item in self["items"]:
if item.updateFunc is not None:
item.updateFunc()
def __refresh_frame_size(self):
if not self["autoUpdateFrameSize"]:
return
width = self.__get_items_width()# + self["pad"][0]*2
height = self.__get_items_height()# + self["pad"][1]*2
# dependent on orientation, start at 0 and extend to the
# maximum height or width and keep the respective other
# direction centered
if self['orientation'] == DGG.HORIZONTAL:
self["frameSize"] = (0, width, -height/2, height/2)
elif self['orientation'] == DGG.HORIZONTAL_INVERTED:
self["frameSize"] = (-width, 0, -height/2, height/2)
elif self['orientation'] == DGG.VERTICAL:
self["frameSize"] = (-width/2, width/2, -height, 0)
elif self['orientation'] == DGG.VERTICAL_INVERTED:
self["frameSize"] = (-width/2, width/2, 0, height)
def __get_items_width(self):
'''
Get the maximum item width
'''
width = 0
for item in self["items"]:
item_width = (
DGH.getRealWidth(item.element)
+ self["itemMargin"][0] # margin left
+ self["itemMargin"][1]) # margin right
if self['orientation'] in [DGG.VERTICAL, DGG.VERTICAL_INVERTED]:
# look for the widest item
width = max(width, item_width)
else:
# add up all item widths
width += item_width
return width
def __get_items_height(self):
'''
Get the maximum item height
'''
height = 0
for item in self["items"]:
item_height = (
DGH.getRealHeight(item.element)
+ self["itemMargin"][3] # margin top
+ self["itemMargin"][2]) # margin bottom
if self['orientation'] in [DGG.HORIZONTAL, DGG.HORIZONTAL_INVERTED]:
# look for the talest item
height = max(height, item_height)
else:
# add up all item heights
height += item_height
return height
#
# ITEM ORDER POSITION REFRESH
#
# HORIZONTAL
def __refresh_horizontal_ltr(self):
# Horizontal - Left to Right placement
# get the left side of the box sizer frame
nextX = DGH.getRealLeft(self) / self.getScale().x
itemMargin = self["itemMargin"]
# go through all items in the box and place them
for item in self["items"]:
# place the element and calculate the next x position
y = self.__get_vertical_item_alignment(item.element)
item.element.setPos(nextX - DGH.getRealLeft(item.element) + itemMargin[0], 0, y)
nextX += DGH.getRealWidth(item.element) + itemMargin[1] + itemMargin[0]
def __refresh_horizontal_rtl(self):
# Horizontal - Right to Left
# get the right side of the box sizer frame
nextX = DGH.getRealRight(self) / self.getScale().x
itemMargin = self["itemMargin"]
# go through all items in the box and place them
for item in self["items"]:
# place the element and calculate the next x position
y = self.__get_vertical_item_alignment(item.element)
item.element.setPos(nextX - DGH.getRealRight(item.element) - itemMargin[1], 0, y)
nextX -= DGH.getRealWidth(item.element) + itemMargin[1] + itemMargin[0]
# VERTICAL
def __refresh_vertical_ttb(self):
# Vertical - Top to Bottom
# get the top side of the box sizer frame
nextY = DGH.getRealTop(self) / self.getScale().z
itemMargin = self["itemMargin"]
# go through all items in the box and place them
for item in self["items"]:
# place the element and calculate the next y position
x = self.__get_horizontal_item_alignment(item.element)
item.element.setPos(x, 0, nextY - DGH.getRealTop(item.element) - itemMargin[3])
nextY -= DGH.getRealHeight(item.element) + itemMargin[2] + itemMargin[3]
def __refresh_vertical_btt(self):
# Vertical - Bottom to Top
# get the bottom side of the box sizer frame
nextY = DGH.getRealBottom(self) / self.getScale().z
itemMargin = self["itemMargin"]
# go through all items in the box and place them
for item in self["items"]:
# place the element and calculate the next y position
x = self.__get_horizontal_item_alignment(item.element)
item.element.setPos(x, 0, nextY - DGH.getRealBottom(item.element) + itemMargin[2])
nextY += DGH.getRealHeight(item.element) + itemMargin[2] + itemMargin[3]
#
# ITEM ALIGN POSITION CALCULATIONS
#
def __get_horizontal_item_alignment(self, curElem):
itemMargin = self["itemMargin"]
# Horizontal Alingment
if self["itemAlign"] & self.A_Left:
# get the left side of the frame
x = self["frameSize"][0]
# shift x right to be aligned with the items left side
x -= DGH.getRealLeft(curElem)
x += itemMargin[0]
return x
elif self["itemAlign"] & self.A_Right:
# get the right side of the frame
x = self["frameSize"][1]
# shift x left to be aligned with the items right side
x -= DGH.getRealRight(curElem)
x -= itemMargin[0]
return x
elif self["itemAlign"] & self.A_Center:
# aligned by the center of the frame
self_l = DGH.getRealLeft(self) / self.getScale().x
self_r = DGH.getRealRight(self) / self.getScale().z
x = (self_l + self_r) / 2
# shift x by items center shift
item_l = DGH.getRealLeft(curElem)
item_r = DGH.getRealRight(curElem)
x += (item_l + item_r) / 2
x -= itemMargin[0]
return x
return 0
def __get_vertical_item_alignment(self, curElem):
itemMargin = self["itemMargin"]
# Vertical Alingment
if self["itemAlign"] & self.A_Bottom:
# Vertical adjustment to the box' size
y = DGH.getRealBottom(self) / self.getScale().z # self["frameSize"][2]
# shift y up to be aligned with the items bottom side
y += DGH.getRealBottom(curElem)
return y
elif self["itemAlign"] & self.A_Top:
# Items are alligned by their upper edge
y = DGH.getRealTop(self) / self.getScale().z # self["frameSize"][3]
# shift y down to be aligned with the items top side
y -= DGH.getRealTop(curElem)
y -= itemMargin[3]
return y
elif self["itemAlign"] & self.A_Middle:
# Items are alligned by their center
# aligned by the center of the frame
self_t = DGH.getRealTop(self) / self.getScale().z
self_b = DGH.getRealBottom(self) / self.getScale().z
y = (self_t + self_b) / 2
# shift y by items center shift
item_t = DGH.getRealTop(curElem)
item_b = DGH.getRealBottom(curElem)
y += (item_t + item_b) / 2
return y
return 0