scustomtkinter 0.2.1__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.
Files changed (101) hide show
  1. scustomtkinter/__init__.py +81 -0
  2. scustomtkinter/assets/images/error.png +0 -0
  3. scustomtkinter/assets/images/info.png +0 -0
  4. scustomtkinter/assets/images/warning.png +0 -0
  5. scustomtkinter/assets/sCTkThemes.json +1963 -0
  6. scustomtkinter/license_morecustomtkinterwidgets.txt +22 -0
  7. scustomtkinter/sctk_button_primary.py +366 -0
  8. scustomtkinter/sctk_button_secondary.py +354 -0
  9. scustomtkinter/sctk_button_tertiary.py +375 -0
  10. scustomtkinter/sctk_checkbox.py +270 -0
  11. scustomtkinter/sctk_combobox.py +155 -0
  12. scustomtkinter/sctk_core.py +15 -0
  13. scustomtkinter/sctk_dial.py +1472 -0
  14. scustomtkinter/sctk_dialog.py +701 -0
  15. scustomtkinter/sctk_dialog_toplevel.py +268 -0
  16. scustomtkinter/sctk_entry_primary.py +400 -0
  17. scustomtkinter/sctk_entry_secondary.py +342 -0
  18. scustomtkinter/sctk_file_explorer.py +879 -0
  19. scustomtkinter/sctk_frame.py +210 -0
  20. scustomtkinter/sctk_frame_labeled_primary.py +667 -0
  21. scustomtkinter/sctk_frame_labeled_secondary.py +598 -0
  22. scustomtkinter/sctk_frequency_display.py +636 -0
  23. scustomtkinter/sctk_label_primary.py +283 -0
  24. scustomtkinter/sctk_label_secondary.py +275 -0
  25. scustomtkinter/sctk_label_tertiary.py +278 -0
  26. scustomtkinter/sctk_messagebox.py +275 -0
  27. scustomtkinter/sctk_notebook.py +1103 -0
  28. scustomtkinter/sctk_optionmenu_border_mixin.py +203 -0
  29. scustomtkinter/sctk_optionmenu_primary.py +378 -0
  30. scustomtkinter/sctk_optionmenu_secondary.py +346 -0
  31. scustomtkinter/sctk_path_chooser.py +516 -0
  32. scustomtkinter/sctk_progress_bar.py +274 -0
  33. scustomtkinter/sctk_radiobutton.py +360 -0
  34. scustomtkinter/sctk_scroll_mixin.py +546 -0
  35. scustomtkinter/sctk_scrollable_frame.py +795 -0
  36. scustomtkinter/sctk_scrollbar.py +313 -0
  37. scustomtkinter/sctk_segmentedbutton.py +424 -0
  38. scustomtkinter/sctk_selector.py +581 -0
  39. scustomtkinter/sctk_separator.py +308 -0
  40. scustomtkinter/sctk_slider.py +264 -0
  41. scustomtkinter/sctk_smeter.py +405 -0
  42. scustomtkinter/sctk_smeter_bar.py +557 -0
  43. scustomtkinter/sctk_spinbox.py +664 -0
  44. scustomtkinter/sctk_switch.py +463 -0
  45. scustomtkinter/sctk_tableview.py +869 -0
  46. scustomtkinter/sctk_tabview.py +409 -0
  47. scustomtkinter/sctk_textbox_primary.py +291 -0
  48. scustomtkinter/sctk_textbox_secondary.py +225 -0
  49. scustomtkinter/sctk_toplevel.py +153 -0
  50. scustomtkinter/themeable_widget.py +938 -0
  51. scustomtkinter-0.2.1.dist-info/METADATA +336 -0
  52. scustomtkinter-0.2.1.dist-info/RECORD +101 -0
  53. scustomtkinter-0.2.1.dist-info/WHEEL +5 -0
  54. scustomtkinter-0.2.1.dist-info/entry_points.txt +2 -0
  55. scustomtkinter-0.2.1.dist-info/licenses/LICENSE +21 -0
  56. scustomtkinter-0.2.1.dist-info/licenses/scustomtkinter/license_morecustomtkinterwidgets.txt +22 -0
  57. scustomtkinter-0.2.1.dist-info/top_level.txt +2 -0
  58. scustomtkinter_pygubu/__init__.py +1 -0
  59. scustomtkinter_pygubu/designer/__init__.py +0 -0
  60. scustomtkinter_pygubu/designer/plugin.py +1854 -0
  61. scustomtkinter_pygubu/designer/properties.py +247 -0
  62. scustomtkinter_pygubu/sCTkButtonPrimarybo.py +46 -0
  63. scustomtkinter_pygubu/sCTkButtonSecondarybo.py +45 -0
  64. scustomtkinter_pygubu/sCTkButtonTertiarybo.py +44 -0
  65. scustomtkinter_pygubu/sCTkCheckBoxbo.py +45 -0
  66. scustomtkinter_pygubu/sCTkComboBoxbo.py +45 -0
  67. scustomtkinter_pygubu/sCTkCorebo.py +120 -0
  68. scustomtkinter_pygubu/sCTkDialbo.py +371 -0
  69. scustomtkinter_pygubu/sCTkDialogbo.py +397 -0
  70. scustomtkinter_pygubu/sCTkEntryPrimarybo.py +100 -0
  71. scustomtkinter_pygubu/sCTkEntrySecondarybo.py +98 -0
  72. scustomtkinter_pygubu/sCTkFileExplorerbo.py +189 -0
  73. scustomtkinter_pygubu/sCTkFrameLabeledPrimarybo.py +69 -0
  74. scustomtkinter_pygubu/sCTkFrameLabeledSecondarybo.py +71 -0
  75. scustomtkinter_pygubu/sCTkFramebo.py +46 -0
  76. scustomtkinter_pygubu/sCTkFrequencyDisplaybo.py +175 -0
  77. scustomtkinter_pygubu/sCTkLabelPrimarybo.py +45 -0
  78. scustomtkinter_pygubu/sCTkLabelSecondarybo.py +44 -0
  79. scustomtkinter_pygubu/sCTkLabelTertiarybo.py +45 -0
  80. scustomtkinter_pygubu/sCTkNotebookbo.py +392 -0
  81. scustomtkinter_pygubu/sCTkOptionMenuPrimarybo.py +45 -0
  82. scustomtkinter_pygubu/sCTkOptionMenuSecondarybo.py +39 -0
  83. scustomtkinter_pygubu/sCTkPathChooserbo.py +204 -0
  84. scustomtkinter_pygubu/sCTkProgressBarbo.py +45 -0
  85. scustomtkinter_pygubu/sCTkRadioButtonbo.py +45 -0
  86. scustomtkinter_pygubu/sCTkSMeterBarbo.py +153 -0
  87. scustomtkinter_pygubu/sCTkSMeterbo.py +101 -0
  88. scustomtkinter_pygubu/sCTkScrollableFramebo.py +66 -0
  89. scustomtkinter_pygubu/sCTkScrollbarbo.py +48 -0
  90. scustomtkinter_pygubu/sCTkSegmentedButtonbo.py +47 -0
  91. scustomtkinter_pygubu/sCTkSelectorbo.py +167 -0
  92. scustomtkinter_pygubu/sCTkSeparatorbo.py +141 -0
  93. scustomtkinter_pygubu/sCTkSliderbo.py +45 -0
  94. scustomtkinter_pygubu/sCTkSpinboxbo.py +200 -0
  95. scustomtkinter_pygubu/sCTkSwitchbo.py +45 -0
  96. scustomtkinter_pygubu/sCTkTableviewbo.py +188 -0
  97. scustomtkinter_pygubu/sCTkTabviewbo.py +226 -0
  98. scustomtkinter_pygubu/sCTkTextboxPrimarybo.py +45 -0
  99. scustomtkinter_pygubu/sCTkTextboxSecondarybo.py +45 -0
  100. scustomtkinter_pygubu/sCTkToplevelbo.py +55 -0
  101. scustomtkinter_pygubu/sCTkWidgetSetForPygubuDesigner.py +141 -0
