SwiftGUI 0.11.18__tar.gz → 0.11.20__tar.gz

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.
Files changed (90) hide show
  1. {swiftgui-0.11.18 → swiftgui-0.11.20}/PKG-INFO +1 -1
  2. {swiftgui-0.11.18 → swiftgui-0.11.20}/pyproject.toml +1 -1
  3. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Base.py +74 -8
  4. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/BasePopup.py +23 -13
  5. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/Text.py +1 -1
  6. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/_Base.py +1 -1
  7. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Combined_Elements/BaseCombinedElement.py +6 -0
  8. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Combined_Elements/Console.py +19 -6
  9. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Combined_Elements/Form.py +104 -43
  10. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Compat.py +0 -2
  11. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Examples/AllElements.py +22 -6
  12. swiftgui-0.11.20/src/SwiftGUI/Exceptions.py +13 -0
  13. swiftgui-0.11.20/src/SwiftGUI/Extended_Elements/PythonConsole.py +299 -0
  14. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Extended_Elements/TabFrame.py +2 -2
  15. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Extended_Elements/Table.py +3 -1
  16. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/GlobalOptions.py +16 -3
  17. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Popups/SmallPopups.py +36 -29
  18. swiftgui-0.11.20/src/SwiftGUI/Popups/TooltipPopup.py +41 -0
  19. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Popups/__init__.py +1 -0
  20. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Themes/FourColors.py +3 -0
  21. swiftgui-0.11.20/src/SwiftGUI/Tooltips.py +112 -0
  22. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Button.py +1 -1
  23. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Checkbox.py +1 -1
  24. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Combobox.py +1 -1
  25. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Frame.py +10 -5
  26. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/GridFrame.py +1 -1
  27. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Input.py +1 -1
  28. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Listbox.py +1 -1
  29. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Radiobutton.py +1 -1
  30. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Scale.py +2 -4
  31. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Spinbox.py +1 -1
  32. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Text.py +1 -1
  33. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/TextField.py +4 -3
  34. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Treeview.py +1 -1
  35. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Windows.py +34 -2
  36. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/__init__.py +5 -1
  37. {swiftgui-0.11.18 → swiftgui-0.11.20}/LICENSE +0 -0
  38. {swiftgui-0.11.18 → swiftgui-0.11.20}/README.md +0 -0
  39. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/Arc.py +0 -0
  40. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/Bitmap.py +0 -0
  41. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/Element.py +0 -0
  42. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/Image.py +0 -0
  43. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/Line.py +0 -0
  44. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/Oval.py +0 -0
  45. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/Polygon.py +0 -0
  46. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/Rectangle.py +0 -0
  47. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/__init__.py +0 -0
  48. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Colors.py +0 -0
  49. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Combined_Elements/Calendar.py +0 -0
  50. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Combined_Elements/MultistateButton.py +0 -0
  51. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Debug.py +0 -0
  52. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/DecoratorKeys.py +0 -0
  53. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/ElementFlags.py +0 -0
  54. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Events.py +0 -0
  55. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Examples/AllThemes.py +0 -0
  56. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Examples/GlobalOptionBrowser.py +0 -0
  57. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Examples/Previews.py +0 -0
  58. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Examples/__init__.py +0 -0
  59. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Extended_Elements/ColorChooserButton.py +0 -0
  60. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Extended_Elements/ComboboxMapping.py +0 -0
  61. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Extended_Elements/FileBrowseButton.py +0 -0
  62. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Extended_Elements/Image.py +0 -0
  63. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Extended_Elements/ImageButton.py +0 -0
  64. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Extended_Elements/Separator.py +0 -0
  65. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Extended_Elements/Spacer.py +0 -0
  66. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Extras/SwiftGUI.py +0 -0
  67. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Extras/__init__.py +0 -0
  68. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Files/ConfigFile.py +0 -0
  69. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Files/ConfigurationEditor.py +0 -0
  70. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Files/DictFile.py +0 -0
  71. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Files/ProgramDirectory.py +0 -0
  72. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Files/__init__.py +0 -0
  73. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Fonts.py +0 -0
  74. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/KeyFunctions.py +0 -0
  75. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Literals.py +0 -0
  76. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Popups/ElementPopup.py +0 -0
  77. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Popups/ListPicker.py +0 -0
  78. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Popups/VirtualKeyboard.py +0 -0
  79. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Themes/Thematic.py +0 -0
  80. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Themes/_BaseTheme.py +0 -0
  81. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Themes/__init__.py +0 -0
  82. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Tools.py +0 -0
  83. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Utilities/Images.py +0 -0
  84. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Utilities/Threads.py +0 -0
  85. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Canvas.py +0 -0
  86. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/LabelFrame.py +0 -0
  87. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Notebook.py +0 -0
  88. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Progressbar.py +0 -0
  89. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Scrollbar.py +0 -0
  90. {swiftgui-0.11.18 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/TKContainer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SwiftGUI
3
- Version: 0.11.18
3
+ Version: 0.11.20
4
4
  Summary: An easy-to-use package for quickly creating smaller and bigger GUIs
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "SwiftGUI"
3
- version = "0.11.18"
3
+ version = "0.11.20"
4
4
  packages = [
5
5
  { include = "SwiftGUI", from = "src" }
6
6
  ]
@@ -1,10 +1,9 @@
1
- from collections.abc import Iterable, Callable
2
1
  from functools import wraps
3
- from typing import Literal, Union, Any, Hashable, Protocol
2
+ from typing import Literal, Union, Any, Hashable, Protocol, Iterable, Callable, TypeVar
4
3
  from SwiftGUI.Compat import Self
5
4
  import tkinter as tk
6
5
 
7
- from SwiftGUI import Event, GlobalOptions, Color, remove_None_vals, Literals
6
+ from SwiftGUI import Event, GlobalOptions, Color, remove_None_vals, Literals, ReuseError, RowTypeError, Tooltips
8
7
  from SwiftGUI.ElementFlags import ElementFlag
9
8
  #from SwiftGUI.Widget_Elements.Frame import Frame
10
9
 
@@ -52,6 +51,7 @@ class _BaseSharedAttributes:
52
51
  key: Hashable = None # If given, this will be written to the event-value. Elements without a key can not throw key-events
53
52
  _key_function: Callable | Iterable[Callable] = None # Saves the key-function(s), if given
54
53
 
54
+ same_class = TypeVar("same_class")
55
55
  class BaseElement(_BaseSharedAttributes):
56
56
  """
57
57
  Base for every element and widget.
@@ -59,11 +59,11 @@ class BaseElement(_BaseSharedAttributes):
59
59
  The different to BaseWidget is that BaseWidget is designed for a single tk-Widget.
60
60
  BaseElement allows you to do whatever the f you want, it's just a class pattern.
61
61
  """
62
- parent:Self = None # next higher Element
63
- _fake_tk_element:tk.Widget = None # This gets returned when parent is None
64
- _element_flags:set[ElementFlag] # Properties the element has
62
+ parent: Self = None # next higher Element
63
+ _fake_tk_element: tk.Widget = None # This gets returned when parent is None
64
+ _element_flags: set[ElementFlag] # Properties the element has
65
65
 
66
- defaults:type[GlobalOptions.DEFAULT_OPTIONS_CLASS] = GlobalOptions.EMPTY # Change this to apply a different default configuration
66
+ defaults: type[GlobalOptions.DEFAULT_OPTIONS_CLASS] = GlobalOptions.EMPTY # Change this to apply a different default configuration
67
67
 
68
68
  # So you can use it on inheriting classes without importing it
69
69
  _run_after_window_creation = run_after_window_creation
@@ -73,6 +73,7 @@ class BaseElement(_BaseSharedAttributes):
73
73
  self._run_when_window_exists = list()
74
74
 
75
75
  self._update_options = dict() # This saves all valuew passed by .update, so they can be recalled using .get_option()
76
+ self._is_used_up: bool = False # True, if self._init ran at least once, indicating that this element was part of a window once
76
77
 
77
78
  def _init(self,parent:"BaseElement",window):
78
79
  """
@@ -84,6 +85,10 @@ class BaseElement(_BaseSharedAttributes):
84
85
  :param window: Main Window
85
86
  :return:
86
87
  """
88
+ if self._is_used_up:
89
+ raise ReuseError(f"Element was used in a window multiple times: {repr(self)}")
90
+ self._is_used_up = True
91
+
87
92
  self._init_defaults() # Default configuration
88
93
  self._flag_init()
89
94
 
@@ -91,7 +96,7 @@ class BaseElement(_BaseSharedAttributes):
91
96
  self._personal_init()
92
97
 
93
98
  self._apply_update()
94
- self.add_flags(ElementFlag.IS_CREATED) # Todo: Check if this is okay. It was behind self._flag_init() before.
99
+ self.add_flags(ElementFlag.IS_CREATED)
95
100
  #element_logger.debug(f"Initialized {repr(self)} in {window}")
96
101
 
97
102
  def _flag_init(self):
@@ -378,6 +383,52 @@ class BaseElement(_BaseSharedAttributes):
378
383
 
379
384
  return self
380
385
 
386
+ _tooltip_text: str | None = None
387
+ _bound_tooltip_functions: bool = False # True, if the events for tooltips are already bound
388
+ @run_after_window_creation
389
+ def set_tooltip(self, text: str | None = None) -> Self:
390
+ """
391
+ Add/Change/Remove a tooltip from this element.
392
+
393
+ Set it to None to deactivate tooltips for this element
394
+
395
+ :param text: Text for the tooltip to show
396
+ :return:
397
+ """
398
+ self._tooltip_text = text
399
+
400
+ if not self._bound_tooltip_functions:
401
+ self._bound_tooltip_functions = True
402
+ aw = self.window.actual_window
403
+ aw.activate_tooltips()
404
+ self.bind_event(
405
+ "Enter",
406
+ key_function=Tooltips.mouse_entered_elem_callback,
407
+ ).bind_event(
408
+ "Leave",
409
+ key_function=Tooltips.mouse_exited_elem_callback,
410
+ )
411
+
412
+ @property
413
+ def tooltip_text(self) -> str | None:
414
+ return self._tooltip_text
415
+
416
+ @tooltip_text.setter
417
+ def tooltip_text(self, text: str | None):
418
+ self.set_tooltip(text)
419
+
420
+ @classmethod
421
+ def with_go(cls: type[same_class], go_class: type[GlobalOptions.DEFAULT_OPTIONS_CLASS]) -> type[same_class]:
422
+ """
423
+ Create this element with a different global-option-class
424
+ :param go_class: The global-option-class this object should apply
425
+ :return:
426
+ """
427
+ class Modified(cls):
428
+ defaults = go_class
429
+
430
+ return Modified
431
+
381
432
  class BaseWidget(BaseElement):
382
433
  """
383
434
  Base for every Widget
@@ -766,6 +817,21 @@ class BaseWidgetContainer(BaseWidget):
766
817
  self._line_insert_kwargs = dict()
767
818
  self.add_flags(ElementFlag.IS_CONTAINER)
768
819
 
820
+ def _row_iter_to_list(self, row: Iterable[BaseElement | None]) -> list[BaseElement | None]:
821
+ """
822
+ Convert a row to a list.
823
+ If the row can't be converted, a RowTypeError is raised
824
+ :param row:
825
+ :return:
826
+ """
827
+ try:
828
+ return list(row)
829
+ except TypeError:
830
+ raise RowTypeError("\n\nA row in one of your layouts is not a row.\n"
831
+ "\n"
832
+ f"This... {repr(self)}\n\n"
833
+ f"Contains this (should be a row)... {repr(row)}")
834
+
769
835
  def update_all_containing(self, **kwargs) -> Self:
770
836
  """
771
837
  Update all elements inside this frame
@@ -1,11 +1,11 @@
1
1
  import io
2
- from collections.abc import Iterable
3
2
  from os import PathLike
4
- from typing import Hashable, Any, Literal
3
+ from typing import Hashable, Any, Literal, Generic, Iterable
4
+ from typing_extensions import TypeVar
5
5
  from PIL import Image
6
6
 
7
7
  import SwiftGUI as sg
8
- from SwiftGUI import Color, ValueDict
8
+ from SwiftGUI import Color, ValueDict, ReuseError
9
9
  from SwiftGUI.Compat import Self
10
10
 
11
11
  class BasePopupNonblocking:
@@ -67,12 +67,14 @@ class BasePopupNonblocking:
67
67
  """
68
68
  ...
69
69
 
70
- class BasePopup(BasePopupNonblocking):
70
+ return_type = TypeVar("return_type", default=Any)
71
+
72
+ class BasePopupTyped(BasePopupNonblocking, Generic[return_type]):
71
73
  def __init__(
72
74
  self,
73
75
  layout: Iterable[Iterable[sg.BaseElement]],
74
76
  *,
75
- default: Any = None, # Returned instead of None
77
+ default: return_type = None, # Returned instead of None
76
78
  keep_on_top: bool = True,
77
79
  title: str = None,
78
80
  titlebar: bool = None,
@@ -86,6 +88,7 @@ class BasePopup(BasePopupNonblocking):
86
88
 
87
89
  self._return = None
88
90
  self._default = default
91
+ self._used_up = False
89
92
 
90
93
  super().__init__(
91
94
  layout,
@@ -100,13 +103,7 @@ class BasePopup(BasePopupNonblocking):
100
103
  **kwargs,
101
104
  )
102
105
 
103
- def __new__(cls, *args, **kwargs) -> Any:
104
- me = super().__new__(cls)
105
- me.__init__(*args, **kwargs)
106
-
107
- return me() # Run the popup and return the result
108
-
109
- def done(self, val: Any = None):
106
+ def done(self, val: Any = None) -> return_type:
110
107
  """
111
108
  Call this instead of return.
112
109
  The popup will close and return_value is returned.
@@ -116,7 +113,7 @@ class BasePopup(BasePopupNonblocking):
116
113
  self._return = val
117
114
  self.w.close()
118
115
 
119
- def __call__(self, *args, **kwargs):
116
+ def __call__(self, *args, **kwargs) -> return_type:
120
117
  """
121
118
  Execute the popup-functionality.
122
119
  YOU DON'T NEED TO CALL THIS!
@@ -125,9 +122,22 @@ class BasePopup(BasePopupNonblocking):
125
122
  :param kwargs:
126
123
  :return:
127
124
  """
125
+ if self._used_up:
126
+ raise ReuseError("You cannot re-open popup-instances. Create a new instance to re-use the popup.")
127
+ self._used_up = True
128
+
128
129
  self.w.block_others_until_close()
129
130
 
130
131
  if self._return is None:
131
132
  return self._default
132
133
 
133
134
  return self._return
135
+
136
+ class BasePopup(BasePopupTyped, Generic[return_type]):
137
+
138
+ def __new__(cls, *args, **kwargs) -> return_type:
139
+ me = super().__new__(cls)
140
+ me.__init__(*args, **kwargs)
141
+
142
+ return me() # Run the popup and return the result
143
+
@@ -89,7 +89,7 @@ class Text(Canvas_Elements.BaseCanvasElement):
89
89
  new_font = font.Font(
90
90
  self.canvas.tk_widget,
91
91
  family=self._fonttype,
92
- size=self._fontsize,
92
+ size=self._fontsize if self._fontsize else 8,
93
93
  weight="bold" if self._bold else "normal",
94
94
  slant="italic" if self._italic else "roman",
95
95
  underline=bool(self._underline),
@@ -200,4 +200,4 @@ class BaseCanvasElement(sg.BaseWidget): # Inheritance mainly for the update-rout
200
200
  return self._unflatten(self.canvas.tk_widget.bbox(self.canvas_id))
201
201
 
202
202
  def __repr__(self):
203
- return f"<sgc.{self.__class__.__name__} element at {id(self)}>"
203
+ return f"<sgc.{self.__class__.__name__} canvas-element with key={self.key} at {id(self)}>"
@@ -43,6 +43,7 @@ class BaseCombinedElement(MixinElementWithDefaultEvent, BaseElement):
43
43
  frame = layout
44
44
  else:
45
45
  frame = Frame(layout)
46
+ self.frame = frame
46
47
 
47
48
  if internal_key_system:
48
49
  self._has_sublayout = True
@@ -233,3 +234,8 @@ class BaseCombinedElement(MixinElementWithDefaultEvent, BaseElement):
233
234
  self.set_value(val)
234
235
 
235
236
  return self
237
+
238
+ def _bind_event_to_widget(self, tk_event: str, event_function: Callable) -> Self:
239
+ self.frame.tk_widget.bind(tk_event, event_function)
240
+
241
+
@@ -14,6 +14,7 @@ class Console(sg.BaseCombinedElement):
14
14
  *,
15
15
  key: Hashable = None,
16
16
  key_function: Callable | Iterable[Callable] = None,
17
+ input_text_color: str | sg.Color = None,
17
18
  default_event: bool = False,
18
19
  input_prefix: str = None,
19
20
  print_prefix: str = None,
@@ -54,6 +55,7 @@ class Console(sg.BaseCombinedElement):
54
55
  add_timestamp = add_timestamp,
55
56
  width = width,
56
57
  height = height,
58
+ input_text_color=input_text_color,
57
59
  )
58
60
 
59
61
  def _get_value(self) -> Any:
@@ -86,6 +88,16 @@ class Console(sg.BaseCombinedElement):
86
88
  self._print_prefix = new_val
87
89
  case "add_timestamp":
88
90
  self._add_timestamp = new_val
91
+ case "input_text_color":
92
+ if new_val is None:
93
+ return True
94
+
95
+ if self.window is None:
96
+ self.update_after_window_creation(input_text_color = new_val)
97
+ return True
98
+
99
+ self.textField.tk_widget.tag_config("input", foreground=new_val)
100
+
89
101
  case _: # No other case covered this key, so let's let's the parent-class handle the rest
90
102
  return super()._update_special_key(key, new_val)
91
103
 
@@ -138,23 +150,24 @@ class Console(sg.BaseCombinedElement):
138
150
  """
139
151
  self._prev_value = text
140
152
 
141
- text = self._input_prefix + text
142
-
143
153
  if self._add_timestamp:
144
- text = self.get_time() + text
154
+ self.append(self.get_time() + " ", add_newline=False)
155
+
156
+ self.append(self._input_prefix, add_newline=False)
145
157
 
146
- self.append(text + "\n", add_newline= False)
158
+ self.append(text + "\n", add_newline= False, tag="input")
147
159
 
148
160
  return self
149
161
 
150
- def append(self, text: str, add_newline: bool = True) -> Self:
162
+ def append(self, text: str, add_newline: bool = True, tag: str = None) -> Self:
151
163
  """
152
164
  Just append to the text-field, nothing more, nothing less
153
165
  :param add_newline: True, if \n should be appended too
154
166
  :param text:
167
+ :param tag: tkinter-tag
155
168
  :return:
156
169
  """
157
- self.textField.append(text, add_newline=add_newline)
170
+ self.textField.append(text, add_newline=add_newline, tags=tag)
158
171
  self.textField.see_end()
159
172
  return self
160
173
 
@@ -1,13 +1,7 @@
1
-
2
- from collections.abc import Iterable, Callable
3
- from functools import partial
4
- from typing import Any, Hashable, Mapping
1
+ from typing import Any, Hashable, Mapping, Iterable, Callable
5
2
  from SwiftGUI.Compat import Self
6
- import json
7
3
 
8
- from SwiftGUI import BaseElement, Frame, Text, Input, BaseCombinedElement, Button, Event
9
- #from SwiftGUI.Widget_Elements.Separator import HorizontalSeparator
10
- from SwiftGUI.Extended_Elements.Spacer import Spacer
4
+ from SwiftGUI import BaseElement, Text, Input, BaseCombinedElement, Button, Event, GlobalOptions, ValueDict, Spacer
11
5
 
12
6
 
13
7
  # Advanced / Combined elements
@@ -18,6 +12,8 @@ class Form(BaseCombinedElement):
18
12
  It is WIP, but I'm adding more functionality regularely
19
13
  I'll probably throw this whole class out of the window and redo it, since it is so unfathomly bad atm.
20
14
  """
15
+ defaults = GlobalOptions.Form
16
+ value: ValueDict | tuple
21
17
 
22
18
  def __init__(
23
19
  self,
@@ -27,11 +23,15 @@ class Form(BaseCombinedElement):
27
23
  key: Hashable = None,
28
24
  key_function: Callable | Iterable[Callable] = None,
29
25
  default_event: bool = None,
26
+ return_submits: bool = None,
30
27
  small_clear_buttons: bool = None,
31
28
  big_clear_button: bool = None,
29
+ clear_button_text: str = None,
32
30
  submit_button: bool = None,
31
+ submit_button_text: str = None,
32
+ space_over_buttons: int = None,
33
+ space_between_rows: int = None,
33
34
  #submit_key: Any = None,
34
- return_submits: bool = None,
35
35
  ):
36
36
  """
37
37
 
@@ -44,10 +44,17 @@ class Form(BaseCombinedElement):
44
44
  :param big_clear_button: One big clear-button under the inputs to clear all inputs at once
45
45
  :param submit_button: True, if there should be a submit-button that throws an event when clicked. Ignores the default event
46
46
  :param return_submits: True, if pressing enter should be equal to pressing submit. Ignores the default event
47
+ :param clear_button_text: Text on the clear-button
48
+ :param submit_button_text: Text on the submit-button
49
+ :param space_over_buttons: Height of the separator in the row before the buttons
50
+ :param space_between_rows: Height of the separators between the rows
47
51
  """
48
- self._has_key_mapping = isinstance(texts, Mapping)
52
+ space_between_rows = self.defaults.single("space_between_rows", space_between_rows)
53
+ space_over_buttons = self.defaults.single("space_over_buttons", space_over_buttons)
49
54
 
50
- if self._has_key_mapping:
55
+ self._mapping_mode = isinstance(texts, Mapping)
56
+
57
+ if self._mapping_mode:
51
58
  values = list(texts.values())
52
59
  else:
53
60
  values = list(texts)
@@ -56,41 +63,51 @@ class Form(BaseCombinedElement):
56
63
 
57
64
  self._input_elements: list[Input] = list()
58
65
  self._text_elements: list[Text] = list()
66
+ self._small_clear_button_elements: list[Button] = list()
59
67
 
60
68
  self._small_clear_buttons = small_clear_buttons
61
69
  self._return_submits = return_submits
62
70
 
63
- if self._has_key_mapping:
64
- layout = [
65
- *[
66
- self._make_row(k,t, text_len=_max_len) for t,k in texts.items()
67
- ]
71
+ if self._mapping_mode:
72
+ rows = [
73
+ self._make_row(k,t, text_len=_max_len) for t,k in texts.items()
68
74
  ]
69
75
  else:
70
- layout = [
71
- *[
72
- self._make_row(t, text_len=_max_len) for t in values
73
- ]
76
+ rows = [
77
+ self._make_row(t, text_len=_max_len) for t in values
74
78
  ]
75
79
 
80
+ layout = []
81
+ if space_between_rows:
82
+ for row in rows[:-1]:
83
+ layout.append(row)
84
+ layout.append([Spacer(height=space_between_rows)])
85
+
86
+ if len(rows):
87
+ layout.append(rows[-1])
88
+ else:
89
+ layout = rows
90
+
76
91
  button_row: list[BaseElement] = list()
77
92
 
78
- if big_clear_button:
79
- self.clear_button = Button(
80
- "Clear",
81
- key_function= lambda: self.clear_all_values(throw_default_event=True),
82
- )
83
- button_row.append(self.clear_button)
93
+ self.clear_button = Button(
94
+ self.defaults.single("clear_button_text", clear_button_text),
95
+ key_function= lambda: self.clear_all_values(throw_default_event=True),
96
+ ).set_visible(big_clear_button)
97
+ button_row.append(self.clear_button)
84
98
 
85
- if submit_button:
86
- self.submit_button = Button(
87
- "Submit",
88
- key_function=self.throw_event,
89
- )
90
- button_row.append(self.submit_button)
99
+ self.submit_button = Button(
100
+ self.defaults.single("submit_button_text", submit_button_text),
101
+ key_function=self.throw_event,
102
+ ).set_visible(submit_button)
103
+ button_row.append(self.submit_button)
91
104
 
92
- if button_row:
93
- layout.append(button_row)
105
+ if space_over_buttons:
106
+ layout.append([Spacer(height=space_over_buttons)])
107
+ elif space_between_rows:
108
+ layout.append([Spacer(height=space_between_rows)])
109
+
110
+ layout.append(button_row)
94
111
 
95
112
  super().__init__(
96
113
  layout,
@@ -102,6 +119,18 @@ class Form(BaseCombinedElement):
102
119
  if default_values is not None:
103
120
  self.set_value(default_values)
104
121
 
122
+ def _update_special_key(self,key:str,new_val:Any) -> bool|None:
123
+ match key:
124
+ case "clear_button_text":
125
+ self.clear_button.set_value(new_val)
126
+ case "submit_button_text":
127
+ self.submit_button.set_value(new_val)
128
+
129
+ case _:
130
+ return super()._update_special_key(key, new_val)
131
+
132
+ return True
133
+
105
134
  def _make_row(self, text: str, key: Hashable = None, text_len: int = 20) -> list[BaseElement]:
106
135
  """
107
136
  Create a single row of elements
@@ -128,15 +157,32 @@ class Form(BaseCombinedElement):
128
157
  ]
129
158
 
130
159
  if self._small_clear_buttons:
131
- row.append(Button(
160
+ button = Button(
132
161
  text= "x",
133
162
  width=2,
134
163
  key_function= lambda: input_elem.set_value("", throw_event=True),
135
164
  takefocus= False,
136
- ))
165
+ )
166
+ row.append(button)
167
+ self._small_clear_button_elements.append(button)
137
168
 
138
169
  return row
139
170
 
171
+ def __getitem__(self, item: Hashable | int) -> str:
172
+ assert self._mapping_mode or isinstance(item, int), f"{repr(self)} is created in list-mode, so only integer indexes can be used as key!"
173
+
174
+ return self.value[item]
175
+
176
+ def __setitem__(self, key: Hashable | int, value):
177
+ assert self._mapping_mode or isinstance(key, int), f"{repr(self)} is created in list-mode, so only integer indexes can be used as key!"
178
+
179
+ if self._mapping_mode:
180
+ if not key in self.v:
181
+ raise KeyError(f"{repr(self)} was given a key that it doesn't contain")
182
+ self.v[key] = value
183
+ else:
184
+ self._input_elements[key].value = value
185
+
140
186
  @BaseCombinedElement._run_after_window_creation
141
187
  def set_value(self, val: Iterable | Mapping) -> Self:
142
188
  """
@@ -150,7 +196,9 @@ class Form(BaseCombinedElement):
150
196
  for value, elem in zip(val, self._input_elements):
151
197
  elem.value = value
152
198
 
153
- def values(self) -> tuple:
199
+ return self
200
+
201
+ def as_tuple(self) -> tuple:
154
202
  """
155
203
  Return a tuple with all form-values one after the other
156
204
  :return:
@@ -158,10 +206,10 @@ class Form(BaseCombinedElement):
158
206
  return tuple(map(lambda a:a.value, self._input_elements))
159
207
 
160
208
  def _get_value(self):
161
- if self._has_key_mapping:
209
+ if self._mapping_mode:
162
210
  return super()._get_value()
163
211
 
164
- return self.values()
212
+ return self.as_tuple()
165
213
 
166
214
  @BaseElement._run_after_window_creation
167
215
  def update_texts(self,**kwargs) -> Self:
@@ -177,7 +225,7 @@ class Form(BaseCombinedElement):
177
225
  @BaseElement._run_after_window_creation
178
226
  def update_inputs(self,**kwargs) -> Self:
179
227
  """
180
- Evoke .update on every text-element
228
+ Evoke .update on every input-element
181
229
  :param kwargs:
182
230
  :return:
183
231
  """
@@ -185,7 +233,18 @@ class Form(BaseCombinedElement):
185
233
  elem._update_initial(**kwargs)
186
234
  return self
187
235
 
188
- def clear_all_values(self, throw_default_event: bool = False):
236
+ @BaseElement._run_after_window_creation
237
+ def update_small_clear_buttons(self,**kwargs) -> Self:
238
+ """
239
+ Evoke .update on every input-element
240
+ :param kwargs:
241
+ :return:
242
+ """
243
+ for elem in self._small_clear_button_elements:
244
+ elem._update_initial(**kwargs)
245
+ return self
246
+
247
+ def clear_all_values(self, throw_default_event: bool = False) -> Self:
189
248
  """
190
249
  Does what it says
191
250
  :return:
@@ -196,13 +255,15 @@ class Form(BaseCombinedElement):
196
255
  if throw_default_event:
197
256
  self.throw_default_event()
198
257
 
199
- def set_focus(self):
258
+ return self
259
+
260
+ def set_focus(self) -> Self:
200
261
  """
201
262
  Focus the first row of this element
202
263
  :return:
203
264
  """
204
265
  self._input_elements[0].set_focus()
205
-
266
+ return self
206
267
 
207
268
 
208
269
 
@@ -35,5 +35,3 @@ else:
35
35
 
36
36
  if collected:
37
37
  yield tuple(collected)
38
-
39
-
@@ -97,6 +97,19 @@ def preview_all_elements(
97
97
  sg.FileBrowseButton("save_single", file_browse_type="save_single", disabled= flip_disabled),
98
98
  sg.FileBrowseButton("open_multiple", file_browse_type="open_multiple", disabled= flip_disabled),
99
99
  sg.FileBrowseButton("open_directory", file_browse_type="open_directory", disabled= flip_disabled),
100
+ ],[
101
+ sg.HSep(),
102
+ sg.Spacer(height=10),
103
+ ], [
104
+ sg.T("sg.PythonConsole, works with the normal print and input functions:"),
105
+ ],[
106
+ sg.PythonConsole(width=50, height=10),
107
+ ],[
108
+ sg.Spacer(height=5),
109
+ ], [
110
+ sg.T("See for yourself: "),
111
+ sg.Button("print('Hello World')", key_function=lambda: print("Hello World")),
112
+ sg.Button("input('Your input: ')", key_function=lambda: input("Your input: ")),
100
113
  ]
101
114
  ], fake_key= "Extended elements")
102
115
 
@@ -152,7 +165,6 @@ def preview_all_elements(
152
165
  sg.Notebook(
153
166
  sg.TabFrame([[sg.T("sg.Notebook")]], fake_key="Tab1"),
154
167
  sg.TabFrame([[sg.T("still sg.Notebook")]], fake_key="Tab2"),
155
-
156
168
  )
157
169
  ],[
158
170
  sg.Spacer(height=20)
@@ -161,9 +173,9 @@ def preview_all_elements(
161
173
  [
162
174
  sg.T("sg.SubLayout"),
163
175
  ],[
164
- sg.T("A seperate layout inside your layout.")
176
+ sg.T("A separate layout inside your layout.")
165
177
  ],[
166
- sg.T("It has seperate keys and its own segregated event-loop.")
178
+ sg.T("It has separate keys and its own segregated event-loop.")
167
179
  ]
168
180
  ], alignment="left"))
169
181
  ]
@@ -212,6 +224,8 @@ def preview_all_elements(
212
224
  big_clear_button= True,
213
225
  submit_button= True,
214
226
  default_values= ("", "", "Click on Clear"),
227
+ ).update_inputs(
228
+ readonly=flip_readonly,
215
229
  )
216
230
  ],[
217
231
  sg.HSep()
@@ -245,7 +259,9 @@ def preview_all_elements(
245
259
  sg.T("Lets you select a day"),
246
260
  ], [
247
261
  sg.T(expand=True),
248
- sg.Calendar(),
262
+ sg.Calendar(
263
+ disabled=flip_disabled,
264
+ ),
249
265
  sg.T(expand=True),
250
266
  ], [
251
267
  sg.HSep()
@@ -305,7 +321,7 @@ def preview_all_elements(
305
321
  [
306
322
  sg.T("sgc.Element")
307
323
  ],[
308
- sg.Button("Put any sg-element onto your canvas!!")
324
+ sg.Button("Put any sg-element onto your canvas!!", disabled=flip_disabled)
309
325
  ]
310
326
  ]),
311
327
  anchor= "nw",
@@ -388,7 +404,7 @@ def preview_all_elements(
388
404
  ]
389
405
  ]
390
406
 
391
- w = sg.SubWindow(layout, title="Preview of all currently available elements")
407
+ w = sg.SubWindow(layout, title="Preview of all currently available elements", grab_anywhere=True)
392
408
  w["List"].index = 2
393
409
  console.print("Hi, I am sg.Console!")
394
410
  console.print("I have a print-function that works just like the normal print(...)")