36
36
VISIBLE ,
37
37
)
38
38
from travertino .layout import BaseBox
39
+ from travertino .properties .aliased import Condition , aliased_property
39
40
from travertino .properties .shorthand import directional_property
40
41
from travertino .properties .validated import validated_property
41
42
from travertino .size import BaseIntrinsicSize
57
58
58
59
PACK = "pack"
59
60
60
- # Used in backwards compatibility section below
61
- ALIGNMENT = "alignment"
62
- ALIGN_ITEMS = "align_items"
61
+ ######################################################################
62
+ # 2024-12: Backwards compatibility for Toga < 0.5.0
63
+ ######################################################################
64
+
65
+
66
+ class _AlignmentCondition (Condition ):
67
+ def __init__ (self , main_value , / , ** properties ):
68
+ super ().__init__ (** properties )
69
+ self .main_value = main_value
70
+
71
+ def match (self , style , main_name = None ):
72
+ # main_name can't be accessed the "normal" way without causing a loop; we need
73
+ # to access the private stored value.
74
+ return (
75
+ super ().match (style ) and getattr (style , f"_{ main_name } " ) == self .main_value
76
+ )
77
+
78
+
79
+ class _alignment_property (validated_property ):
80
+ # Alignment is deprecated in favor of align_items, but the two share a complex
81
+ # relationship because they don't use the same set of values; translating from one
82
+ # to the other may require knowing the value of direction and text_direction as
83
+ # well.
84
+
85
+ # Both names exist as actual properties on the style object. If one of them has been
86
+ # set, that one is the source of truth; if the other name is requested, its value
87
+ # is computed / translated. They can never both be set at the same time; setting
88
+ # one deletes any value stored in the other.
89
+
90
+ def __set_name__ (self , owner , name ):
91
+ # Hard-coded because it's only called on alignment, not align_items.
92
+
93
+ self .name = "alignment"
94
+ owner ._BASE_ALL_PROPERTIES [owner ].add ("alignment" )
95
+ self .other = "align_items"
96
+ self .derive = {
97
+ _AlignmentCondition (CENTER ): CENTER ,
98
+ _AlignmentCondition (START , direction = COLUMN , text_direction = LTR ): LEFT ,
99
+ _AlignmentCondition (START , direction = COLUMN , text_direction = RTL ): RIGHT ,
100
+ _AlignmentCondition (START , direction = ROW ): TOP ,
101
+ _AlignmentCondition (END , direction = COLUMN , text_direction = LTR ): RIGHT ,
102
+ _AlignmentCondition (END , direction = COLUMN , text_direction = RTL ): LEFT ,
103
+ _AlignmentCondition (END , direction = ROW ): BOTTOM ,
104
+ }
105
+
106
+ # Replace the align_items validated_property with another instance of this
107
+ # class. This is needed so accessing or setting either one will properly
108
+ # reference the other.
109
+ owner .align_items = _alignment_property (START , CENTER , END )
110
+ owner .align_items .name = "align_items"
111
+ owner .align_items .other = "alignment"
112
+ owner .align_items .derive = {
113
+ # Invert each condition so that it maps in the opposite direction.
114
+ _AlignmentCondition (result , ** condition .properties ): condition .main_value
115
+ for condition , result in self .derive .items ()
116
+ }
117
+
118
+ def __get__ (self , obj , objtype = None ):
119
+ if obj is None :
120
+ return self
121
+
122
+ self .warn_if_deprecated ()
123
+
124
+ if hasattr (obj , f"_{ self .other } " ):
125
+ # If the other property is set, attempt to translate.
126
+ for condition , value in self .derive .items ():
127
+ if condition .match (obj , main_name = self .other ):
128
+ return value
129
+
130
+ # If the other property isn't set (or no condition is valid), access this
131
+ # property as usual.
132
+ return super ().__get__ (obj )
133
+
134
+ def __set__ (self , obj , value ):
135
+ # This won't be executed until @dataclass is added
136
+ if value is self : # pragma: no cover
137
+ # This happens during autogenerated dataclass __init__ when no value is
138
+ # supplied.
139
+ return
140
+
141
+ self .warn_if_deprecated ()
142
+
143
+ # Delete the other property when setting this one.
144
+ try :
145
+ delattr (obj , f"_{ self .other } " )
146
+ except AttributeError :
147
+ pass
148
+ super ().__set__ (obj , value )
149
+
150
+ def __delete__ (self , obj ):
151
+ self .warn_if_deprecated ()
152
+
153
+ # Delete the other property too.
154
+ try :
155
+ delattr (obj , f"_{ self .other } " )
156
+ except AttributeError :
157
+ pass
158
+ super ().__delete__ (obj )
159
+
160
+ def is_set_on (self , obj ):
161
+ self .warn_if_deprecated ()
162
+
163
+ # Counts as set if *either* of the two properties is set.
164
+ return super ().is_set_on (obj ) or hasattr (obj , f"_{ self .other } " )
165
+
166
+ def warn_if_deprecated (self ):
167
+ if self .name == "alignment" :
168
+ warnings .warn (
169
+ "Pack.alignment is deprecated. Use Pack.align_items instead." ,
170
+ DeprecationWarning ,
171
+ stacklevel = 3 ,
172
+ )
173
+
174
+
175
+ ######################################################################
176
+ # End backwards compatibility
177
+ ######################################################################
63
178
64
179
65
180
class Pack (BaseStyle ):
@@ -77,9 +192,6 @@ class IntrinsicSize(BaseIntrinsicSize):
77
192
visibility : str = validated_property (VISIBLE , HIDDEN , initial = VISIBLE )
78
193
direction : str = validated_property (ROW , COLUMN , initial = ROW )
79
194
align_items : str | None = validated_property (START , CENTER , END )
80
- alignment : str | None = validated_property (
81
- LEFT , RIGHT , TOP , BOTTOM , CENTER
82
- ) # Deprecated
83
195
justify_content : str | None = validated_property (START , CENTER , END , initial = START )
84
196
gap : int = validated_property (integer = True , initial = 0 )
85
197
@@ -109,153 +221,48 @@ class IntrinsicSize(BaseIntrinsicSize):
109
221
font_weight : str = validated_property (* FONT_WEIGHTS , initial = NORMAL )
110
222
font_size : int = validated_property (integer = True , initial = SYSTEM_DEFAULT_FONT_SIZE )
111
223
112
- @ classmethod
113
- def _debug ( cls , * args : str ) -> None : # pragma: no cover
114
- print ( " " * cls . _depth , * args )
224
+ ######################################################################
225
+ # Directional aliases
226
+ ######################################################################
115
227
116
- @property
117
- def _hidden (self ) -> bool :
118
- """Does this style declaration define an object that should be hidden."""
119
- return self .visibility == HIDDEN
228
+ horizontal_align_content : str | None = aliased_property (
229
+ source = {Condition (direction = ROW ): "justify_content" }
230
+ )
231
+ horizontal_align_items : str | None = aliased_property (
232
+ source = {Condition (direction = COLUMN ): "align_items" }
233
+ )
234
+ vertical_align_content : str | None = aliased_property (
235
+ source = {Condition (direction = COLUMN ): "justify_content" }
236
+ )
237
+ vertical_align_items : str | None = aliased_property (
238
+ source = {Condition (direction = ROW ): "align_items" }
239
+ )
120
240
121
241
######################################################################
122
242
# 2024-12: Backwards compatibility for Toga < 0.5.0
123
243
######################################################################
124
244
125
- def update ( self , ** properties ):
126
- # Set direction first, as it may change the interpretation of direction-based
127
- # property aliases in _update_property_name.
128
- if direction := properties . pop ( "direction " , None ):
129
- self . direction = direction
245
+ padding : int | tuple [ int ] = aliased_property ( source = "margin" , deprecated = True )
246
+ padding_top : int = aliased_property ( source = "margin_top" , deprecated = True )
247
+ padding_right : int = aliased_property ( source = "margin_right" , deprecated = True )
248
+ padding_bottom : int = aliased_property ( source = "margin_bottom " , deprecated = True )
249
+ padding_left : int = aliased_property ( source = "margin_left" , deprecated = True )
130
250
131
- properties = {
132
- self ._update_property_name (name .replace ("-" , "_" )): value
133
- for name , value in properties .items ()
134
- }
135
- super ().update (** properties )
136
-
137
- _DEPRECATED_PROPERTIES = {
138
- # Map each deprecated property name to its replacement.
139
- # alignment / align_items is handled separately.
140
- "padding" : "margin" ,
141
- "padding_top" : "margin_top" ,
142
- "padding_right" : "margin_right" ,
143
- "padding_bottom" : "margin_bottom" ,
144
- "padding_left" : "margin_left" ,
145
- }
146
-
147
- _ALIASES = {
148
- "horizontal_align_content" : {ROW : "justify_content" },
149
- "horizontal_align_items" : {COLUMN : "align_items" },
150
- "vertical_align_content" : {COLUMN : "justify_content" },
151
- "vertical_align_items" : {ROW : "align_items" },
152
- }
153
-
154
- def _update_property_name (self , name ):
155
- if aliases := self ._ALIASES .get (name ):
156
- try :
157
- name = aliases [self .direction ]
158
- except KeyError :
159
- raise AttributeError (
160
- f"{ name !r} is not supported on a { self .direction } "
161
- ) from None
162
-
163
- if new_name := self ._DEPRECATED_PROPERTIES .get (name ):
164
- self ._warn_deprecated (name , new_name , stacklevel = 4 )
165
- name = new_name
166
-
167
- return name
168
-
169
- def _warn_deprecated (self , old_name , new_name , stacklevel = 3 ):
170
- msg = f"Pack.{ old_name } is deprecated; use { new_name } instead"
171
- warnings .warn (msg , DeprecationWarning , stacklevel = stacklevel )
172
-
173
- # Dot lookup
174
-
175
- def __getattribute__ (self , name ):
176
- if name .startswith ("_" ):
177
- return super ().__getattribute__ (name )
178
-
179
- # Align_items and alignment are paired. Both can never be set at the same time;
180
- # if one is requested, and the other one is set, compute the requested value
181
- # from the one that is set.
182
- if name == ALIGN_ITEMS and (alignment := super ().__getattribute__ (ALIGNMENT )):
183
- if alignment == CENTER :
184
- return CENTER
185
-
186
- if self .direction == ROW :
187
- if alignment == TOP :
188
- return START
189
- if alignment == BOTTOM :
190
- return END
191
-
192
- # No remaining valid combinations
193
- return None
194
-
195
- # direction must be COLUMN
196
- if alignment == LEFT :
197
- return START if self .text_direction == LTR else END
198
- if alignment == RIGHT :
199
- return START if self .text_direction == RTL else END
200
-
201
- # No remaining valid combinations
202
- return None
203
-
204
- if name == ALIGNMENT :
205
- # Warn, whether it's set or not.
206
- self ._warn_deprecated (ALIGNMENT , ALIGN_ITEMS )
207
-
208
- if align_items := super ().__getattribute__ (ALIGN_ITEMS ):
209
- if align_items == START :
210
- if self .direction == COLUMN :
211
- return LEFT if self .text_direction == LTR else RIGHT
212
- return TOP # for ROW
213
-
214
- if align_items == END :
215
- if self .direction == COLUMN :
216
- return RIGHT if self .text_direction == LTR else LEFT
217
- return BOTTOM # for ROW
218
-
219
- # Only CENTER remains
220
- return CENTER
221
-
222
- return super ().__getattribute__ (self ._update_property_name (name ))
223
-
224
- def __setattr__ (self , name , value ):
225
- # Only one of these can be set at a time.
226
- if name == ALIGN_ITEMS :
227
- super ().__delattr__ (ALIGNMENT )
228
- elif name == ALIGNMENT :
229
- self ._warn_deprecated (ALIGNMENT , ALIGN_ITEMS )
230
- super ().__delattr__ (ALIGN_ITEMS )
231
-
232
- super ().__setattr__ (self ._update_property_name (name ), value )
233
-
234
- def __delattr__ (self , name ):
235
- # If one of the two is being deleted, delete the other also.
236
- if name == ALIGN_ITEMS :
237
- super ().__delattr__ (ALIGNMENT )
238
- elif name == ALIGNMENT :
239
- self ._warn_deprecated (ALIGNMENT , ALIGN_ITEMS )
240
- super ().__delattr__ (ALIGN_ITEMS )
241
-
242
- super ().__delattr__ (self ._update_property_name (name ))
243
-
244
- # Index notation
245
-
246
- def __getitem__ (self , name ):
247
- return super ().__getitem__ (self ._update_property_name (name .replace ("-" , "_" )))
248
-
249
- def __setitem__ (self , name , value ):
250
- super ().__setitem__ (self ._update_property_name (name .replace ("-" , "_" )), value )
251
-
252
- def __delitem__ (self , name ):
253
- super ().__delitem__ (self ._update_property_name (name .replace ("-" , "_" )))
251
+ alignment : str | None = _alignment_property (TOP , RIGHT , BOTTOM , LEFT , CENTER )
254
252
255
253
######################################################################
256
254
# End backwards compatibility
257
255
######################################################################
258
256
257
+ @classmethod
258
+ def _debug (cls , * args : str ) -> None : # pragma: no cover
259
+ print (" " * cls ._depth , * args )
260
+
261
+ @property
262
+ def _hidden (self ) -> bool :
263
+ """Does this style declaration define an object that should be hidden."""
264
+ return self .visibility == HIDDEN
265
+
259
266
def apply (self , * names : list [str ]) -> None :
260
267
if self ._applicator :
261
268
for name in names or self ._PROPERTIES :
@@ -939,6 +946,3 @@ def __css__(self) -> str:
939
946
css .append (f"font-variant: { self .font_variant } ;" )
940
947
941
948
return " " .join (css )
942
-
943
-
944
- Pack ._BASE_ALL_PROPERTIES [Pack ].update (Pack ._ALIASES )
0 commit comments