SwiftGUI 0.11.19__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.19 → swiftgui-0.11.20}/PKG-INFO +1 -1
  2. {swiftgui-0.11.19 → swiftgui-0.11.20}/pyproject.toml +1 -1
  3. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Base.py +49 -3
  4. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/Text.py +1 -1
  5. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Combined_Elements/BaseCombinedElement.py +6 -0
  6. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Combined_Elements/Form.py +104 -43
  7. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Examples/AllElements.py +9 -6
  8. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Extended_Elements/Table.py +3 -1
  9. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/GlobalOptions.py +11 -1
  10. swiftgui-0.11.20/src/SwiftGUI/Popups/TooltipPopup.py +41 -0
  11. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Popups/__init__.py +1 -0
  12. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Themes/FourColors.py +3 -0
  13. swiftgui-0.11.20/src/SwiftGUI/Tooltips.py +112 -0
  14. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Button.py +1 -1
  15. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Checkbox.py +1 -1
  16. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Combobox.py +1 -1
  17. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Frame.py +3 -0
  18. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Input.py +1 -1
  19. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Listbox.py +1 -1
  20. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Radiobutton.py +1 -1
  21. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Scale.py +1 -1
  22. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Spinbox.py +1 -1
  23. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Text.py +1 -1
  24. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/TextField.py +1 -1
  25. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Treeview.py +1 -1
  26. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Windows.py +34 -2
  27. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/__init__.py +1 -0
  28. {swiftgui-0.11.19 → swiftgui-0.11.20}/LICENSE +0 -0
  29. {swiftgui-0.11.19 → swiftgui-0.11.20}/README.md +0 -0
  30. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/BasePopup.py +0 -0
  31. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/Arc.py +0 -0
  32. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/Bitmap.py +0 -0
  33. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/Element.py +0 -0
  34. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/Image.py +0 -0
  35. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/Line.py +0 -0
  36. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/Oval.py +0 -0
  37. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/Polygon.py +0 -0
  38. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/Rectangle.py +0 -0
  39. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/_Base.py +0 -0
  40. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Canvas_Elements/__init__.py +0 -0
  41. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Colors.py +0 -0
  42. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Combined_Elements/Calendar.py +0 -0
  43. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Combined_Elements/Console.py +0 -0
  44. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Combined_Elements/MultistateButton.py +0 -0
  45. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Compat.py +0 -0
  46. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Debug.py +0 -0
  47. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/DecoratorKeys.py +0 -0
  48. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/ElementFlags.py +0 -0
  49. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Events.py +0 -0
  50. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Examples/AllThemes.py +0 -0
  51. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Examples/GlobalOptionBrowser.py +0 -0
  52. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Examples/Previews.py +0 -0
  53. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Examples/__init__.py +0 -0
  54. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Exceptions.py +0 -0
  55. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Extended_Elements/ColorChooserButton.py +0 -0
  56. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Extended_Elements/ComboboxMapping.py +0 -0
  57. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Extended_Elements/FileBrowseButton.py +0 -0
  58. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Extended_Elements/Image.py +0 -0
  59. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Extended_Elements/ImageButton.py +0 -0
  60. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Extended_Elements/PythonConsole.py +0 -0
  61. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Extended_Elements/Separator.py +0 -0
  62. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Extended_Elements/Spacer.py +0 -0
  63. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Extended_Elements/TabFrame.py +0 -0
  64. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Extras/SwiftGUI.py +0 -0
  65. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Extras/__init__.py +0 -0
  66. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Files/ConfigFile.py +0 -0
  67. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Files/ConfigurationEditor.py +0 -0
  68. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Files/DictFile.py +0 -0
  69. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Files/ProgramDirectory.py +0 -0
  70. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Files/__init__.py +0 -0
  71. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Fonts.py +0 -0
  72. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/KeyFunctions.py +0 -0
  73. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Literals.py +0 -0
  74. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Popups/ElementPopup.py +0 -0
  75. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Popups/ListPicker.py +0 -0
  76. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Popups/SmallPopups.py +0 -0
  77. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Popups/VirtualKeyboard.py +0 -0
  78. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Themes/Thematic.py +0 -0
  79. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Themes/_BaseTheme.py +0 -0
  80. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Themes/__init__.py +0 -0
  81. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Tools.py +0 -0
  82. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Utilities/Images.py +0 -0
  83. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Utilities/Threads.py +0 -0
  84. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Canvas.py +0 -0
  85. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/GridFrame.py +0 -0
  86. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/LabelFrame.py +0 -0
  87. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Notebook.py +0 -0
  88. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Progressbar.py +0 -0
  89. {swiftgui-0.11.19 → swiftgui-0.11.20}/src/SwiftGUI/Widget_Elements/Scrollbar.py +0 -0
  90. {swiftgui-0.11.19 → 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.19
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.19"
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, ReuseError, RowTypeError
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.
@@ -383,6 +383,52 @@ class BaseElement(_BaseSharedAttributes):
383
383
 
384
384
  return self
385
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
+
386
432
  class BaseWidget(BaseElement):
387
433
  """
388
434
  Base for every Widget
@@ -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),
@@ -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
+
@@ -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
 
@@ -165,7 +165,6 @@ def preview_all_elements(
165
165
  sg.Notebook(
166
166
  sg.TabFrame([[sg.T("sg.Notebook")]], fake_key="Tab1"),
167
167
  sg.TabFrame([[sg.T("still sg.Notebook")]], fake_key="Tab2"),
168
-
169
168
  )
170
169
  ],[
171
170
  sg.Spacer(height=20)
@@ -174,9 +173,9 @@ def preview_all_elements(
174
173
  [
175
174
  sg.T("sg.SubLayout"),
176
175
  ],[
177
- sg.T("A seperate layout inside your layout.")
176
+ sg.T("A separate layout inside your layout.")
178
177
  ],[
179
- 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.")
180
179
  ]
181
180
  ], alignment="left"))
182
181
  ]
@@ -225,6 +224,8 @@ def preview_all_elements(
225
224
  big_clear_button= True,
226
225
  submit_button= True,
227
226
  default_values= ("", "", "Click on Clear"),
227
+ ).update_inputs(
228
+ readonly=flip_readonly,
228
229
  )
229
230
  ],[
230
231
  sg.HSep()
@@ -258,7 +259,9 @@ def preview_all_elements(
258
259
  sg.T("Lets you select a day"),
259
260
  ], [
260
261
  sg.T(expand=True),
261
- sg.Calendar(),
262
+ sg.Calendar(
263
+ disabled=flip_disabled,
264
+ ),
262
265
  sg.T(expand=True),
263
266
  ], [
264
267
  sg.HSep()
@@ -318,7 +321,7 @@ def preview_all_elements(
318
321
  [
319
322
  sg.T("sgc.Element")
320
323
  ],[
321
- sg.Button("Put any sg-element onto your canvas!!")
324
+ sg.Button("Put any sg-element onto your canvas!!", disabled=flip_disabled)
322
325
  ]
323
326
  ]),
324
327
  anchor= "nw",
@@ -401,7 +404,7 @@ def preview_all_elements(
401
404
  ]
402
405
  ]
403
406
 
404
- 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)
405
408
  w["List"].index = 2
406
409
  console.print("Hi, I am sg.Console!")
407
410
  console.print("I have a print-function that works just like the normal print(...)")
@@ -235,7 +235,9 @@ class Table(MixinElementWithDefaultEvent, BaseWidgetTTK, MixinScrollbar):
235
235
 
236
236
  self._prev_selection = all_indexes
237
237
 
238
- if not all_indexes: # It's impossible for the user to cause a real event without anything selected in the table
238
+ if not all_indexes: # It's NEARLY impossible for the user to cause a real event without anything selected in the table
239
+ # The annoyance is worse without this check, so keep it!
240
+ # If the selectmode is extended, you can deselect rows. This is the only exception
239
241
  return
240
242
 
241
243
  self.throw_default_event()
@@ -253,9 +253,9 @@ class Text(Common, Common_Textual, Common_Background):
253
253
  justify:Literal["left","right","center"] = "left"
254
254
  #borderwidth:int = "5c" # Does not work
255
255
  apply_parent_background_color:bool = True
256
-
257
256
  padding:Literals.padding = 0
258
257
  width:int = None
258
+ relief: Literals.relief = "flat"
259
259
 
260
260
  class Scale(Common_Background, Common_Textual):
261
261
  default_value: int | float = None
@@ -409,6 +409,10 @@ class Window(Common_Background):
409
409
  hidden: bool = False
410
410
  minimized: bool = False
411
411
 
412
+ class Tooltip(Text):
413
+ relief: Literals.relief = "ridge"
414
+ padding: Literals.padding = (10, 5)
415
+ text_color: Color | str = None
412
416
 
413
417
  class SubWindow(Window):
414
418
  ...
@@ -708,6 +712,12 @@ class Canvas_Image(Common_Canvas_Element, Image):
708
712
  image_height: int = None
709
713
  anchor: sg.Literals.anchor = None
710
714
 
715
+ class Form(EMPTY):
716
+ clear_button_text: str = "Clear"
717
+ submit_button_text: str = "Submit"
718
+ space_over_buttons: int = 2
719
+ space_between_rows: int = 0
720
+
711
721
  def reset_all_options():
712
722
  """
713
723
  Reset everything done to the global options on runtime.
@@ -0,0 +1,41 @@
1
+ import SwiftGUI as sg
2
+
3
+ TooltipText = sg.T.with_go(sg.GlobalOptions.Tooltip)
4
+
5
+ class TooltipPopup(sg.BasePopupNonblocking):
6
+
7
+ def __init__(
8
+ self,
9
+ tooltip_text: str,
10
+ titlebar: bool = False,
11
+ padx: int = 0,
12
+ pady: int = 0,
13
+ **window_kwargs,
14
+ ):
15
+ super().__init__(
16
+ self._create_layout(tooltip_text),
17
+ titlebar=titlebar,
18
+ padx=padx,
19
+ pady=pady,
20
+ position= self._get_position(),
21
+ **window_kwargs,
22
+ )
23
+
24
+ def _get_position(self) -> tuple[int, int]:
25
+ posx, posy = sg.main_window().get_mouse_position_global()
26
+ return posx + 15, posy
27
+
28
+ def _create_layout(self, tooltip_text: str) -> list[list[sg.BaseElement]]:
29
+ layout = [
30
+ [
31
+ TooltipText( # Options of this text are defined in GlobalOptions.Tooltip
32
+ tooltip_text,
33
+ )
34
+ ]
35
+ ]
36
+
37
+ return layout
38
+
39
+
40
+ sg.Tooltips.set_tooltip_callable(TooltipPopup)
41
+
@@ -3,4 +3,5 @@ from .ListPicker import list_picker
3
3
  from .SmallPopups import get_text, button_menu, yes_no, show_text
4
4
  from .VirtualKeyboard import virtual_keyboard
5
5
  from .ElementPopup import ElementPopupNonblocking, ElementPopup
6
+ from .TooltipPopup import TooltipPopup
6
7
 
@@ -109,6 +109,9 @@ class BaseFourColors(BaseTheme):
109
109
  temp = go.Canvas_Text
110
110
  temp.color = c4
111
111
 
112
+ temp = go.Tooltip
113
+ temp.text_color = c3
114
+
112
115
  def get_palette_frame(self, width: int = 50, height: int = 200) -> sg.Frame:
113
116
  """
114
117
  Returns an sg.Frame that contains the 4 containing colors.
@@ -0,0 +1,112 @@
1
+ import threading
2
+ from typing import Callable, Protocol, Union
3
+
4
+ import SwiftGUI as sg
5
+ import time
6
+
7
+ ### YOU MAY CHANGE THESE ###
8
+ tooltip_open_delay: float = 0.7 # How long the mouse must be still to open a tooltip
9
+ tooltip_close_delay: float = 0.1 # Maximum delay before a tooltip closes
10
+
11
+ ### PLEASE DO NOT TOUCH ###
12
+ _last_mouse_movement = 0 # When the mouse last moved
13
+ _current_elem = None # The sg.element the mouse is currently hovering over
14
+ _tooltips_active: bool = False # If the tooltip-thread is running
15
+ _tooltip_function: Callable[[str], "SupportsClose"] | None = None # This gets called when a tooltip is opened. Must take one parameter: text
16
+
17
+ class SupportsClose(Protocol):
18
+ def close(self):
19
+ ...
20
+
21
+ def set_tooltip_callable(fct: Callable[[str], SupportsClose], only_if_nonexistent: bool = False):
22
+ """
23
+ Specify the function called on tooltip-opening
24
+ :param fct: The function to call
25
+ :param only_if_nonexistent: Only set that function if no function was set yet
26
+ :return:
27
+ """
28
+ global _tooltip_function
29
+
30
+ if only_if_nonexistent and _tooltip_function:
31
+ return
32
+
33
+ _tooltip_function = fct
34
+
35
+ def mouse_moved_callback():
36
+ """Callback for the move-event"""
37
+ global _last_mouse_movement
38
+ _last_mouse_movement = time.time()
39
+
40
+ def mouse_entered_elem_callback(elem): # Can't use typehints due to circular imports
41
+ """
42
+ Mouse entered an element (key-function)
43
+ :param elem:
44
+ :return:
45
+ """
46
+ global _current_elem
47
+ _current_elem = elem
48
+
49
+ def mouse_exited_elem_callback(elem):
50
+ """
51
+ Mouse is no longer over an element
52
+ :param elem:
53
+ :return:
54
+ """
55
+ global _current_elem
56
+
57
+ if _current_elem is elem:
58
+ _current_elem = None
59
+
60
+ def _handle_tooltips():
61
+ """
62
+ Threaded.
63
+ If the mouse hasn't moved in some time, open the tooltip
64
+ :return:
65
+ """
66
+ global _last_mouse_movement
67
+ _tooltip_open = False
68
+ _opened_tooltip_object: Union["SupportsClose", None] = None # The currently opened tooltip-object
69
+
70
+ while True:
71
+ while not _tooltip_open:
72
+ time_since_movement = time.time() - _last_mouse_movement
73
+ time_until_tooltip = tooltip_open_delay - time_since_movement
74
+
75
+ if time_until_tooltip > 0:
76
+ time.sleep(time_until_tooltip + 0.01)
77
+ continue
78
+
79
+ if _current_elem is None:
80
+ time.sleep(tooltip_open_delay)
81
+ continue
82
+
83
+ _tooltip_open = True
84
+
85
+ _last_mouse_movement = 0
86
+ _opened_for_this_element = _current_elem
87
+ _opened_tooltip_object = _tooltip_function(_current_elem.tooltip_text) # Open tooltip
88
+
89
+ while _tooltip_open: # Tooltip open
90
+ time.sleep(tooltip_close_delay)
91
+
92
+ if _current_elem is not _opened_for_this_element: # If the mouse moved again
93
+ _tooltip_open = False
94
+ break
95
+
96
+ _opened_tooltip_object.close() # Close tooltip
97
+ _opened_tooltip_object = None
98
+
99
+ def activate_tooltips():
100
+ """
101
+ Activate the tooltip-thread if it isn't running already
102
+ :return:
103
+ """
104
+ global _tooltips_active
105
+
106
+ if _tooltips_active:
107
+ return
108
+
109
+ _tooltips_active = True
110
+ threading.Thread(target=_handle_tooltips, daemon=True).start()
111
+
112
+
@@ -214,7 +214,7 @@ class Button(MixinElementWithDefaultEvent, BaseWidget):
214
214
  self._tk_kwargs["font"] = font.Font(
215
215
  self.window.parent_tk_widget,
216
216
  family=self._fonttype,
217
- size=self._fontsize,
217
+ size=self._fontsize if self._fontsize else 8,
218
218
  weight="bold" if self._bold else "normal",
219
219
  slant="italic" if self._italic else "roman",
220
220
  underline=bool(self._underline),
@@ -153,7 +153,7 @@ class Checkbox(MixinElementWithValue, BaseWidget):
153
153
  self._tk_kwargs["font"] = font.Font(
154
154
  self.window.parent_tk_widget,
155
155
  family=self._fonttype,
156
- size=self._fontsize,
156
+ size=self._fontsize if self._fontsize else 8,
157
157
  weight="bold" if self._bold else "normal",
158
158
  slant="italic" if self._italic else "roman",
159
159
  underline=bool(self._underline),
@@ -285,7 +285,7 @@ class Combobox(MixinElementWithValue, BaseWidgetTTK):
285
285
  self._tk_kwargs["font"] = font.Font(
286
286
  self.window.parent_tk_widget,
287
287
  family=self._fonttype,
288
- size=self._fontsize,
288
+ size=self._fontsize if self._fontsize else 8,
289
289
  weight="bold" if self._bold else "normal",
290
290
  slant="italic" if self._italic else "roman",
291
291
  underline=bool(self._underline),
@@ -187,6 +187,9 @@ class Frame(BaseWidgetContainer):
187
187
 
188
188
  element._init(self._containing_row_elements[row_index], self.window)
189
189
 
190
+ if self.has_flag(ElementFlag.IS_CREATED):
191
+ element.init_window_creation_done()
192
+
190
193
  return self
191
194
 
192
195
  def get_row_index(self, elem: BaseElement) -> int | None:
@@ -160,7 +160,7 @@ class Input(MixinElementWithValue, BaseWidget):
160
160
  self._tk_kwargs["font"] = font.Font(
161
161
  self.window.parent_tk_widget,
162
162
  family=self._fonttype,
163
- size=self._fontsize,
163
+ size=self._fontsize if self._fontsize else 8,
164
164
  weight="bold" if self._bold else "normal",
165
165
  slant="italic" if self._italic else "roman",
166
166
  underline=bool(self._underline),
@@ -222,7 +222,7 @@ class Listbox(MixinElementWithValue, BaseWidget, MixinScrollbar):
222
222
  self._tk_kwargs["font"] = font.Font(
223
223
  self.window.parent_tk_widget,
224
224
  family=self._fonttype,
225
- size=self._fontsize,
225
+ size=self._fontsize if self._fontsize else 8,
226
226
  weight="bold" if self._bold else "normal",
227
227
  slant="italic" if self._italic else "roman",
228
228
  underline=bool(self._underline),
@@ -192,7 +192,7 @@ class Radiobutton(MixinElementWithDefaultEvent, BaseWidget):
192
192
  self._tk_kwargs["font"] = font.Font(
193
193
  self.window.parent_tk_widget,
194
194
  family=self._fonttype,
195
- size=self._fontsize,
195
+ size=self._fontsize if self._fontsize else 8,
196
196
  weight="bold" if self._bold else "normal",
197
197
  slant="italic" if self._italic else "roman",
198
198
  underline=bool(self._underline),
@@ -158,7 +158,7 @@ class Scale(MixinElementWithValue, BaseWidget):
158
158
  self._tk_kwargs["font"] = font.Font(
159
159
  self.window.parent_tk_widget,
160
160
  family=self._fonttype,
161
- size=self._fontsize,
161
+ size=self._fontsize if self._fontsize else 8,
162
162
  weight="bold" if self._bold else "normal",
163
163
  slant="italic" if self._italic else "roman",
164
164
  underline=bool(self._underline),
@@ -189,7 +189,7 @@ class Spinbox(MixinElementWithValue, BaseWidget):
189
189
  self._tk_kwargs["font"] = font.Font(
190
190
  self.window.parent_tk_widget,
191
191
  family=self._fonttype,
192
- size=self._fontsize,
192
+ size=self._fontsize if self._fontsize else 8,
193
193
  weight="bold" if self._bold else "normal",
194
194
  slant="italic" if self._italic else "roman",
195
195
  underline=bool(self._underline),
@@ -115,7 +115,7 @@ class Text(BaseWidget):
115
115
  self._tk_kwargs["font"] = font.Font(
116
116
  self.window.parent_tk_widget,
117
117
  family=self._fonttype,
118
- size=self._fontsize,
118
+ size=self._fontsize if self._fontsize else 8,
119
119
  weight="bold" if self._bold else "normal",
120
120
  slant="italic" if self._italic else "roman",
121
121
  underline=bool(self._underline),
@@ -258,7 +258,7 @@ class TextField(MixinElementWithValue, BaseWidget, MixinScrollbar):
258
258
  temp = font.Font(
259
259
  self.window.parent_tk_widget,
260
260
  family=self._fonttype,
261
- size=self._fontsize,
261
+ size=self._fontsize if self._fontsize else 8,
262
262
  weight="bold" if self._bold else "normal",
263
263
  slant="italic" if self._italic else "roman",
264
264
  underline=bool(self._underline),
@@ -140,7 +140,7 @@ class Treeview(BaseWidget):
140
140
  temp = font.Font(
141
141
  self.window.parent_tk_widget,
142
142
  family=self._fonttype,
143
- size=self._fontsize,
143
+ size=self._fontsize if self._fontsize else 8,
144
144
  weight="bold" if self._bold else "normal",
145
145
  slant="italic" if self._italic else "roman",
146
146
  underline=bool(self._underline),
@@ -16,7 +16,7 @@ from PIL import Image, ImageTk
16
16
  import time
17
17
  import logging
18
18
 
19
- from SwiftGUI import BaseElement, Frame, ElementFlag, Literals, GlobalOptions, Color, Debug
19
+ from SwiftGUI import BaseElement, Frame, ElementFlag, Literals, GlobalOptions, Color, Debug, Tooltips
20
20
 
21
21
  if TYPE_CHECKING:
22
22
  from SwiftGUI import AnyElement
@@ -640,6 +640,15 @@ class BaseKeyHandler(BaseElement):
640
640
  self.value.from_json(data)
641
641
  return self
642
642
 
643
+ def activate_tooltips(self) -> Self:
644
+ """
645
+ Activate the necessary event for tooltip-handling.
646
+ You don't need to call this, just ignore.
647
+ :return:
648
+ """
649
+ self.actual_window.activate_tooltips()
650
+ return self
651
+
643
652
  class SubLayout(BaseKeyHandler):
644
653
  """
645
654
  Can be used as an sg-element.
@@ -862,6 +871,7 @@ class Window(BaseKeyHandler):
862
871
 
863
872
  self._sg_widget:Frame = Frame(layout,alignment= self.defaults.single("alignment", alignment), expand_y=True, expand=True)
864
873
  self.root = tk.Tk()
874
+ self.root.withdraw()
865
875
  self.ttk_style: ttk.Style = ttk.Style(self.root)
866
876
 
867
877
  if ttk_style is None:
@@ -1425,6 +1435,22 @@ class Window(BaseKeyHandler):
1425
1435
 
1426
1436
  return x, y
1427
1437
 
1438
+ ### TOOLTIPS ###
1439
+ _tooltips_active: bool = False
1440
+ def activate_tooltips(self) -> Self:
1441
+ if self._tooltips_active:
1442
+ return self
1443
+
1444
+ self.bind_event("Motion", key_function=Tooltips.mouse_moved_callback)
1445
+ self._tooltips_active = True
1446
+ Tooltips.activate_tooltips()
1447
+ return self
1448
+
1449
+ @staticmethod
1450
+ def _mouse_moved():
1451
+ """Callback for the motion-event"""
1452
+ Tooltips.mouse_moved_callback()
1453
+
1428
1454
  class HiddenMainWindow(Window):
1429
1455
  def __init__(self):
1430
1456
  """
@@ -1509,7 +1535,8 @@ class SubWindow(Window):
1509
1535
  self._grab_anywhere = self.defaults.single("grab_anywhere", grab_anywhere)
1510
1536
 
1511
1537
  self._sg_widget:Frame = Frame(layout,alignment= self.defaults.single("alignment", alignment), expand_y=True, expand=True)
1512
- self.root: tk.Toplevel = tk.Toplevel()
1538
+ self.root: tk.Toplevel = tk.Toplevel(_main_window.root)
1539
+ self.root.withdraw() # Todo: Stop the window from flickering when it is being created
1513
1540
  self.ttk_style: ttk.Style = _main_window.ttk_style
1514
1541
 
1515
1542
  if icon is None:
@@ -1535,6 +1562,7 @@ class SubWindow(Window):
1535
1562
  )
1536
1563
 
1537
1564
  self.init(self._sg_widget, self.root, grab_anywhere_window= self)
1565
+ self.root.deiconify()
1538
1566
 
1539
1567
  self.bind_grab_anywhere_to_element(self._sg_widget.tk_widget)
1540
1568
 
@@ -1543,6 +1571,10 @@ class SubWindow(Window):
1543
1571
  _main_window.register_element(self)
1544
1572
  _main_window._value_dict.set_extra_value(key, self.value)
1545
1573
 
1574
+ def init_window_creation_done(self):
1575
+ super().init_window_creation_done()
1576
+ self.root.deiconify()
1577
+
1546
1578
  def loop_close(self, block_others: bool = True) -> tuple[Any,dict[Hashable, Any]]:
1547
1579
  """
1548
1580
  Loop until the first keyed event.
@@ -85,6 +85,7 @@ Slider = Scale
85
85
 
86
86
  Combo = Combobox
87
87
 
88
+ from . import Tooltips
88
89
 
89
90
  from .Windows import Window, BaseKeyHandler, ttk_style, main_window, SubLayout, all_decorator_key_functions, SubWindow, close_all_windows, ValueDict, call_periodically, HiddenMainWindow
90
91
 
File without changes
File without changes