@@ -0,0 +1,371 @@
1
+ #!/usr/bin/python3
2
+ """
3
+ sCTkDialbo
4
+
5
+ Unified Pygubu Builder Objects for the complete sCTkDial custom widget suite.
6
+ Houses sCTkDialContinuous, sCTkDialRange, and sCTkDialSelector builders
7
+ side-by-side, sharing the metadata configuration pipeline.
8
+ """
9
+ import pygubu
10
+ from pygubu.api.v1 import (
11
+ BuilderObject,
12
+ register_widget,
13
+ register_custom_property
14
+ )
15
+ from pygubu.plugins.customtkinter.widgets import CTkFrameBO
16
+
17
+ # Import the native custom classes directly out of your single-file source module
18
+ from scustomtkinter.sctk_dial import sCTkDialContinuous, sCTkDialRange, sCTkDialSelector
19
+ from scustomtkinter.themeable_widget import parse_list_property, parse_font_property
20
+
21
+ builder_namespace = "scustomtkinter"
22
+ section_name = "sCustomTkinter"
23
+
24
+
25
+ # =====================================================================
26
+ # 1. BUILDER OBJECT: INFINITE FLYWHEEL TUNING ENCODER (CONTINUOUS)
27
+ # =====================================================================
28
+ class sCTkDialContinuousBO(BuilderObject):
29
+ class_ = sCTkDialContinuous
30
+ classname = "sCTkDialContinuous"
31
+ _code_classname = "sCTkDialContinuous"
32
+ container = False
33
+
34
+ # width and height come from CTkFrameBO.properties below, so only `state`
35
+ # is listed here. `properties` decides what the inspector SHOWS; the
36
+ # copy_custom_property() calls in designer/properties.py supply the editor
37
+ # definition for each name. Both are required -- copying alone leaves a
38
+ # property invisible, listing alone leaves it with no editor.
39
+ OPTIONS_STANDARD = ("state",)
40
+ OPTIONS_CUSTOM = ("divisions", "knob_diameter", "command", "left_click_callback", "right_click_callback",
41
+ "double_click_command", "shift_double_click_command",
42
+ "latching",
43
+ "text_color", "dial_color", "pointer_glow_color")
44
+ properties = CTkFrameBO.properties + OPTIONS_STANDARD + OPTIONS_CUSTOM
45
+ command_properties = ("command", "left_click_callback",
46
+ "right_click_callback", "double_click_command",
47
+ "shift_double_click_command")
48
+
49
+ def _code_define_callback_args(self, cmd_pname, cmd):
50
+ """
51
+ Declares what each callback receives, so the generated stub has
52
+ matching parameters.
53
+
54
+ Without this, pygubu generates every stub with no arguments -- and the
55
+ dial calls `command` with a value:
56
+
57
+ TypeError: on_turn() takes 1 positional argument but 2 were given
58
+
59
+ The two click callbacks genuinely take nothing, so only `command` gets
60
+ a parameter. Continuous reports a signed step delta rather than a position, since it has none.
61
+ """
62
+ if cmd_pname in ("double_click_command", "shift_double_click_command"):
63
+ # Called with the dial itself, so a handler can act on it without
64
+ # a closure over the variable name -- typically to arm a latching
65
+ # dial:
66
+ #
67
+ # def on_double_click(self, dial):
68
+ # dial.toggle_pressed()
69
+ return ("dial",)
70
+ if cmd_pname in ("left_click_callback", "right_click_callback"):
71
+ # Deferred to the base rather than returning () -- pygubu joins the
72
+ # names onto "self, ", so an empty tuple left the separator with
73
+ # nothing after it:
74
+ #
75
+ # def left_dialRange_CB(self, ):
76
+ #
77
+ # Valid Python, but untidy. The base class already produces a stub
78
+ # with no extra parameters.
79
+ return super()._code_define_callback_args(cmd_pname, cmd)
80
+ return ("step_delta",)
81
+
82
+
83
+ # NOTE: realize() no longer forces width and height from the knob size.
84
+ #
85
+ # It used to write both canvas dimensions from `diameter`, overwriting
86
+ # whatever the user had set in the inspector -- so a dial could not be
87
+ # given a canvas wider than it was tall, and long labels had nowhere to go.
88
+ #
89
+ # knob_diameter now names the KNOB and width/height name the CANVAS, which
90
+ # is what those words should always have meant. The widget supplies a
91
+ # canvas default around the knob when none is given, so a bare
92
+ # knob_diameter still works -- see sCTKDialBase._default_canvas_size().
93
+
94
+
95
+ # =====================================================================
96
+ # 2. BUILDER OBJECT: HARD END-STOP POTENTIOMETER (RANGE)
97
+ # =====================================================================
98
+ class sCTkDialRangeBO(BuilderObject):
99
+ class_ = sCTkDialRange
100
+ classname = "sCTkDialRange"
101
+ _code_classname = "sCTkDialRange"
102
+ container = False
103
+
104
+ # width and height come from CTkFrameBO.properties below, so only `state`
105
+ # is listed here. `properties` decides what the inspector SHOWS; the
106
+ # copy_custom_property() calls in designer/properties.py supply the editor
107
+ # definition for each name. Both are required -- copying alone leaves a
108
+ # property invisible, listing alone leaves it with no editor.
109
+ OPTIONS_STANDARD = ("state",)
110
+ OPTIONS_CUSTOM = ("from_", "to", "divisions", "knob_diameter", "arc_angle","command", "left_click_callback", "right_click_callback", "label_font",
111
+ "double_click_command", "shift_double_click_command",
112
+ "latching",
113
+ "text_color", "dial_color", "pointer_color")
114
+ properties = CTkFrameBO.properties + OPTIONS_STANDARD + OPTIONS_CUSTOM
115
+ command_properties = ("command", "left_click_callback",
116
+ "right_click_callback", "double_click_command",
117
+ "shift_double_click_command")
118
+
119
+ def _code_define_callback_args(self, cmd_pname, cmd):
120
+ """
121
+ Declares what each callback receives, so the generated stub has
122
+ matching parameters.
123
+
124
+ Without this, pygubu generates every stub with no arguments -- and the
125
+ dial calls `command` with a value:
126
+
127
+ TypeError: on_turn() takes 1 positional argument but 2 were given
128
+
129
+ The two click callbacks genuinely take nothing, so only `command` gets
130
+ a parameter. Range reports the new value.
131
+ """
132
+ if cmd_pname in ("double_click_command", "shift_double_click_command"):
133
+ # Called with the dial itself, so a handler can act on it without
134
+ # a closure over the variable name -- typically to arm a latching
135
+ # dial:
136
+ #
137
+ # def on_double_click(self, dial):
138
+ # dial.toggle_pressed()
139
+ return ("dial",)
140
+ if cmd_pname in ("left_click_callback", "right_click_callback"):
141
+ # Deferred to the base rather than returning () -- pygubu joins the
142
+ # names onto "self, ", so an empty tuple left the separator with
143
+ # nothing after it:
144
+ #
145
+ # def left_dialRange_CB(self, ):
146
+ #
147
+ # Valid Python, but untidy. The base class already produces a stub
148
+ # with no extra parameters.
149
+ return super()._code_define_callback_args(cmd_pname, cmd)
150
+ return ("value",)
151
+
152
+
153
+ # NOTE: realize() no longer forces width and height from the knob size.
154
+ # See the equivalent note on sCTkDialContinuousBO above.
155
+
156
+
157
+ # =====================================================================
158
+ # 3. BUILDER OBJECT: DISCRETE SWITCH SELECTOR (SELECTOR)
159
+ # =====================================================================
160
+ class sCTkDialSelectorBO(BuilderObject):
161
+ class_ = sCTkDialSelector
162
+ classname = "sCTkDialSelector"
163
+ _code_classname = "sCTkDialSelector"
164
+ container = False
165
+
166
+ # width and height come from CTkFrameBO.properties below, so only `state`
167
+ # is listed here. `properties` decides what the inspector SHOWS; the
168
+ # copy_custom_property() calls in designer/properties.py supply the editor
169
+ # definition for each name. Both are required -- copying alone leaves a
170
+ # property invisible, listing alone leaves it with no editor.
171
+ OPTIONS_STANDARD = ("state",)
172
+ OPTIONS_CUSTOM = ("knob_diameter", "arc_angle","command", "left_click_callback", "right_click_callback", "labels", "label_font",
173
+ "double_click_command", "shift_double_click_command",
174
+ "latching",
175
+ "text_color", "dial_color", "pointer_color") # Labels handles lists, usually initialized in code
176
+ properties = CTkFrameBO.properties + OPTIONS_STANDARD + OPTIONS_CUSTOM
177
+ command_properties = ("command", "left_click_callback",
178
+ "right_click_callback", "double_click_command",
179
+ "shift_double_click_command")
180
+
181
+ def _code_define_callback_args(self, cmd_pname, cmd):
182
+ """
183
+ Declares what each callback receives, so the generated stub has
184
+ matching parameters.
185
+
186
+ Without this, pygubu generates every stub with no arguments -- and the
187
+ dial calls `command` with a value:
188
+
189
+ TypeError: on_turn() takes 1 positional argument but 2 were given
190
+
191
+ The two click callbacks genuinely take nothing, so only `command` gets
192
+ a parameter. Selector reports the index of the chosen label, not the label itself.
193
+ """
194
+ if cmd_pname in ("double_click_command", "shift_double_click_command"):
195
+ # Called with the dial itself, so a handler can act on it without
196
+ # a closure over the variable name -- typically to arm a latching
197
+ # dial:
198
+ #
199
+ # def on_double_click(self, dial):
200
+ # dial.toggle_pressed()
201
+ return ("dial",)
202
+ if cmd_pname in ("left_click_callback", "right_click_callback"):
203
+ # Deferred to the base rather than returning () -- pygubu joins the
204
+ # names onto "self, ", so an empty tuple left the separator with
205
+ # nothing after it:
206
+ #
207
+ # def left_dialRange_CB(self, ):
208
+ #
209
+ # Valid Python, but untidy. The base class already produces a stub
210
+ # with no extra parameters.
211
+ return super()._code_define_callback_args(cmd_pname, cmd)
212
+ return ("selected_index",)
213
+
214
+
215
+ def _process_property_value(self, name, value):
216
+ if name == 'label_font':
217
+ # Shared parser: handles the "{}" Tk emits for "no style",
218
+ # which reaches a canvas item as unknown font style "".
219
+ return parse_font_property(value)
220
+ if name == 'labels':
221
+ # FIX: was value.split(","), which did not strip whitespace -- so
222
+ # "AM, FM, LSB" produced ["AM", " FM", " LSB"] and the dial drew
223
+ # labels with leading spaces. The shared parser also accepts the
224
+ # Python-literal form, so this property now behaves identically to
225
+ # sCTkTableview's `columns` and sCTkSelector's `items`.
226
+ return parse_list_property(value)
227
+ return value
228
+
229
+ def _code_set_property(self, targetid, pname, value, code_bag):
230
+ """
231
+ Emits `labels` as a real Python list, not a quoted string.
232
+
233
+ The bracketed form -- the one this widget's own help text recommends --
234
+ contains double quotes, and the default handler wraps the whole value
235
+ in double quotes again:
236
+
237
+ sctkdialselector1.configure(labels="["AM", "FM"]", ...)
238
+ ^^^^^^^^^^^^^
239
+ SyntaxError: invalid syntax
240
+
241
+ Emitting the literal unquoted gives valid Python and a real list, so
242
+ the widget receives one directly rather than parsing a string back out
243
+ of it. Same approach sCTkSelectorbo uses for `items` and
244
+ sCTkFileExplorerbo for `filetypes`.
245
+
246
+ A bare comma-separated value has no brackets and no quotes, so it is
247
+ emitted as a quoted string and parsed by the widget as usual.
248
+ """
249
+ if pname == "labels" and value:
250
+ text = str(value).strip()
251
+ if text.startswith("[") and text.endswith("]"):
252
+ code_bag[pname] = text
253
+ return None
254
+ return super()._code_set_property(targetid, pname, value, code_bag)
255
+
256
+ # NOTE: realize() no longer forces width and height from the knob size.
257
+ # See the equivalent note on sCTkDialContinuousBO above.
258
+ #
259
+ # This one mattered most: a Selector's labels are user text and can be any
260
+ # length, so a square canvas locked to the knob size was exactly the wrong
261
+ # constraint -- there was no way to widen it for a long label.
262
+
263
+
264
+ # =====================================================================
265
+ # UNIFIED MODULE-LEVEL PYGUBU REGISTRATION LEDGER
266
+ # Registers all three semantic variations simultaneously inside the palette.
267
+ # =====================================================================
268
+
269
+ # --- 1. CONTINUOUS VFO ENCODER MATRIX REGISTRY ---
270
+ id_continuous = f"{builder_namespace}.sCTkDialContinuous"
271
+ register_widget(id_continuous, sCTkDialContinuousBO, "sCTkDialContinuous", ("ttk", section_name))
272
+ # `state` appears in OPTIONS_STANDARD for all three dials but was
273
+ # registered for none of them, so Pygubu supplied its own definition --
274
+ # which is why the Selector and Range dials offered a third value that
275
+ # the Continuous dial did not. Registering it explicitly makes all
276
+ # three agree and matches the widget's real two-state model.
277
+ register_custom_property(id_continuous, "text_color", "colorentry", help="Colour of the labels AND the tick marks -- both are drawn with this one key. Blank uses the theme's text_color.")
278
+ register_custom_property(id_continuous, "dial_color", "colorentry", help="Colour of the knob face. Blank uses the theme's dial_color.")
279
+ register_custom_property(id_continuous, "pointer_glow_color", "colorentry", help="Colour of the glow around the pointer. Blank uses the theme's pointer_glow_color.")
280
+ # `pressed` is deliberately NOT a Designer property.
281
+ #
282
+ # It is the dial's initial armed state, and the inspector reflects the LIVE
283
+ # widget -- so double-clicking a dial on the design canvas set it True, and
284
+ # pygubu then wrote pressed=True into the generated code as a non-default.
285
+ # The application started armed, which is the opposite of what latching is
286
+ # for. It remains a constructor argument for code that genuinely wants a dial
287
+ # to start switched on.
288
+ register_custom_property(id_continuous, "latching", "choice", values=("True", "False"),
289
+ default_value="False", help="Opt-in. True makes the dial start switched off and ignore clicks, drags and the wheel until armed -- typically by wiring double_click_command to toggle_pressed(). It then draws from the theme's pressed_map. False, the default, is always live.")
290
+ register_custom_property(id_continuous, "state", "choice", values=("normal", "disabled"), help="Enabled or dimmed and inert.")
291
+ register_custom_property(id_continuous, "width", "naturalnumber", help="Width in pixels.")
292
+ register_custom_property(id_continuous, "height", "naturalnumber", help="Height in pixels.")
293
+ register_custom_property(id_continuous, "divisions", "naturalnumber", default_value=24, help="Flywheel detents per 360 turn.")
294
+ register_custom_property(id_continuous, "knob_diameter", "naturalnumber", default_value=120,
295
+ help="Diameter of the knob itself, in pixels. Set width and height for the canvas around it.")
296
+ register_custom_property(id_continuous, "command", "commandentry", help="Callback for knob turn by mousewheel.")
297
+ register_custom_property(id_continuous, "left_click_callback", "commandentry", help="Callback for left mouse click.")
298
+ register_custom_property(id_continuous, "double_click_command", "commandentry",
299
+ help="Callback for a double-click on the knob. Receives the dial itself. "
300
+ "Wire it to toggle_pressed() to arm a latching dial.")
301
+ register_custom_property(id_continuous, "shift_double_click_command", "commandentry",
302
+ help="Callback for a shift-double-click on the knob. Receives the dial itself. "
303
+ "Its own gesture because a plain double-click competes with click-stepping; "
304
+ "wire it to disarm a latching dial.")
305
+ register_custom_property(id_continuous, "right_click_callback", "commandentry", help="Callback for right mouse click.")
306
+
307
+ # --- 2. RANGED POTENTIOMETER MATRIX REGISTRY ---
308
+ id_range = f"{builder_namespace}.sCTkDialRange"
309
+ register_widget(id_range, sCTkDialRangeBO, "sCTkDialRange", ("ttk", section_name))
310
+ # `state` appears in OPTIONS_STANDARD for all three dials but was
311
+ # registered for none of them, so Pygubu supplied its own definition --
312
+ # which is why the Selector and Range dials offered a third value that
313
+ # the Continuous dial did not. Registering it explicitly makes all
314
+ # three agree and matches the widget's real two-state model.
315
+ register_custom_property(id_range, "label_font", "fontentry", help="Font for the labels drawn around the dial. Blank uses the theme's label_font.")
316
+ register_custom_property(id_range, "text_color", "colorentry", help="Colour of the labels AND the tick marks -- both are drawn with this one key. Blank uses the theme's text_color.")
317
+ register_custom_property(id_range, "dial_color", "colorentry", help="Colour of the knob face. Blank uses the theme's dial_color.")
318
+ register_custom_property(id_range, "pointer_color", "colorentry", help="Colour of the pointer on the knob. Blank uses the theme's pointer_color.")
319
+ register_custom_property(id_range, "latching", "choice", values=("True", "False"),
320
+ default_value="False", help="Opt-in. True makes the dial start switched off and ignore clicks, drags and the wheel until armed -- typically by wiring double_click_command to toggle_pressed(). It then draws from the theme's pressed_map. False, the default, is always live.")
321
+ register_custom_property(id_range, "state", "choice", values=("normal", "disabled"), help="Enabled or dimmed and inert.")
322
+ register_custom_property(id_range, "width", "naturalnumber", help="Width in pixels.")
323
+ register_custom_property(id_range, "height", "naturalnumber", help="Height in pixels.")
324
+ register_custom_property(id_range, "from_", "integernumber", default_value=0, help="Absolute minimum boundary limit.")
325
+ register_custom_property(id_range, "to", "integernumber", default_value=100, help="Absolute maximum boundary limit.")
326
+ register_custom_property(id_range, "divisions", "naturalnumber", default_value=5, help="Number of calibration tick lines drawn.")
327
+ register_custom_property(id_range, "knob_diameter", "naturalnumber", default_value=120,
328
+ help="Diameter of the knob itself, in pixels. Widen the canvas with width and height if labels are clipped.")
329
+ register_custom_property(id_range, "arc_angle", "naturalnumber", default_value=270, help="Symmetrical active arc sweep.")
330
+ register_custom_property(id_range, "command", "commandentry", help="Callback for knob turn by mousewheel.")
331
+ register_custom_property(id_range, "left_click_callback", "commandentry", help="Callback for left mouse click.")
332
+ register_custom_property(id_range, "double_click_command", "commandentry",
333
+ help="Callback for a double-click on the knob. Receives the dial itself. "
334
+ "Wire it to toggle_pressed() to arm a latching dial.")
335
+ register_custom_property(id_range, "shift_double_click_command", "commandentry",
336
+ help="Callback for a shift-double-click on the knob. Receives the dial itself. "
337
+ "Its own gesture because a plain double-click competes with click-stepping; "
338
+ "wire it to disarm a latching dial.")
339
+ register_custom_property(id_range, "right_click_callback", "commandentry", help="Callback for right mouse click.")
340
+
341
+ # --- 3. MODE SELECTOR MATRIX REGISTRY ---
342
+ id_selector = f"{builder_namespace}.sCTkDialSelector"
343
+ register_widget(id_selector, sCTkDialSelectorBO, "sCTkDialSelector", ("ttk", section_name))
344
+ # `state` appears in OPTIONS_STANDARD for all three dials but was
345
+ # registered for none of them, so Pygubu supplied its own definition --
346
+ # which is why the Selector and Range dials offered a third value that
347
+ # the Continuous dial did not. Registering it explicitly makes all
348
+ # three agree and matches the widget's real two-state model.
349
+ register_custom_property(id_selector, "label_font", "fontentry", help="Font for the labels drawn around the dial. Blank uses the theme's label_font.")
350
+ register_custom_property(id_selector, "text_color", "colorentry", help="Colour of the labels AND the tick marks -- both are drawn with this one key. Blank uses the theme's text_color.")
351
+ register_custom_property(id_selector, "dial_color", "colorentry", help="Colour of the knob face. Blank uses the theme's dial_color.")
352
+ register_custom_property(id_selector, "pointer_color", "colorentry", help="Colour of the pointer on the knob. Blank uses the theme's pointer_color.")
353
+ register_custom_property(id_selector, "latching", "choice", values=("True", "False"),
354
+ default_value="False", help="Opt-in. True makes the dial start switched off and ignore clicks, drags and the wheel until armed -- typically by wiring double_click_command to toggle_pressed(). It then draws from the theme's pressed_map. False, the default, is always live.")
355
+ register_custom_property(id_selector, "state", "choice", values=("normal", "disabled"), help="Enabled or dimmed and inert.")
356
+ register_custom_property(id_selector, "width", "naturalnumber", help="Width in pixels.")
357
+ register_custom_property(id_selector, "height", "naturalnumber", help="Height in pixels.")
358
+ register_custom_property(id_selector, "knob_diameter", "naturalnumber", default_value=120,
359
+ help="Diameter of the knob itself, in pixels. Widen the canvas with width and height if labels are clipped.")
360
+ register_custom_property(id_selector, "arc_angle", "naturalnumber", default_value=270, help="Symmetrical active arc sweep.")
361
+ register_custom_property(id_selector, "command", "commandentry", help="Callback for knob turn by mousewheel.")
362
+ register_custom_property(id_selector, "left_click_callback", "commandentry", help="Callback for left mouse click.")
363
+ register_custom_property(id_selector, "double_click_command", "commandentry",
364
+ help="Callback for a double-click on the knob. Receives the dial itself. "
365
+ "Wire it to toggle_pressed() to arm a latching dial.")
366
+ register_custom_property(id_selector, "shift_double_click_command", "commandentry",
367
+ help="Callback for a shift-double-click on the knob. Receives the dial itself. "
368
+ "Its own gesture because a plain double-click competes with click-stepping; "
369
+ "wire it to disarm a latching dial.")
370
+ register_custom_property(id_selector, "right_click_callback", "commandentry", help="Callback for right mouse click.")
371
+ register_custom_property(id_selector, "labels", "entry", help='Preferred: ["A", "B", "C"]. Bare comma-separated (A, B, C) also works, but cannot contain a comma inside a value.')