xulbux 1.6.1__py3-none-any.whl → 1.6.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of xulbux might be problematic. Click here for more details.

xulbux/xx_color.py CHANGED
@@ -59,7 +59,10 @@ class rgba:
59
59
  - `with_alpha(alpha)` to create a new color with different alpha
60
60
  - `complementary()` to get the complementary color"""
61
61
 
62
- def __init__(self, r: int, g: int, b: int, a: float = None):
62
+ def __init__(self, r: int, g: int, b: int, a: float = None, _validate: bool = True):
63
+ if not _validate:
64
+ self.r, self.g, self.b, self.a = r, g, b, a
65
+ return
63
66
  if any(isinstance(x, rgba) for x in (r, g, b)):
64
67
  raise ValueError("Color is already a rgba() color")
65
68
  elif not all(isinstance(x, int) and 0 <= x <= 255 for x in (r, g, b)):
@@ -70,25 +73,25 @@ class rgba:
70
73
  elif a is not None and not (isinstance(a, (int, float)) and 0 <= a <= 1):
71
74
  raise ValueError(f"Alpha channel must be a float/int in [0.0, 1.0]: got '{a}'")
72
75
  self.r, self.g, self.b = r, g, b
73
- self.a = (1.0 if a > 1.0 else float(a)) if a else None
76
+ self.a = None if a is None else (1.0 if a > 1.0 else float(a))
74
77
 
75
78
  def __len__(self):
76
- return 4 if self.a else 3
79
+ return 3 if self.a is None else 4
77
80
 
78
81
  def __iter__(self):
79
- return iter((self.r, self.g, self.b) + ((self.a,) if self.a else ()))
82
+ return iter((self.r, self.g, self.b) + (() if self.a is None else (self.a,)))
80
83
 
81
84
  def __dict__(self):
82
85
  return self.dict()
83
86
 
84
87
  def __getitem__(self, index):
85
- return ((self.r, self.g, self.b) + ((self.a,) if self.a else ()))[index]
88
+ return ((self.r, self.g, self.b) + (() if self.a is None else (self.a,)))[index]
86
89
 
87
90
  def __repr__(self):
88
- return f'rgba({self.r}, {self.g}, {self.b}{f", {self.a}" if self.a else ""})'
91
+ return f'rgba({self.r}, {self.g}, {self.b}{"" if self.a is None else f", {self.a}"})'
89
92
 
90
93
  def __str__(self):
91
- return f'({self.r}, {self.g}, {self.b}{f", {self.a}" if self.a else ""})'
94
+ return f'({self.r}, {self.g}, {self.b}{"" if self.a is None else f", {self.a}"})'
92
95
 
93
96
  def __eq__(self, other):
94
97
  if not isinstance(other, rgba):
@@ -102,7 +105,7 @@ class rgba:
102
105
 
103
106
  def dict(self) -> dict:
104
107
  """Returns the color components as a dictionary with keys `'r'`, `'g'`, `'b'` and optionally `'a'`"""
105
- return dict(r=self.r, g=self.g, b=self.b, a=self.a) if self.a else dict(r=self.r, g=self.g, b=self.b)
108
+ return dict(r=self.r, g=self.g, b=self.b) if self.a is None else dict(r=self.r, g=self.g, b=self.b, a=self.a)
106
109
 
107
110
  def values(self) -> tuple:
108
111
  """Returns the color components as separate values `r, g, b, a`"""
@@ -110,11 +113,11 @@ class rgba:
110
113
 
111
114
  def to_hsla(self) -> "hsla":
112
115
  """Returns the color as a `hsla()` color"""
113
- return hsla(*self._rgb_to_hsl(self.r, self.g, self.b), self.a)
116
+ return hsla(*self._rgb_to_hsl(self.r, self.g, self.b), self.a, _validate=False)
114
117
 
115
118
  def to_hexa(self) -> "hexa":
116
119
  """Returns the color as a `hexa()` color"""
117
- return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
120
+ return hexa("", self.r, self.g, self.b, self.a)
118
121
 
119
122
  def has_alpha(self) -> bool:
120
123
  """Returns `True` if the color has an alpha channel and `False` otherwise"""
@@ -123,39 +126,39 @@ class rgba:
123
126
  def lighten(self, amount: float) -> "rgba":
124
127
  """Increases the colors lightness by the specified amount (`0.0`-`1.0`)"""
125
128
  self.r, self.g, self.b, self.a = self.to_hsla().lighten(amount).to_rgba().values()
126
- return rgba(self.r, self.g, self.b, self.a)
129
+ return rgba(self.r, self.g, self.b, self.a, _validate=False)
127
130
 
128
131
  def darken(self, amount: float) -> "rgba":
129
132
  """Decreases the colors lightness by the specified amount (`0.0`-`1.0`)"""
130
133
  self.r, self.g, self.b, self.a = self.to_hsla().darken(amount).to_rgba().values()
131
- return rgba(self.r, self.g, self.b, self.a)
134
+ return rgba(self.r, self.g, self.b, self.a, _validate=False)
132
135
 
133
136
  def saturate(self, amount: float) -> "rgba":
134
137
  """Increases the colors saturation by the specified amount (`0.0`-`1.0`)"""
135
138
  self.r, self.g, self.b, self.a = self.to_hsla().saturate(amount).to_rgba().values()
136
- return rgba(self.r, self.g, self.b, self.a)
139
+ return rgba(self.r, self.g, self.b, self.a, _validate=False)
137
140
 
138
141
  def desaturate(self, amount: float) -> "rgba":
139
142
  """Decreases the colors saturation by the specified amount (`0.0`-`1.0`)"""
140
143
  self.r, self.g, self.b, self.a = self.to_hsla().desaturate(amount).to_rgba().values()
141
- return rgba(self.r, self.g, self.b, self.a)
144
+ return rgba(self.r, self.g, self.b, self.a, _validate=False)
142
145
 
143
146
  def rotate(self, degrees: int) -> "rgba":
144
147
  """Rotates the colors hue by the specified number of degrees"""
145
148
  self.r, self.g, self.b, self.a = self.to_hsla().rotate(degrees).to_rgba().values()
146
- return rgba(self.r, self.g, self.b, self.a)
149
+ return rgba(self.r, self.g, self.b, self.a, _validate=False)
147
150
 
148
151
  def invert(self, invert_alpha: bool = False) -> "rgba":
149
152
  """Inverts the color by rotating hue by 180 degrees and inverting lightness"""
150
153
  self.r, self.g, self.b = 255 - self.r, 255 - self.g, 255 - self.b
151
154
  if invert_alpha:
152
155
  self.a = 1 - self.a
153
- return rgba(self.r, self.g, self.b, self.a)
156
+ return rgba(self.r, self.g, self.b, self.a, _validate=False)
154
157
 
155
158
  def grayscale(self) -> "rgba":
156
159
  """Converts the color to grayscale using the luminance formula"""
157
160
  self.r = self.g = self.b = Color.luminance(self.r, self.g, self.b)
158
- return rgba(self.r, self.g, self.b, self.a)
161
+ return rgba(self.r, self.g, self.b, self.a, _validate=False)
159
162
 
160
163
  def blend(self, other: "rgba", ratio: float = 0.5, additive_alpha: bool = False) -> "rgba":
161
164
  """Blends the current color with another color using the specified ratio (`0.0`-`1.0`):
