-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathdearpygui_grid.py
1672 lines (1351 loc) · 64.7 KB
/
dearpygui_grid.py
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
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import sys
import numbers
import functools
import threading
import dataclasses
import math
from array import array
import dearpygui.dearpygui as dearpygui
from dearpygui._dearpygui import (
get_item_configuration as _item_get_config,
get_item_state as _item_get_state,
configure_item as _item_set_config,
)
from typing import (
Any,
Protocol,
Callable,
Literal,
Sequence,
Generic,
Iterator,
SupportsIndex,
Sized,
Iterable,
Annotated,
TypeVar,
overload,
cast,
)
from typing_extensions import Self
_T = TypeVar('_T')
_N = TypeVar('_N', bound=numbers.Real)
Item = int | str
FloatV = float | None
Vec2 = Annotated[tuple[FloatV, FloatV] | Sequence[FloatV], Literal[2]]
Vec4 = Annotated[tuple[FloatV, FloatV, FloatV, FloatV] | Sequence[FloatV], Literal[4]]
NaN = math.nan
# [ HELPER FUNCTIONS ]
def _create_context():
# runs once, then no-op
dearpygui.create_context()
@functools.wraps(_create_context)
def create_context(): ...
setattr(sys.modules[__name__], _create_context.__name__, create_context)
def _is_nan(v: Any) -> bool:
return v != v
def _is_nanlike(v: Any) -> bool:
return v is None or v != v
def _to_value(value: Any, default: Any):
if value is None or _is_nan(value):
return default
return value
def _to_float_arr(value: Sequence[float | None] | float | None, length: int, default: float = NaN) -> 'array[float]':
"""
# Non-Sequence value (None | float('nan'))
>>> arr = _to_float_array(None, 2)
>>> len(arr) == 2 and all(_is_nan(v) for v in arr)
True
>>> arr = _to_float_array(float("nan"), 2)
>>> len(arr) == 2 and all(_is_nan(v) for v in arr)
True
# Non-Sequence value (number)
>>> arr = _to_float_array(20, 4)
>>> len(arr) == 4 and all(v == 20 for v in arr)
True
# Sequence[float | nan | None], len(value) >= 4
>>> arr = _to_float_array([20, None, float("nan"), 10, 0, 0], 4, -5.0) # 6-len input
>>> tuple(arr) == (20, -5.0, -5.0, 10)
True
# Sequence[float | float('nan') | None], len(value) < 4
>>> arr = _to_float_array([20, None], 4, -5.0)
>>> tuple(arr) == (20, -5.0, -5.0, -5.0)
True
"""
if value is None or _is_nan(value):
return array('f', (default,) * length)
if isinstance(value, (float, int)):
return array('f', (value,) * length)
arr = array('f', (default,) * length)
try:
for i in range(length):
arr[i] = _to_value(value[i], default)
except IndexError:
pass
return arr
# [ MINOR COMPONENTS ] (major components/helpers of minor ones)
class _RectGetter(Protocol):
"""A function that returns an item's size and position; as
`(width, height, x_pos, y_pos, is_visible)`.
"""
def __call__(self, item: Item) -> tuple[int, int, int, int, bool]: ...
def _get_item_rect(item: Item) -> tuple[int, int, int, int, bool]:
d = _item_get_state(item)
if 'visible' in d:
return *d['rect_size'], *d['pos'], d['visible'] # type: ignore
return *d['rect_size'], *d['pos'], _item_get_config(item)['show'] # type: ignore
class _RectSetter(Protocol):
"""A function that updates an item's size and position via
`dearpygui.configure_item`.
"""
def __call__(self, item: Item, x_pos: int, y_pos: int, width: int, height: int, show: bool) -> Any: ...
def _set_item_rect(item: Item, x_pos: int, y_pos: int, width: int, height: int, show: bool):
_item_set_config(
item,
width=int(width),
height=int(height),
pos=(int(x_pos), int(y_pos)),
show=show,
)
def _set_text_rect(item: Item, x_pos: int, y_pos: int, width: int, height: int, show: bool):
_item_set_config(
item,
pos=(int(x_pos), int(y_pos)),
show=show,
)
class _Positioner(Protocol):
"""Calculates the final position of an item in the occupying cell
or cellspan.
"""
def __call__(self, item_wt: float, item_ht: float, cell_x: float, cell_y: float, cell_wt: float, cell_ht: float, /) -> tuple[float, float]: ...
_ANCHOR_MAP: dict[str, _Positioner] = {}
def _get_positioner(key: str):
try:
return _ANCHOR_MAP[key.casefold()]
except AttributeError:
return _ANCHOR_MAP[key] # raise `KeyError`
def _anchor_position(*anchors: str) -> Callable[[_Positioner], _Positioner]:
def register_anchor_fn(fn: _Positioner) -> _Positioner:
for s in anchors:
_ANCHOR_MAP[s.lower()] = fn # type: ignore
return fn
return register_anchor_fn
@_anchor_position("n", "north")
def _anchor_position_N(item_wt: float, item_ht: float, cell_x: float, cell_y: float, cell_wt: float, cell_ht: float) -> tuple[float, float]:
return (cell_wt - item_wt) / 2 + cell_x, cell_y # center x
@_anchor_position("ne", "northeast")
def _anchor_position_NE(item_wt: float, item_ht: float, cell_x: float, cell_y: float, cell_wt: float, cell_ht: float) -> tuple[float, float]:
return (cell_wt - item_wt) + cell_x, cell_y
@_anchor_position("e", "east")
def _anchor_position_E(item_wt: float, item_ht: float, cell_x: float, cell_y: float, cell_wt: float, cell_ht: float) -> tuple[float, float]:
return (cell_wt - item_wt) + cell_x, (cell_ht - item_ht) / 2 + cell_y # center y
@_anchor_position("se", "southeast")
def _anchor_position_SE(item_wt: float, item_ht: float, cell_x: float, cell_y: float, cell_wt: float, cell_ht: float) -> tuple[float, float]:
return (cell_wt - item_wt) + cell_x, (cell_ht - item_ht) + cell_y
@_anchor_position("s", "south")
def _anchor_position_S(item_wt: float, item_ht: float, cell_x: float, cell_y: float, cell_wt: float, cell_ht: float) -> tuple[float, float]:
return (cell_wt - item_wt) / 2 + cell_x, (cell_ht - item_ht) + cell_y # center x
@_anchor_position("sw", "southwest")
def _anchor_position_SW(item_wt: float, item_ht: float, cell_x: float, cell_y: float, cell_wt: float, cell_ht: float) -> tuple[float, float]:
return cell_x, (cell_ht - item_ht) + cell_y
@_anchor_position("w", "west")
def _anchor_position_W(item_wt: float, item_ht: float, cell_x: float, cell_y: float, cell_wt: float, cell_ht: float) -> tuple[float, float]:
return cell_x, (cell_ht - item_ht) / 2 + cell_y # center y
@_anchor_position("nw", "northwest")
def _anchor_position_NW(item_wt: float, item_ht: float, cell_x: float, cell_y: float, cell_wt: float, cell_ht: float) -> tuple[float, float]:
return cell_x, cell_y
@_anchor_position("c", "center", "centered")
def _anchor_position_C(item_wt: float, item_ht: float, cell_x: float, cell_y: float, cell_wt: float, cell_ht: float) -> tuple[float, float]:
return (cell_wt - item_wt) / 2 + cell_x, (cell_ht - item_ht) / 2 + cell_y # center x & y
@dataclasses.dataclass(slots=True)
class ItemData:
"""Contains the size and placement information of an item attached
to a `Grid` object.
Attributes:
* item (int | str): The target item's integer uuid or string alias.
* cellspan (tuple[int, int, int, int]): Contains coordinates for
the target cell range, as `(col_start, row_start, col_end, row_end)`.
* max_size (tuple[int, int]): Indicates the item's maximum width
and height (min 0 per value).
* padding (tuple[float, float, float, float]): Top-level padding
override values.
* positioner (_Positioner): Function used to calculate the item's
final position.
* rect_setter (_RectSetter): Callable used to update the item's size,
position, and visibility status.
* is_text (bool): If True, the target item an `mvText` object.
"""
item : Item
cellspan : tuple[int, int, int, int]
max_size : Sequence[float]
padding : Sequence[float]
positioner : _Positioner
rect_setter: _RectSetter
is_text : bool
def __hash__(self):
return hash(self.item)
# [ MAJOR COMPONENTS ]
class _GridSetting(Generic[_T]):
__slots__ = ("_key",)
def __init__(self, key: str = '', /):
self._key = key
def __set_name__(self, cls: type, name: str):
self._key = self._key or name
@overload
def __get__(self, inst: Any, cls: type | None = ...) -> _T: ...
@overload
def __get__(self, inst: None, cls: type | None = None) -> Self: ...
def __get__(self, inst: Any, cls: Any = None):
if inst is None:
return self
return inst.configuration()[self._key]
def __set__(self, inst: Any, value: Any):
inst.configure(**{self._key: value})
@dataclasses.dataclass(init=False)
class _GridComponent:
__slots__ = ('_label', '_spacing', '_padding')
label : _GridSetting[str] = dataclasses.field(default=_GridSetting('label'))
spacing: _GridSetting['array[float]'] = dataclasses.field(default=_GridSetting('spacing'))
padding: _GridSetting['array[float]'] = dataclasses.field(default=_GridSetting('padding'))
def __init__(self, *, label: str | None = None, spacing: FloatV = None, padding: Vec2 | FloatV = None, **kwargs):
self.configure(label=label, spacing=spacing, padding=padding, **kwargs)
def configure(self, **kwargs) -> None:
"""Update the object's high-level settings.
Accepts all arguments accepted by the object's constructor
as keyword arguments.
"""
if 'label' in kwargs:
self._label = kwargs['label'] or ''
if 'padding' in kwargs:
self._padding = _to_float_arr(kwargs['padding'], 2, NaN)
if 'spacing' in kwargs:
spacing = _to_value(kwargs['spacing'], None)
if spacing is None:
spacing = NaN
else:
spacing = max(0.0, spacing)
self._spacing = spacing
def configuration(self) -> dict[str, Any]:
"""Return a mapping of the object's settings and their current values."""
return {f:getattr(self, f'_{f}') for f in self.__dataclass_fields__}
@dataclasses.dataclass(init=False)
class Slot(_GridComponent):
"""A row or column in a table-like structure.
Attributes:
* label: Informal name for the slot.
* spacing (float): Used to offset the slot's position during a draw
event. Is ignored for calculations when NaN.
* padding (array[float, float]): Used as the left/upper and right/lower
(for columns and rows respectively) padding values for cells in the slot
during a draw event. Inner NaN values are ignored for calculations.
* weight (float): During a draw event and when the slot's policy is
evaluated as "sized", this affects how much the slot is scaled
in proportion to the size of the grid and the total weight of all
other slots in the axis.
* size (int): The width/height (for columns and rows respectively) of
the slot. When this value is not 0, a "fixed" sizing policy is enforced
upon the slot, disabling dynamic resizing.
A slot's sizing policy is not set, but determined during a draw event
based on the slot's state. A "fixed" policy is enforced whenever a slot's
`.size` attribute is greater than 0, and uses a "sized" policy otherwise.
When "fixed", a slot is ensured to always be drawn to it's `.size`.
A slot is unaware of its' role in the grid (column vs row). A higher-level
object may indicate this through its' `.label` attribute.
"""
__slots__ = ('_state', '_size', '_weight')
weight: _GridSetting[float] = dataclasses.field(default=_GridSetting('weight'))
size : _GridSetting[int] = dataclasses.field(default=_GridSetting('size'))
@overload
def __init__(self, *, label: str = ..., spacing: FloatV = ..., padding: Vec2 | FloatV = ..., weight: FloatV = ..., size: FloatV = ...) -> None: ... # type: ignore
def __init__(self, *, weight: Any = 1.0, size: Any = 0, **kwargs) -> None:
"""Args:
* label: Used to update `self.label`. None is treated as ''.
* spacing: Used to update `self.spacing`. A minimum 0 is used
for non-NaN values. None is treated as NaN.
* padding: Used to update `self.padding`. When the value is a
sequence, the first two items of the sequence are used as the
left/upper and right/lower (for columns and rows respectively)
padding values, where inner None values are treated as NaN.
Otherwise, the value is applied to all padding values while
treating NaN and None as `(NaN, NaN)`.
* weight: Used to update `self.weight`. A minimum of 0 is used
for non-NaN values. None is treated as NaN.
* size: Used to update `self.size`. A minimum of 0 is used for
non-NaN values. None is treated as NaN.
"""
# managed by the parenting `Grid` during a draw event
self._state = cast(
dict[Literal['pos' ,'size', 'spacing', 'padding'], Any],
{
'pos' : 0,
'size' : 0,
'spacing': 0,
'padding': (0, 0),
}
)
super().__init__(weight=weight, size=size, **kwargs)
@overload
def configure(self, *, label: str = ..., spacing: FloatV = ..., padding: Vec2 | FloatV = ..., weight: FloatV = ..., size: FloatV = ...) -> None: ... # type: ignore
def configure(self, **kwargs) -> None:
if 'weight' in kwargs:
self._weight = max(0.0, _to_value(kwargs['weight'], 0.0))
if 'size' in kwargs:
self._size = max(0, int(_to_value(kwargs['size'], 0)))
super().configure(**kwargs)
def configuration(self) -> dict[Literal['label', 'spacing', 'padding', 'weight', 'size'], Any]: ...
del configuration
@dataclasses.dataclass(init=False)
class Axis(_GridComponent, Iterable[Slot], Sized):
"""A collection of rows or columns.
Attributes:
* label (str): Informal name for the axis.
* spacing (float): Used as a fallback value to offset the slot's
position during a draw event. Is ignored for calculations when NaN.
* padding (array[float, float]): Fallback values used during a draw
event when a slot contained in the axis does not specify a padding
value. Inner NaN values are ignored for calculations.
* length (int): Evaluates to `len(self)`. Setting a value resizes
the length of the axis; adding or removing slots as necessary.
Instances are treated as immutable, unsigned pseudo-integrals
for most operations; the value of which is a representation
of the number of slots in the axis. However, in-place operations
directly affect the number of slots contained.
Axes objects behave similar to lists. Slots are identified by their
positions (index) in the axis. Indexing the axis returns the `Slot`
object for that row or column.
When the axis' number of slots is changed, slots are always appended
to, or are removed from, the end of the axis.
Operations that update the number of slots in the axis are
thread-safe.
An axis object is unaware of its' role in the grid i.e. x
(columns) vs y (rows). A higher-level object may indicate this
through its' `.label` attribute.
"""
__slots__ = ('_slots', '_lock')
length: _GridSetting[int] = dataclasses.field(default=_GridSetting('length'))
@overload
def __init__(self, length: int = ..., *, label: str = ..., spacing: FloatV = ..., padding: Vec2 | FloatV = ...) -> None: ... # type: ignore
def __init__(self, length: int = 0, **kwargs) -> None:
"""Args:
* length: Integer value used to update `self.length`.
* label: Used to update `self.label`. None is treated as ''.
* spacing: Used to update `self.spacing`. A minimum 0 is used
for non-NaN values. None is treated as NaN.
* padding: Used to update `self.padding`. When the value is a
sequence, the first two items of the sequence are used as the
left/upper and right/lower (for columns and rows respectively)
padding values, where inner None values are treated as NaN.
Otherwise, the value is applied to all padding values while
treating NaN and None as `(NaN, NaN)`.
"""
self._slots = cast(list[Slot], [])
self._lock = threading.Lock()
super().__init__(length=length, **kwargs)
@overload
def configure(self, *, label: str = ..., spacing: FloatV = ..., padding: Vec2 | FloatV = ..., length: int = ...): ... # type: ignore
def configure(self, **kwargs):
if 'length' in kwargs:
self.resize(kwargs['length'])
super().configure(**kwargs)
def configuration(self) -> dict[Literal['label', 'spacing', 'padding', 'length'], Any]: ...
del configuration
def __str__(self):
return str(self._slots)
def __bool__(self):
return bool(self._slots)
def __len__(self) -> int:
# TODO: check for concurrency issues
return len(self._slots)
__int__ = __index__ = __len__
def __float__(self):
return float(len(self))
def __complex__(self):
return complex(len(self))
def __lt__(self, other: Any) -> bool:
return len(self) < other
def __le__(self, other: Any) -> bool:
return len(self) <= other
def __eq__(self, other: Any) -> bool: # XXX: `__hash__ == None`
return len(self) == other
def __ne__(self, other: Any) -> bool:
return len(self) != other
def __gt__(self, other: Any) -> bool:
return len(self) > other
def __ge__(self, other: Any) -> bool:
return len(self) >= other
def slot_weight(self) -> float:
"""Return the weight sum of all "sized" slots in the axis."""
with self._lock:
return sum(s._weight for s in self._slots if not s._size)
def slot_size(self) -> int:
"""Return the size sum of all "fixed" slots in the axis."""
with self._lock:
return sum(s._size for s in self._slots)
# Number Behaviors (no bitwise)
def __pos__(self):
return +len(self)
def __neg__(self):
return -len(self)
def __add__(self, x: _N) -> _N:
return len(self) + x
def __sub__(self, x: _N) -> _N:
return len(self) - x
def __mul__(self, x: _N) -> _N:
return len(self) * x
def __truediv__(self, x: _N) -> _N:
return len(self) * x
def __floordiv__(self, x: _N) -> _N | float:
return len(self) // x
def __mod__(self, x: _N) -> _N:
return len(self) % x
def __divmod__(self, x: Any) -> tuple[float, float]:
length = len(self)
return (length // x, length % x)
def __pow__(self, x: int, mod: int | None = None) -> int:
return pow(len(self), x, mod)
def __round__(self, ndigits: int = 0):
return round(len(self), ndigits)
__trunc__ = __floor__ = __ceil__ = __abs__ = __int__
# Sequence Behaviors/Methods
def __getitem__(self, index: SupportsIndex) -> Slot:
return self._slots[index]
def __iter__(self) -> Iterator[Slot]:
yield from self._slots
def __iadd__(self, x: int) -> Self:
"""
>>> x = Axis(4)
>>> x += 8
>>> len(x)
12
>>> x += 0
>>> len(x)
12
>>> x += -10 # __isub__
>>> len(x)
2
"""
if not x:
return self
if x > 0:
with self._lock:
self._slots.extend(Slot() for _ in range(x))
return self
return self.__isub__(abs(x))
def __isub__(self, x: int) -> Self:
"""
>>> x = Axis(12)
>>> x -= 8
>>> len(x)
4
>>> x -= 0
>>> len(x)
4
>>> x -= -10 # __iadd__
>>> len(x)
12
"""
if not x:
return self
if x > 0:
with self._lock:
del self._slots[-x:]
return self
return self.__iadd__(abs(x))
def resize(self, length: int):
"""Add/remove slots from the axis so that it contains the number
of slots specified.
Awaits internal lock release.
Args:
* length: A positive number indicating the target length of
the axis.
>>> x = Axis(8)
>>> x.resize(2) # trim
>>> len(x)
2
>>> x.resize(12) # extend
>>> len(x)
12
"""
if length < 0:
raise ValueError(f'`length` cannot be less than zero (got {length!r}).')
return self.__iadd__(length-len(self))
def __imul__(self, x: int): # NOTE: ROUNDS DOWN
"""
>>> x = Axis(4)
>>> x *= 4
>>> len(x)
16
>>> x *= 0.73 # trunc 11.68
>>> len(x)
11
"""
if x < 0:
raise ValueError(f'cannot multiply slots by a negative number (got {x!r}).')
return self.resize(int(len(self) * x))
def __itruediv__(self, x: float): # NOTE: ROUNDS DOWN (floor division)
"""
>>> x = Axis(16)
>>> x /= 2
>>> len(x)
8
>>> x /= 3 # floor div
2
"""
if x < 0:
raise ValueError(f"cannot divide slots by a negative number (got {x!r}).")
return self.resize(len(self) // x) # type: ignore
__ifloordiv__ = __itruediv__
def insert(self, index: SupportsIndex):
"""Adds a new row/column at the specified index.
Awaits internal lock release.
Args:
* index: Position of the new slot.
"""
with self._lock:
self._slots.insert(index, Slot())
def remove(self, index: SupportsIndex = -1):
"""Delete the row/column at the specified index, or the last
row/column if not specified.
Awaits internal lock release.
Args:
* index: Target slot index.
"""
with self._lock:
self._slots.pop(index)
@dataclasses.dataclass(init=False)
class Grid(_GridComponent):
"""Layout manager for Dear PyGui, emulating a table-like structure.
Attributes:
* cols (Axis): Living representation of the grid's columns (x-axis).
See the `Axis` class for more information.
* rows (Axis): Living representation of the grid's rows (y-axis).
See the `Axis` class for more information.
* target (int | str): The integer identifier or string alias of the
item used as the grid's positional reference. Additionally, it's
visibility state is used to determine whether or not to display (draw)
the grid. The size of the target item is also used as fallback values
as necessary when `self.width` and/or `self.height` are not applicable.
* label (str): An informal name for the grid.
* spacing (array[float, float]): Values used to offset a slot's (columns
and rows, respectively) position during a draw event when both
`slot.spacing` and `axis.spacing` are not applicable (min. 0 per value).
* padding (array[float, float, float, float]): Used as the left, upper,
right, and lower padding values during a draw event when item-level
padding override values were not provided and both `slot.spacing` and
`axis.spacing` are not applicable. The first and third values are used
for column padding, while the second and fourth values are used for rows
(min. 0 per value).
* width (int): The horizontal size of the grid. Can be used to extend
the grid beyond the default view of the target item, allowing for items
to be positioned in their parent's scroll area. If 0, the width of the
target item is used instead (min. 0).
* height (int): The vertical size of the grid. Can be used to extend
the grid beyond the default view of the target item, allowing for items
to be positioned in their parent's scroll area. If 0, the height of the
target item is used instead (min. 0).
* offsets (array[float, float, float, float]): Similar to padding, but
targets the left, upper, right, and lower edges of the grid itself and
not its' slots. Can be used to expand or shrink the size of the grid's
content region (min. 0 per value).
* rect_getter (_RectGetter): Called before attempting to draw the grid
to fetch the target item's size, position, and visibility status. The
default implementation is a light wrapper around
`dearpygui.get_item_state`. It may also call
`dearpygui.get_item_configuration` if one or more details cannot be
accessed by reading the item's state.
* overlay (bool): If True, a visual representation of the grid will be
displayed over it.
* show (bool): If True, the grid and its' content will be visible.
The grid calculates the positions, sizes, and visibility states of
itself and attached items during a draw event; triggered by calling the
grid. The grid must be frequently drawn in order for it to operate as
expected, performing best when registered as the resize callback for its'
target item or the parenting root-level window. However, the grid can
registered as any Dear PyGui callback or even called directly.
The grid does not use or create items to assist with the layout. Any item
generated by the grid is used for its' overlay, enabled when `self.overlay`
is True. A class-level `mvViewportDrawlist` item is created when the first
instance of `Grid` is created, while a `mvDrawLayer` item is created per
`Grid` instance. When `self.overlay` is False, no additional items are
created.
When a draw event occurs, the "draw" step is skipped when the grid is
not visible. Additionally, the grid, its content, and overlay are drawn
and displayed only if the visibility value returned from `self.rect_getter`
is True, regardless of the value of `self.show` and `self.overlay`.
Items can be attached to the grid via the `.push` method. When sizing and
positioning an item during a draw event, settings provided when attatching
the item (padding, spacing, etc.) are used over slot, axis and grid settings.
If setting(s) are unspecified (usually indicated by None or NaN), the grid
falls back to using settings found on the slot, then axis, etc.
There are situations where an item may be hidden when drawing
the grid:
* the item's cellspan is outside of the grid's current
range
* given the constraints of the slots in the target cellspan,
the size of the item is calculated to be less than 1 pixel
Any item attached to the grid that does not exist at the time
of a draw event is silently dropped from the grid, and will not
be managed further.
For more information, see `Grid.__init__`, `Grid.__call__` and `Grid.push`.
"""
__slots__ = (
# internal
'_lock',
'_item_data',
'_trashbin',
'_drawlayer',
# configuration
'rows',
'cols',
'_width',
'_height',
'_offsets',
'_target',
'_rect_getter',
'_overlay',
'_show',
# other
'__weakref__',
)
target : _GridSetting[Item] = dataclasses.field(default=_GridSetting("target"))
spacing : _GridSetting['array[float]'] = dataclasses.field(default=_GridSetting("spacing")) # float -> array[float]
width : _GridSetting[int] = dataclasses.field(default=_GridSetting("width"))
height : _GridSetting[int] = dataclasses.field(default=_GridSetting("height"))
offsets : _GridSetting['array[float]'] = dataclasses.field(default=_GridSetting("offsets"))
rect_getter: _GridSetting[_RectGetter] = dataclasses.field(default=_GridSetting("rect_getter"))
overlay : _GridSetting[bool] = dataclasses.field(default=_GridSetting("overlay"))
show : _GridSetting[bool] = dataclasses.field(default=_GridSetting("show"))
__drawlist = '[mvViewportDrawlist] Grid'
@overload
def __init__(self, cols: int = ..., rows: int = ..., target: Item = ..., *, label: str | None = ..., padding: Vec4 | FloatV = ..., spacing: Vec2 | FloatV = ..., width: FloatV = ..., height: FloatV = ..., offsets: Vec4 | FloatV = ..., rect_getter: _RectGetter = ..., overlay: bool = ..., show: bool = ...) -> None: ... # type: ignore
def __init__(
self,
cols : int = 1,
rows : int = 1,
target : Item = 0,
*,
width : Any = None,
height : Any = None,
offsets : Any = None,
rect_getter: Any = None,
overlay : bool = False,
show : bool = True,
**kwargs,
) -> None:
"""Args:
* cols: Integer value used to update `self.cols` via `self.cols.resize`.
Sets the number of slots in the x-axis.
* rows: Integer value used to update `self.rows` via `self.rows.resize`.
Sets the number of slots in the y-axis.
* target: Integer or string value used to update `self.target`. Non-
optional in most cases.
* label: Used to update `self.label`. None is treated as ''.
* rect_getter: Used to update `self.rect_getter`. Should be None, or a
callable accepting `self.target` as a positional argument and returns a
5-tuple containing a related width, height, x-position, y-position, and
visibility status. None is treated as the internal default value.
* width: Used to update `self.width`. Sets a minimum value of 0.
None is treated as 0.
* height: Used to update `self.height`. Sets a minimum value of 0.
None is treated as 0.
* offsets: Used to update `self.offsets`. When the value is a
sequence, the first four items are used as the grid's left, upper,
right, and lower inner padding values, where inner None values are
treated as 0. Otherwise, the value is applied to all padding values
while treating NaN and None as `(0, 0, 0, 0)`.
* padding: Used to update `self.padding`. When the value is a
sequence, the first four items are used as the left, upper,
right, and lower padding values for slots, where inner None values
are treated as 0. Otherwise, the value is applied to all padding values
while treating NaN and None as `(0, 0, 0, 0)`.
* spacing: Used to update `self.spacing`. When the value is a
sequence, the first two items are used as column and row spacing values,
where inner None values are treated as 0. Otherwise, the value is applied
to all spacing values while treating NaN and None as `(0, 0)`.
* overlay: Used to update `self.overlay`. Evaluated as a boolean, True
will cause the overlay to be displayed once a draw event has been
triggered.
* show: Used to update `self.show`. Evaluated as a boolean,
False will hide the grid, its' overlay, and all attached items (and
vice-versa).
"""
_create_context()
if not dearpygui.does_item_exist(self.__drawlist):
type(self).__drawlist = dearpygui.add_viewport_drawlist()
self._drawlayer = dearpygui.add_draw_layer(parent=self.__drawlist)
self._item_data = cast(set[ItemData], set())
self._trashbin = set()
self._lock = threading.Lock() if not sys.gettrace() else threading.RLock()
self.cols = Axis(0, label='x')
self.rows = Axis(0, label='y')
super().__init__(
rows=rows,
cols=cols,
width=width,
height=height,
offsets=offsets,
target=target,
rect_getter=rect_getter,
overlay=overlay,
show=show,
**kwargs,
)
def _clean_cache(self):
# currently called before or after:
# * updating the grid's settings
# * updating `self._item_data`
# * drawing the grid
self._item_data.difference_update(self._trashbin)
self._trashbin.clear()
@overload
def configure(self, *, cols: int = ..., rows: int = ..., target: Item = ..., label: str | None = ..., padding: Vec4 | FloatV = ..., spacing: Vec2 | FloatV = ..., offsets: Vec4 | FloatV = ..., width: FloatV = ..., height: FloatV = ..., rect_getter: _RectGetter = ..., overlay: bool = ..., show: bool = ...): ... # type: ignore
def configure(self, **kwargs):
"""Update the grid's settings.
Awaits internal lock release.
"""
with self._lock:
# slots
if 'cols' in kwargs:
cols = kwargs['cols']
if not isinstance(cols, int):
raise TypeError(f'expected int for `cols` (got {type(cols)!r}).')
self.cols.resize(kwargs['cols'])
if 'rows' in kwargs:
rows = kwargs['rows']
if not isinstance(rows, int):
raise TypeError(f'expected int for `rows` (got {type(rows)!r}).')
self.rows.resize(kwargs['rows'])
if 'target' in kwargs or 'rect_getter' in kwargs:
target = kwargs.get('target', getattr(self, '_target', 0)) or 0
rect_getter = kwargs.get('rect_getter', getattr(self, '_rect_getter', _get_item_rect)) or _get_item_rect
if not target and rect_getter is _get_item_rect:
raise ValueError(f'`target` required when using the default rect-getter.')
try:
_, _, _, _, _ = rect_getter(target)
except ValueError:
raise TypeError(f'expected `rect_getter` to return a 5-tuple, got {type(rect_getter)!r}.')
except TypeError:
if not callable(rect_getter):
raise TypeError(f'expected callable for `rect_getter`, got {type(rect_getter)!r}.') from None
raise
except SystemError:
if target and dearpygui.does_item_exist(target):
raise
self._target = target
self._rect_getter = rect_getter
# sizing
if 'width' in kwargs:
self._width = int(max(0, _to_value(kwargs['width'], 0)))
if 'height' in kwargs:
self._height = int(max(0, _to_value(kwargs['height'], 0)))
# offsets
if 'offsets' in kwargs:
self._offsets = _to_float_arr(kwargs['offsets'], 4, 0.0)
if 'padding' in kwargs:
self._padding = _to_float_arr(kwargs.pop('padding'), 4, 0.0) # override
if 'spacing' in kwargs:
self._spacing = _to_float_arr(kwargs.pop('spacing'), 2, 0.0) # override
# visibility
if 'overlay' in kwargs:
overlay = bool(kwargs['overlay'])
_item_set_config(self._drawlayer, show=overlay)
self._overlay = overlay
if 'show' in kwargs:
show = bool(kwargs['show'])
if not show:
_item_set_config(self._drawlayer, show=False)
for item_data in self._item_data:
try:
_item_set_config(item_data.item, show=show)
except SystemError:
if not dearpygui.does_item_exist(item_data.item):
self._trashbin.add(item_data)
else:
raise
self._show = show
super().configure(**kwargs)
self._clean_cache()
def configuration(self):
"""Return the grid's various settings and values."""
cfg = {'cols': len(self.cols), 'rows': len(self.rows)}
cfg.update({f:getattr(self, f'_{f}') for f in self.__dataclass_fields__})
return cfg
def _pop(self, item: Item):
# Two instances of `ItemData` can potentially exist for the same item
# since refs can be integers or strings. Try to remove all of them just
# in case.
if not dearpygui.does_item_exist(item):
self._trashbin.add(item)
else:
if isinstance(item, str):
refs = (item, dearpygui.get_alias_id(item), dearpygui.get_item_alias(item))
else: