1
+ import warnings
2
+ from copy import deepcopy
3
+ from dataclasses import dataclass
4
+ from typing import Union
5
+
1
6
import pyglet
2
7
from pyglet .event import EVENT_HANDLED , EVENT_UNHANDLED
3
8
from pyglet .text .caret import Caret
4
9
from pyglet .text .document import AbstractDocument
5
10
from typing_extensions import Literal , override
6
11
7
12
import arcade
13
+ from arcade import uicolor
8
14
from arcade .gui .events import (
9
15
UIEvent ,
10
16
UIMouseDragEvent ,
11
17
UIMouseEvent ,
12
18
UIMousePressEvent ,
13
19
UIMouseScrollEvent ,
14
20
UIOnChangeEvent ,
21
+ UIOnClickEvent ,
15
22
UITextInputEvent ,
16
23
UITextMotionEvent ,
17
24
UITextMotionSelectEvent ,
18
25
)
19
- from arcade .gui .property import bind
26
+ from arcade .gui .property import Property , bind
27
+ from arcade .gui .style import UIStyleBase , UIStyledWidget
20
28
from arcade .gui .surface import Surface
21
- from arcade .gui .widgets import UIWidget
29
+ from arcade .gui .widgets import UIInteractiveWidget , UIWidget
22
30
from arcade .gui .widgets .layout import UIAnchorLayout
23
31
from arcade .text import FontNameOrNames
24
32
from arcade .types import LBWH , RGBA255 , Color , RGBOrA255
@@ -399,7 +407,27 @@ def ui_label(self) -> UILabel:
399
407
return self ._label
400
408
401
409
402
- class UIInputText (UIWidget ):
410
+ @dataclass
411
+ class UIInputTextStyle (UIStyleBase ):
412
+ """Used to style the UITextWidget for different states. Below is its use case.
413
+
414
+ .. code:: py
415
+
416
+ button = UIInputText(style={"normal": UIInputText.UIStyle(...),})
417
+
418
+ Args:
419
+ bg: Background color.
420
+ border: Border color.
421
+ border_width: Width of the border.
422
+
423
+ """
424
+
425
+ bg : RGBA255 | None = None
426
+ border : RGBA255 | None = uicolor .WHITE
427
+ border_width : int = 2
428
+
429
+
430
+ class UIInputText (UIStyledWidget [UIInputTextStyle ], UIInteractiveWidget ):
403
431
"""An input field the user can type text into.
404
432
405
433
This is useful in returning
@@ -432,9 +460,6 @@ class UIInputText(UIWidget):
432
460
is the same thing as a :py:class:`~arcade.gui.UITextArea`.
433
461
caret_color: An RGBA or RGB color for the caret with each
434
462
channel between 0 and 255, inclusive.
435
- border_color: An RGBA or RGB color for the border with each
436
- channel between 0 and 255, inclusive, can be None to remove border.
437
- border_width: Width of the border in pixels.
438
463
size_hint: A tuple of floats between 0 and 1 defining the amount
439
464
of space of the parent should be requested.
440
465
size_hint_min: Minimum size hint width and height in pixel.
@@ -447,13 +472,36 @@ class UIInputText(UIWidget):
447
472
# position 0.
448
473
LAYOUT_OFFSET = 1
449
474
475
+ # Style
476
+ UIStyle = UIInputTextStyle
477
+
478
+ DEFAULT_STYLE = {
479
+ "normal" : UIStyle (),
480
+ "hover" : UIStyle (
481
+ border = uicolor .WHITE_CLOUDS ,
482
+ ),
483
+ "press" : UIStyle (
484
+ border = uicolor .WHITE_SILVER ,
485
+ ),
486
+ "disabled" : UIStyle (
487
+ bg = uicolor .WHITE_SILVER ,
488
+ ),
489
+ "invalid" : UIStyle (
490
+ bg = uicolor .RED_ALIZARIN .replace (a = 42 ),
491
+ border = uicolor .RED_ALIZARIN ,
492
+ ),
493
+ }
494
+
495
+ # Properties
496
+ invalid = Property (False )
497
+
450
498
def __init__ (
451
499
self ,
452
500
* ,
453
501
x : float = 0 ,
454
502
y : float = 0 ,
455
503
width : float = 100 ,
456
- height : float = 23 , # required height for font size 12 + border width 1
504
+ height : float = 25 , # required height for font size 12 + border width 1
457
505
text : str = "" ,
458
506
font_name = ("Arial" ,),
459
507
font_size : float = 12 ,
@@ -465,8 +513,24 @@ def __init__(
465
513
size_hint
10000
= None ,
466
514
size_hint_min = None ,
467
515
size_hint_max = None ,
516
+ style : Union [dict [str , UIInputTextStyle ], None ] = None ,
468
517
** kwargs ,
469
518
):
519
+ if border_color != arcade .color .WHITE or border_width != 2 :
520
+ warnings .warn (
521
+ "UIInputText is now a UIStyledWidget. "
522
+ "Use the style dict to set the border color and width." ,
523
+ DeprecationWarning ,
524
+ stacklevel = 1 ,
525
+ )
526
+
527
+ # adjusting style to set border color and width
528
+ style = style or UIInputText .DEFAULT_STYLE
529
+ style = deepcopy (style )
530
+
531
+ style ["normal" ].border = border_color
532
+ style ["normal" ].border_width = border_width
533
+
470
534
super ().__init__ (
471
535
x = x ,
472
536
y = y ,
@@ -475,11 +539,10 @@ def __init__(
475
539
size_hint = size_hint ,
476
540
size_hint_min = size_hint_min ,
477
541
size_hint_max = size_hint_max ,
542
+ style = style or UIInputText .DEFAULT_STYLE ,
478
543
** kwargs ,
479
544
)
480
545
481
- self .with_border (color = border_color , width = border_width )
482
-
483
546
self ._active = False
484
547
self ._text_color = Color .from_iterable (text_color )
485
548
@@ -506,6 +569,44 @@ def __init__(
506
569
507
570
self .register_event_type ("on_change" )
508
571
572
+ bind (self , "hovered" , self ._apply_style )
573
+ bind (self , "pressed" , self ._apply_style )
574
+ bind (self , "invalid" , self ._apply_style )
575
+ bind (self , "disabled" , self ._apply_style )
576
+
577
+ # initial style application
578
+ self ._apply_style ()
579
+
580
+ def _apply_style (self ):
581
+ style = self .get_current_style ()
582
+
583
+ self .with_background (
584
+ color = Color .from_iterable (style .bg ) if style .bg else None ,
585
+ )
586
+ self .with_border (
587
+ color = Color .from_iterable (style .border ) if style .border else None ,
588
+ width = style .border_width ,
589
+ )
590
+ self .trigger_full_render ()
591
+
592
+ @override
593
+ def get_current_state (self ) -> str :
594
+ """Get the current state of the slider.
595
+
596
+ Returns:
597
+ ""normal"", ""hover"", ""press"" or ""disabled"".
598
+ """
599
+ if self .disabled :
600
+ return "disabled"
601
+ elif self .pressed :
602
+ return "press"
603
+ elif self .hovered :
604
+ return "hover"
605
+ elif self .invalid :
606
+ return "invalid"
607
+ else :
608
+ return "normal"
609
+
509
610
def _get_caret_blink_state (self ):
510
611
"""Check whether or not the caret is currently blinking or not."""
511
612
return self .caret .visible and self ._active and self .caret ._blink_visible
@@ -519,18 +620,14 @@ def on_update(self, dt):
519
620
self ._blink_state = current_state
520
621
self .trigger_full_render ()
521
622
623
+ def on_click (self , event : UIOnClickEvent ):
624
+ self .activate ()
625
+
522
626
@override
523
627
def on_event (self , event : UIEvent ) -> bool | None :
524
628
"""Handle events for the text input field.
525
629
526
630
Text input is only active when the user clicks on the input field."""
527
- # If not active, check to activate, return
528
- if not self ._active and isinstance (event , UIMousePressEvent ):
529
- if self .rect .point_in_rect (event .pos ):
530
- self .activate ()
531
- # return unhandled to allow other widgets to deactivate
532
- return EVENT_UNHANDLED
533
-
534
631
# If active check to deactivate
535
632
if self ._active and isinstance (event , UIMousePressEvent ):
536
633
if self .rect .point_in_rect (event .pos ):
@@ -571,10 +668,7 @@ def on_event(self, event: UIEvent) -> bool | None:
571
668
if old_text != self .text :
572
669
self .dispatch_event ("on_change" , UIOnChangeEvent (self , old_text , self .text ))
573
670
574
- if super ().on_event (event ):
575
- return EVENT_HANDLED
576
-
577
- return EVENT_UNHANDLED
671
+ return super ().on_event (event )
578
672
579
673
@property
580
674
def active (self ) -> bool :
@@ -585,13 +679,20 @@ def active(self) -> bool:
585
679
586
680
def activate (self ):
587
681
"""Programmatically activate the text input field."""
682
+ if self ._active :
683
+ return
684
+
588
685
self ._active = True
589
686
self .trigger_full_render ()
590
687
self .caret .on_activate ()
591
688
self .caret .position = len (self .doc .text )
592
689
593
690
def deactivate (self ):
594
691
"""Programmatically deactivate the text input field."""
692
+
693
+ if not self ._active :
694
+ return
695
+
595
696
self ._active = False
596
697
self .trigger_full_render ()
597
698
self .caret .on_deactivate ()
0 commit comments