@@ -166,7 +169,7 @@ class rgba:
166
169
  raise ValueError("'ratio' must be a float/int in [0.0, 1.0]")
167
170
  elif not isinstance(other, rgba):
168
171
  if Color.is_valid_rgba(other):
169
- other = rgba(*other)
172
+ other = rgba(*other, _validate=False)
170
173
  else:
171
174
  raise TypeError("'other' must be a valid RGBA color")
172
175
  ratio *= 2
@@ -175,7 +178,7 @@ class rgba:
175
178
  self.b = max(0, min(255, int(round((self.b * (2 - ratio)) + (other.b * ratio)))))
176
179
  none_alpha = self.a is None and (len(other) <= 3 or other[3] is None)
177
180
  if not none_alpha:
178
- self_a = self.a if self.a is not None else 1
181
+ self_a = 1 if self.a is None else self.a
179
182
  other_a = (other[3] if other[3] is not None else 1) if len(other) > 3 else 1
180
183
  if additive_alpha:
181
184
  self.a = max(0, min(1, (self_a * (2 - ratio)) + (other_a * ratio)))
@@ -189,11 +192,11 @@ class rgba:
189
192
  )
190
193
  else:
191
194
  self.a = None
192
- return rgba(self.r, self.g, self.b, None if none_alpha else self.a)
195
+ return rgba(self.r, self.g, self.b, None if none_alpha else self.a, _validate=False)
193
196
 
194
197
  def is_dark(self) -> bool:
195
198
  """Returns `True` if the color is considered dark (`lightness < 50%`)"""
196
- return (0.299 * self.r + 0.587 * self.g + 0.114 * self.b) < 128
199
+ return self.to_hsla().is_dark()
197
200
 
198
201
  def is_light(self) -> bool:
199
202
  """Returns `True` if the color is considered light (`lightness >= 50%`)"""
@@ -211,7 +214,7 @@ class rgba:
211
214
  """Returns a new color with the specified alpha value"""
212
215
  if not (isinstance(alpha, (int, float)) and 0 <= alpha <= 1):
213
216
  raise ValueError("'alpha' must be a float/int in [0.0, 1.0]")
214
- return rgba(self.r, self.g, self.b, alpha)
217
+ return rgba(self.r, self.g, self.b, alpha, _validate=False)
215
218
 
216
219
  def complementary(self) -> "rgba":
217
220
  """Returns the complementary color (180 degrees on the color wheel)"""
@@ -259,7 +262,10 @@ class hsla:
259
262
  - `with_alpha(alpha)` to create a new color with different alpha
260
263
  - `complementary()` to get the complementary color"""
261
264
 
262
- def __init__(self, h: int, s: int, l: int, a: float = None):
265
+ def __init__(self, h: int, s: int, l: int, a: float = None, _validate: bool = True):
266
+ if not _validate:
267
+ self.h, self.s, self.l, self.a = h, s, l, a
268
+ return
263
269
  if any(isinstance(x, hsla) for x in (h, s, l)):
264
270
  raise ValueError("Color is already a hsla() color")
265
271
  elif not (isinstance(h, int) and (0 <= h <= 360) and all(isinstance(x, int) and (0 <= x <= 100) for x in (s, l))):
@@ -270,25 +276,25 @@ class hsla:
270
276
  elif a is not None and (not isinstance(a, (int, float)) or not 0 <= a <= 1):
271
277
  raise ValueError(f"Alpha channel must be a float/int in [0.0, 1.0]: got '{a}'")
272
278
  self.h, self.s, self.l = h, s, l
273
- self.a = (1.0 if a > 1.0 else float(a)) if a else None
279
+ self.a = None if a is None else (1.0 if a > 1.0 else float(a))
274
280
 
275
281
  def __len__(self):
276
- return 4 if self.a else 3
282
+ return 3 if self.a is None else 4
277
283
 
278
284
  def __iter__(self):
279
- return iter((self.h, self.s, self.l) + ((self.a,) if self.a else ()))
285
+ return iter((self.h, self.s, self.l) + (() if self.a is None else (self.a,)))
280
286
 
281
287
  def __dict__(self):
282
288
  return self.dict()
283
289
 
284
290
  def __getitem__(self, index):
285
- return ((self.h, self.s, self.l) + ((self.a,) if self.a else ()))[index]
291
+ return ((self.h, self.s, self.l) + (() if self.a is None else (self.a,)))[index]
286
292
 
287
293
  def __repr__(self):
288
- return f'hsla({self.h}, {self.s}, {self.l}{f", {self.a}" if self.a else ""})'
294
+ return f'hsla({self.h}, {self.s}, {self.l}{"" if self.a is None else f", {self.a}"})'
289
295
 
290
296
  def __str__(self):
291
- return f'({self.h}, {self.s}, {self.l}{f", {self.a}" if self.a else ""})'
297
+ return f'({self.h}, {self.s}, {self.l}{"" if self.a is None else f", {self.a}"})'
292
298
 
293
299
  def __eq__(self, other):
294
300
  if not isinstance(other, hsla):
@@ -302,7 +308,7 @@ class hsla:
302
308
 
303
309
  def dict(self) -> dict:
304
310
  """Returns the color components as a dictionary with keys `'h'`, `'s'`, `'l'` and optionally `'a'`"""
305
- return dict(h=self.h, s=self.s, l=self.l, a=self.a) if self.a else dict(h=self.h, s=self.s, l=self.l)
311
+ return dict(h=self.h, s=self.s, l=self.l) if self.a is None else dict(h=self.h, s=self.s, l=self.l, a=self.a)
306
312
 
307
313
  def values(self) -> tuple:
308
314
  """Returns the color components as separate values `h, s, l, a`"""
@@ -310,12 +316,12 @@ class hsla:
310
316
 
311
317
  def to_rgba(self) -> "rgba":
312
318
  """Returns the color as a `rgba()` color"""
313
- return rgba(*self._hsl_to_rgb(self.h, self.s, self.l), self.a)
319
+ return rgba(*self._hsl_to_rgb(self.h, self.s, self.l), self.a, _validate=False)
314
320
 
315
321
  def to_hexa(self) -> "hexa":
316
322
  """Returns the color as a `hexa()` color"""
317
323
  r, g, b = self._hsl_to_rgb(self.h, self.s, self.l)
318
- return hexa(f'#{r:02X}{g:02X}{b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
324
+ return hexa("", r, g, b, self.a)
319
325
 
320
326
  def has_alpha(self) -> bool:
321
327
  """Returns `True` if the color has an alpha channel and `False` otherwise"""
@@ -326,33 +332,33 @@ class hsla:
326
332
  if not (isinstance(amount, (int, float)) and 0 <= amount <= 1):
327
333
  raise ValueError("'amount' must be a float/int in [0.0, 1.0]")
328
334
  self.l = int(min(100, self.l + (100 - self.l) * amount))
329
- return hsla(self.h, self.s, self.l, self.a)
335
+ return hsla(self.h, self.s, self.l, self.a, _validate=False)
330
336
 
331
337
  def darken(self, amount: float) -> "hsla":
332
338
  """Decreases the colors lightness by the specified amount (`0.0`-`1.0`)"""
333
339
  if not (isinstance(amount, (int, float)) and 0 <= amount <= 1):
334
340
  raise ValueError("'amount' must be a float/int in [0.0, 1.0]")
335
341
  self.l = int(max(0, self.l * (1 - amount)))
336
- return hsla(self.h, self.s, self.l, self.a)
342
+ return hsla(self.h, self.s, self.l, self.a, _validate=False)
337
343
 
338
344
  def saturate(self, amount: float) -> "hsla":
339
345
  """Increases the colors saturation by the specified amount (`0.0`-`1.0`)"""
340
346
  if not (isinstance(amount, (int, float)) and 0 <= amount <= 1):
341
347
  raise ValueError("'amount' must be a float/int in [0.0, 1.0]")
342
348
  self.s = int(min(100, self.s + (100 - self.s) * amount))
343
- return hsla(self.h, self.s, self.l, self.a)
349
+ return hsla(self.h, self.s, self.l, self.a, _validate=False)
344
350
 
345
351
  def desaturate(self, amount: float) -> "hsla":
346
352
  """Decreases the colors saturation by the specified amount (`0.0`-`1.0`)"""
347
353
  if not (isinstance(amount, (int, float)) and 0 <= amount <= 1):
348
354
  raise ValueError("'amount' must be a float/int in [0.0, 1.0]")
349
355
  self.s = int(max(0, self.s * (1 - amount)))
350
- return hsla(self.h, self.s, self.l, self.a)
356
+ return hsla(self.h, self.s, self.l, self.a, _validate=False)
351
357
 
352
358
  def rotate(self, degrees: int) -> "hsla":
353
359
  """Rotates the colors hue by the specified number of degrees"""
354
360
  self.h = (self.h + degrees) % 360
355
- return hsla(self.h, self.s, self.l, self.a)
361
+ return hsla(self.h, self.s, self.l, self.a, _validate=False)
356
362
 
357
363
  def invert(self, invert_alpha: bool = False) -> "hsla":
358
364
  """Inverts the color by rotating hue by 180 degrees and inverting lightness"""
@@ -360,13 +366,13 @@ class hsla:
360
366
  self.l = 100 - self.l
361
367
  if invert_alpha:
362
368
  self.a = 1 - self.a
363
- return hsla(self.h, self.s, self.l, self.a)
369
+ return hsla(self.h, self.s, self.l, self.a, _validate=False)
364
370
 
365
371
  def grayscale(self) -> "hsla":
366
372
  """Converts the color to grayscale using the luminance formula"""
367
373
  l = Color.luminance(*self._hsl_to_rgb(self.h, self.s, self.l))
368
- self.h, self.s, self.l, _ = rgba(l, l, l).to_hsla().values()
369
- return hsla(self.h, self.s, self.l, self.a)
374
+ self.h, self.s, self.l, _ = rgba(l, l, l, _validate=False).to_hsla().values()
375
+ return hsla(self.h, self.s, self.l, self.a, _validate=False)
370
376
 
371
377
  def blend(self, other: "hsla", ratio: float = 0.5, additive_alpha: bool = False) -> "rgba":
372
378
  """Blends the current color with another color using the specified ratio (`0.0`-`1.0`):
@@ -374,7 +380,7 @@ class hsla:
374
380
  - if `ratio` is `0.5` it means 50% of both colors (1:1 mixture)
375
381
  - if `ratio` is `1.0` it means 0% of the current color and 100% of the `other` color (0:2 mixture)"""
376
382
  self.h, self.s, self.l, self.a = self.to_rgba().blend(Color.to_rgba(other), ratio, additive_alpha).to_hsla().values()
377
- return hsla(self.h, self.s, self.l, self.a)
383
+ return hsla(self.h, self.s, self.l, self.a, _validate=False)
378
384
 
379
385
  def is_dark(self) -> bool:
380
386
  """Returns `True` if the color is considered dark (`lightness < 50%`)"""
@@ -396,11 +402,11 @@ class hsla:
396
402
  """Returns a new color with the specified alpha value"""
397
403
  if not (isinstance(alpha, (int, float)) and 0 <= alpha <= 1):
398
404
  raise ValueError("'alpha' must be a float/int in [0.0, 1.0]")
399
- return hsla(self.h, self.s, self.l, alpha)
405
+ return hsla(self.h, self.s, self.l, alpha, _validate=False)
400
406
 
401
407
  def complementary(self) -> "hsla":
402
408
  """Returns the complementary color (180 degrees on the color wheel)"""
403
- return hsla((self.h + 180) % 360, self.s, self.l, self.a)
409
+ return hsla((self.h + 180) % 360, self.s, self.l, self.a, _validate=False)
404
410
 
405
411
  def _hsl_to_rgb(self, h: int, s: int, l: int) -> tuple:
406
412
  h, s, l = h / 360, s / 100, l / 100
@@ -452,7 +458,10 @@ class hexa:
452
458
  - `with_alpha(alpha)` to create a new color with different alpha
453
459
  - `complementary()` to get the complementary color"""
454
460
 
455
- def __init__(self, color: str | int):
461
+ def __init__(self, color: str | int, _r: int = None, _g: int = None, _b: int = None, _a: float = None):
462
+ if all(x is not None for x in (_r, _g, _b)):
463
+ self.r, self.g, self.b, self.a = _r, _g, _b, _a
464
+ return
456
465
  if isinstance(color, hexa):
457
466
  raise ValueError("Color is already a hexa() color")
458
467
  if isinstance(color, str):
@@ -491,27 +500,31 @@ class hexa:
491
500
  else:
492
501
  raise ValueError(f"Invalid HEX format '{color}'")
493
502
  elif isinstance(color, int):
494
- self.r, self.g, self.b, self.a = Color.hex_int_to_rgba(color)
503
+ self.r, self.g, self.b, self.a = Color.hex_int_to_rgba(color).values()
495
504
  else:
496
505
  raise TypeError(f"HEX color must be of type 'str' or 'int': got '{type(color)}'")
497
506
 
498
507
  def __len__(self):
499
- return 4 if self.a else 3
508
+ return 3 if self.a is None else 4
500
509
 
501
510
  def __iter__(self):
502
- return iter((f"{self.r:02X}", f"{self.g:02X}", f"{self.b:02X}") + ((f"{int(self.a * 255):02X}",) if self.a else ()))
511
+ return iter(
512
+ (f"{self.r:02X}", f"{self.g:02X}", f"{self.b:02X}") + (() if self.a is None else (f"{int(self.a * 255):02X}",))
513
+ )
503
514
 
504
515
  def __dict__(self):
505
516
  return self.dict()
506
517
 
507
518
  def __getitem__(self, index):
508
- return ((f"{self.r:02X}", f"{self.g:02X}", f"{self.b:02X}") + ((f"{int(self.a * 255):02X}",) if self.a else ()))[index]
519
+ return (
520
+ (f"{self.r:02X}", f"{self.g:02X}", f"{self.b:02X}") + (() if self.a is None else (f"{int(self.a * 255):02X}",))
521
+ )[index]
509
522
 
510
523
  def __repr__(self):
511
- return f'hexa(#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""})'
524
+ return f'hexa(#{self.r:02X}{self.g:02X}{self.b:02X}{"" if self.a is None else f"{int(self.a * 255):02X}"})'
512
525
 
513
526
  def __str__(self):
514
- return f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}'
527
+ return f'#{self.r:02X}{self.g:02X}{self.b:02X}{"" if self.a is None else f"{int(self.a * 255):02X}"}'
515
528
 
516
529
  def __eq__(self, other):
517
530
  if not isinstance(other, hexa):
@@ -526,14 +539,14 @@ class hexa:
526
539
  def dict(self) -> dict:
527
540
  """Returns the color components as a dictionary with hex string values for keys `'r'`, `'g'`, `'b'` and optionally `'a'`"""
528
541
  return (
529
- dict(
542
+ dict(r=f"{self.r:02X}", g=f"{self.g:02X}", b=f"{self.b:02X}")
543
+ if self.a is None
544
+ else dict(
530
545
  r=f"{self.r:02X}",
531
546
  g=f"{self.g:02X}",
532
547
  b=f"{self.b:02X}",
533
548
  a=f"{int(self.a * 255):02X}",
534
549
  )
535
- if self.a
536
- else dict(r=f"{self.r:02X}", g=f"{self.g:02X}", b=f"{self.b:02X}")
537
550
  )
538
551
 
539
552
  def values(self) -> tuple:
@@ -546,7 +559,8 @@ class hexa:
546
559
  self.r,
547
560
  self.g,
548
561
  self.b,
549
- (round(self.a, 2) if round_alpha else self.a) if self.a else None,
562
+ None if self.a is None else (round(self.a, 2) if round_alpha else self.a),
563
+ _validate=False,
550
564
  )
551
565
 
552
566
  def to_hsla(self, round_alpha: bool = True) -> "hsla":
@@ -560,39 +574,39 @@ class hexa:
560
574
  def lighten(self, amount: float) -> "hexa":
561
575
  """Increases the colors lightness by the specified amount (`0.0`-`1.0`)"""
562
576
  self.r, self.g, self.b, self.a = self.to_rgba(False).lighten(amount).values()
563
- return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
577
+ return hexa("", self.r, self.g, self.b, self.a)
564
578
 
565
579
  def darken(self, amount: float) -> "hexa":
566
580
  """Decreases the colors lightness by the specified amount (`0.0`-`1.0`)"""
567
581
  self.r, self.g, self.b, self.a = self.to_rgba(False).darken(amount).values()
568
- return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
582
+ return hexa("", self.r, self.g, self.b, self.a)
569
583
 
570
584
  def saturate(self, amount: float) -> "hexa":
571
585
  """Increases the colors saturation by the specified amount (`0.0`-`1.0`)"""
572
586
  self.r, self.g, self.b, self.a = self.to_rgba(False).saturate(amount).values()
573
- return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
587
+ return hexa("", self.r, self.g, self.b, self.a)
574
588
 
575
589
  def desaturate(self, amount: float) -> "hexa":
576
590
  """Decreases the colors saturation by the specified amount (`0.0`-`1.0`)"""
577
591
  self.r, self.g, self.b, self.a = self.to_rgba(False).desaturate(amount).values()
578
- return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
592
+ return hexa("", self.r, self.g, self.b, self.a)
579
593
 
580
594
  def rotate(self, degrees: int) -> "hexa":
581
595
  """Rotates the colors hue by the specified number of degrees"""
582
596
  self.r, self.g, self.b, self.a = self.to_rgba(False).rotate(degrees).values()
583
- return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
597
+ return hexa("", self.r, self.g, self.b, self.a)
584
598
 
585
599
  def invert(self, invert_alpha: bool = False) -> "hexa":
586
600
  """Inverts the color by rotating hue by 180 degrees and inverting lightness"""
587
601
  self.r, self.g, self.b, self.a = self.to_rgba(False).invert().values()
588
602
  if invert_alpha:
589
603
  self.a = 1 - self.a
590
- return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
604
+ return hexa("", self.r, self.g, self.b, self.a)
591
605
 
592
606
  def grayscale(self) -> "hexa":
593
607
  """Converts the color to grayscale using the luminance formula"""
594
608
  self.r = self.g = self.b = Color.luminance(self.r, self.g, self.b)
595
- return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
609
+ return hexa("", self.r, self.g, self.b, self.a)
596
610
 
597
611
  def blend(self, other: "hexa", ratio: float = 0.5, additive_alpha: bool = False) -> "rgba":
598
612
  """Blends the current color with another color using the specified ratio (`0.0`-`1.0`):
@@ -600,7 +614,7 @@ class hexa:
600
614
  - if `ratio` is `0.5` it means 50% of both colors (1:1 mixture)
601
615
  - if `ratio` is `1.0` it means 0% of the current color and 100% of the `other` color (0:2 mixture)"""
602
616
  self.r, self.g, self.b, self.a = self.to_rgba(False).blend(Color.to_rgba(other), ratio, additive_alpha).values()
603
- return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(self.a * 255):02X}" if self.a else ""}')
617
+ return hexa("", self.r, self.g, self.b, self.a)
604
618
 
605
619
  def is_dark(self) -> bool:
606
620
  """Returns `True` if the color is considered dark (`lightness < 50%`)"""
@@ -608,7 +622,7 @@ class hexa:
608
622
 
609
623
  def is_light(self) -> bool:
610
624
  """Returns `True` if the color is considered light (`lightness >= 50%`)"""
611
- return self.to_hsla(False).is_light()
625
+ return not self.is_dark()
612
626
 
613
627
  def is_grayscale(self) -> bool:
614
628
  """Returns `True` if the color is grayscale (`saturation == 0`)"""
@@ -616,13 +630,13 @@ class hexa:
616
630
 
617
631
  def is_opaque(self) -> bool:
618
632
  """Returns `True` if the color has no transparency (`alpha == 1.0`)"""
619
- return self.to_hsla(False).is_opaque()
633
+ return self.a == 1 or self.a is None
620
634
 
621
635
  def with_alpha(self, alpha: float) -> "hexa":
622
636
  """Returns a new color with the specified alpha value"""
623
637
  if not (isinstance(alpha, (int, float)) and 0 <= alpha <= 1):
624
638
  raise ValueError("'alpha' must be in [0.0, 1.0]")
625
- return hexa(f'#{self.r:02X}{self.g:02X}{self.b:02X}{f"{int(alpha * 255):02X}" if alpha else ""}')
639
+ return hexa("", self.r, self.g, self.b, alpha)
626
640
 
627
641
  def complementary(self) -> "hexa":
628
642
  """Returns the complementary color (180 degrees on the color wheel)"""
@@ -748,15 +762,11 @@ class Color:
748
762
  if isinstance(color, (hsla, hexa)):
749
763
  return color.to_rgba()
750
764
  elif Color.is_valid_hsla(color):
751
- return hsla(*color).to_rgba() if Color.has_alpha(color) else hsla(color[0], color[1], color[2]).to_rgba()
765
+ return hsla(*color, _validate=False).to_rgba()
752
766
  elif Color.is_valid_hexa(color):
753
767
  return hexa(color).to_rgba()
754
768
  elif Color.is_valid_rgba(color):
755
- return (
756
- color
757
- if isinstance(color, rgba)
758
- else (rgba(*color) if Color.has_alpha(color) else rgba(color[0], color[1], color[2]))
759
- )
769
+ return color if isinstance(color, rgba) else (rgba(*color, _validate=False))
760
770
  raise ValueError(f"Invalid color format '{color}'")
761
771
 
762
772
  @staticmethod
@@ -765,15 +775,11 @@ class Color:
765
775
  if isinstance(color, (rgba, hexa)):
766
776
  return color.to_hsla()
767
777
  elif Color.is_valid_rgba(color):
768
- return rgba(*color).to_hsla() if Color.has_alpha(color) else rgba(color[0], color[1], color[2]).to_hsla()
778
+ return rgba(*color, _validate=False).to_hsla()
769
779
  elif Color.is_valid_hexa(color):
770
780
  return hexa(color).to_hsla()
771
781
  elif Color.is_valid_hsla(color):
772
- return (
773
- color
774
- if isinstance(color, hsla)
775
- else (hsla(*color) if Color.has_alpha(color) else hsla(color[0], color[1], color[2]))
776
- )
782
+ return color if isinstance(color, hsla) else (hsla(*color, _validate=False))
777
783
  raise ValueError(f"Invalid color format '{color}'")
778
784
 
779
785
  @staticmethod
@@ -782,11 +788,11 @@ class Color:
782
788
  if isinstance(color, (rgba, hsla)):
783
789
  return color.to_hexa()
784
790
  elif Color.is_valid_rgba(color):
785
- return rgba(*color).to_hexa() if Color.has_alpha(color) else rgba(color[0], color[1], color[2]).to_hexa()
791
+ return rgba(*color, _validate=False).to_hexa()
786
792
  elif Color.is_valid_hsla(color):
787
- return hsla(*color).to_hexa() if Color.has_alpha(color) else hsla(color[0], color[1], color[2]).to_hexa()
793
+ return hsla(*color, _validate=False).to_hexa()
788
794
  elif Color.is_valid_hexa(color):
789
- return color if isinstance(color, hexa) else hexa(f"#{color}")
795
+ return color if isinstance(color, hexa) else hexa(color)
790
796
  raise ValueError(f"Invalid color format '{color}'")
791
797
 
792
798
  @staticmethod
@@ -804,6 +810,7 @@ class Color:
804
810
  int(m[1]),
805
811
  int(m[2]),
806
812
  ((int(m[3]) if "." not in m[3] else float(m[3])) if m[3] else None),
813
+ _validate=False,
807
814
  )
808
815
  else:
809
816
  matches = _re.findall(Regex.rgb_str(allow_alpha=True), string)
@@ -815,6 +822,7 @@ class Color:
815
822
  int(m[1]),
816
823
  int(m[2]),
817
824
  ((int(m[3]) if "." not in m[3] else float(m[3])) if m[3] else None),
825
+ _validate=False,
818
826
  )
819
827
  for m in matches
820
828
  ]
@@ -838,19 +846,19 @@ class Color:
838
846
  r = max(0, min(255, int(r)))
839
847
  g = max(0, min(255, int(g)))
840
848
  b = max(0, min(255, int(b)))
841
- if a is not None:
849
+ if a is None:
850
+ hex_int = (r << 16) | (g << 8) | b
851
+ if not preserve_original and (hex_int & 0xF00000) == 0:
852
+ hex_int |= 0x010000
853
+ else:
842
854
  a = max(0, min(255, int(a * 255)))
843
855
  hex_int = (r << 24) | (g << 16) | (b << 8) | a
844
856
  if not preserve_original and r == 0:
845
857
  hex_int |= 0x01000000
846
- else:
847
- hex_int = (r << 16) | (g << 8) | b
848
- if not preserve_original and (hex_int & 0xF00000) == 0:
849
- hex_int |= 0x010000
850
858
  return hex_int
851
859
 
852
860
  @staticmethod
853
- def hex_int_to_rgba(hex_int: int, preserve_original: bool = False) -> tuple[int, int, int, float | None]:
861
+ def hex_int_to_rgba(hex_int: int, preserve_original: bool = False) -> rgba:
854
862
  """Convert a HEX integer to RGBA channels.\n
855
863
  -------------------------------------------------------------------------------------------
856
864
  If the red channel is `1` after conversion, it will be set to `0`, because when converting
@@ -862,19 +870,21 @@ class Color:
862
870
  hex_str = f"{hex_int:x}"
863
871
  if len(hex_str) <= 6:
864
872
  hex_str = hex_str.zfill(6)
865
- return (
873
+ return rgba(
866
874
  r if (r := int(hex_str[0:2], 16)) != 1 or preserve_original else 0,
867
875
  int(hex_str[2:4], 16),
868
876
  int(hex_str[4:6], 16),
869
877
  None,
878
+ _validate=False,
870
879
  )
871
880
  elif len(hex_str) <= 8:
872
881
  hex_str = hex_str.zfill(8)
873
- return (
882
+ return rgba(
874
883
  r if (r := int(hex_str[0:2], 16)) != 1 or preserve_original else 0,
875
884
  int(hex_str[2:4], 16),
876
885
  int(hex_str[4:6], 16),
877
886
  int(hex_str[6:8], 16) / 255.0,
887
+ _validate=False,
878
888
  )
879
889
  else:
880
890
  raise ValueError(f"Invalid HEX integer '0x{hex_str}': expected in range [0x000000, 0xFFFFFF]")
@@ -911,9 +921,9 @@ class Color:
911
921
  text_bg_color = Color.to_rgba(text_bg_color)
912
922
  brightness = 0.2126 * text_bg_color[0] + 0.7152 * text_bg_color[1] + 0.0722 * text_bg_color[2]
913
923
  return (
914
- (hexa("#FFF") if was_hexa else rgba(255, 255, 255))
924
+ (hexa("", 255, 255, 255) if was_hexa else rgba(255, 255, 255, _validate=False))
915
925
  if brightness < 128
916
- else ((0x000 if was_int else hexa("#000")) if was_hexa else rgba(0, 0, 0))
926
+ else ((0x000 if was_int else hexa("", 0, 0, 0)) if was_hexa else rgba(0, 0, 0, _validate=False))
917
927
  )
918
928
 
919
929
  @staticmethod
@@ -933,7 +943,7 @@ class Color:
933
943
  color[3] if Color.has_alpha(color) else None,
934
944
  )
935
945
  l = int(max(0, min(100, l + lightness_change * 100)))
936
- return Color.to_hexa((h, s, l, a)) if was_hexa else Color.to_rgba((h, s, l, a))
946
+ return hsla(h, s, l, a, _validate=False).to_hexa() if was_hexa else hsla(h, s, l, a, _validate=False).to_rgba()
937
947
 
938
948
  @staticmethod
939
949
  def adjust_saturation(color: rgba | hexa, saturation_change: float) -> rgba | hexa:
@@ -952,4 +962,4 @@ class Color:
952
962
  color[3] if Color.has_alpha(color) else None,
953
963
  )
954
964
  s = int(max(0, min(100, s + saturation_change * 100)))
955
- return Color.to_hexa((h, s, l, a)) if was_hexa else Color.to_rgba((h, s, l, a))
965
+ return hsla(h, s, l, a, _validate=False).to_hexa() if was_hexa else hsla(h, s, l, a, _validate=False).to_rgba()
xulbux/xx_console.py CHANGED
@@ -29,7 +29,6 @@ from contextlib import suppress
29
29
  import pyperclip as _pyperclip
30
30
  import keyboard as _keyboard
31
31
  import getpass as _getpass
32
- import ctypes as _ctypes
33
32
  import shutil as _shutil
34
33
  import mouse as _mouse
35
34
  import sys as _sys
@@ -67,17 +66,6 @@ class Console:
67
66
  def user() -> str:
68
67
  return _os.getenv("USER") or _os.getenv("USERNAME") or _getpass.getuser()
69
68
 
70
- def is_admin() -> bool:
71
- try:
72
- if _os.name == "nt":
73
- return _ctypes.windll.shell32.IsUserAnAdmin() != 0
74
- elif _os.name == "posix":
75
- return _os.geteuid() == 0
76
- else:
77
- return False
78
- except Exception:
79
- return False
80
-
81
69
  @staticmethod
82
70
  def pause_exit(
83
71
  pause: bool = False,
xulbux/xx_env_path.py CHANGED
@@ -8,8 +8,8 @@ Functions for modifying and checking the systems environment-variables:
8
8
 
9
9
  from .xx_path import Path
10
10
 
11
- import os as _os
12
11
  import sys as _sys
12
+ import os as _os
13
13
 
14
14
 
15
15
  class EnvPath: