spacr 1.0.7__py3-none-any.whl → 1.1.0__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.
- spacr/app_classify.py +10 -0
- spacr/app_mask.py +9 -0
- spacr/app_measure.py +9 -0
- spacr/app_sequencing.py +9 -0
- spacr/core.py +172 -1
- spacr/deep_spacr.py +296 -7
- spacr/gui.py +68 -0
- spacr/gui_core.py +319 -10
- spacr/gui_elements.py +772 -13
- spacr/gui_utils.py +301 -151
- spacr/io.py +887 -71
- spacr/logger.py +36 -0
- spacr/measure.py +206 -28
- spacr/ml.py +606 -142
- spacr/plot.py +797 -131
- spacr/sequencing.py +363 -8
- spacr/settings.py +1158 -38
- spacr/sp_stats.py +80 -12
- spacr/spacr_cellpose.py +115 -2
- spacr/submodules.py +747 -19
- spacr/timelapse.py +237 -53
- spacr/toxo.py +132 -6
- spacr/utils.py +2422 -80
- {spacr-1.0.7.dist-info → spacr-1.1.0.dist-info}/METADATA +31 -17
- {spacr-1.0.7.dist-info → spacr-1.1.0.dist-info}/RECORD +29 -29
- {spacr-1.0.7.dist-info → spacr-1.1.0.dist-info}/LICENSE +0 -0
- {spacr-1.0.7.dist-info → spacr-1.1.0.dist-info}/WHEEL +0 -0
- {spacr-1.0.7.dist-info → spacr-1.1.0.dist-info}/entry_points.txt +0 -0
- {spacr-1.0.7.dist-info → spacr-1.1.0.dist-info}/top_level.txt +0 -0
spacr/gui_elements.py
CHANGED
@@ -12,8 +12,14 @@ fig = None
|
|
12
12
|
|
13
13
|
def restart_gui_app(root):
|
14
14
|
"""
|
15
|
-
Restarts the GUI application by destroying the current
|
16
|
-
and launching a fresh
|
15
|
+
Restarts the SpaCr GUI application by destroying the current root window
|
16
|
+
and launching a fresh instance.
|
17
|
+
|
18
|
+
Args:
|
19
|
+
root (tk.Tk): The current Tkinter root window to be destroyed.
|
20
|
+
|
21
|
+
Note:
|
22
|
+
The new instance is launched by importing and invoking `gui_app()`.
|
17
23
|
"""
|
18
24
|
try:
|
19
25
|
# Destroy the current root window
|
@@ -27,6 +33,20 @@ def restart_gui_app(root):
|
|
27
33
|
print(f"Error restarting GUI application: {e}")
|
28
34
|
|
29
35
|
def create_menu_bar(root):
|
36
|
+
"""
|
37
|
+
Creates a top-level menu bar for the SpaCr GUI containing shortcuts to all
|
38
|
+
major application modules and help resources.
|
39
|
+
|
40
|
+
Args:
|
41
|
+
root (tk.Tk): The root window where the menu bar will be attached.
|
42
|
+
|
43
|
+
Adds:
|
44
|
+
- A 'SpaCr Applications' menu with links to:
|
45
|
+
'Mask', 'Measure', 'Classify', 'ML Analyze', 'Map Barcodes',
|
46
|
+
'Regression', 'Activation', and 'Recruitment'.
|
47
|
+
- A Help option linking to the online documentation.
|
48
|
+
- An Exit option to quit the application.
|
49
|
+
"""
|
30
50
|
from .gui import initiate_root
|
31
51
|
gui_apps = {
|
32
52
|
"Mask": lambda: initiate_root(root, settings_type='mask'),
|
@@ -63,7 +83,18 @@ def create_menu_bar(root):
|
|
63
83
|
root.config(menu=menu_bar)
|
64
84
|
|
65
85
|
def set_element_size():
|
66
|
-
|
86
|
+
"""
|
87
|
+
Calculates and returns standardized UI element dimensions
|
88
|
+
based on the current screen size.
|
89
|
+
|
90
|
+
Returns:
|
91
|
+
dict: A dictionary with element dimensions including:
|
92
|
+
- 'btn_size' (int): Size of buttons.
|
93
|
+
- 'bar_size' (int): Height of progress bars.
|
94
|
+
- 'settings_width' (int): Width of the settings panel.
|
95
|
+
- 'panel_width' (int): Width of the plotting panel.
|
96
|
+
- 'panel_height' (int): Height of the bottom control panel.
|
97
|
+
"""
|
67
98
|
screen_width, screen_height = pyautogui.size()
|
68
99
|
screen_area = screen_width * screen_height
|
69
100
|
|
@@ -84,7 +115,24 @@ def set_element_size():
|
|
84
115
|
return size_dict
|
85
116
|
|
86
117
|
def set_dark_style(style, parent_frame=None, containers=None, widgets=None, font_family="OpenSans", font_size=12, bg_color='black', fg_color='white', active_color='blue', inactive_color='dark_gray'):
|
87
|
-
|
118
|
+
"""
|
119
|
+
Applies a dark theme to the SpaCr GUI using the provided styling options.
|
120
|
+
|
121
|
+
Args:
|
122
|
+
style (ttk.Style): The ttk style instance to configure.
|
123
|
+
parent_frame (tk.Widget, optional): The top-level container to apply styles to.
|
124
|
+
containers (list, optional): Additional containers (ttk.Frame or tk.Frame) to style.
|
125
|
+
widgets (list, optional): List of individual widgets to apply colors and fonts.
|
126
|
+
font_family (str): Font family for all labels and buttons.
|
127
|
+
font_size (int): Font size for all text elements.
|
128
|
+
bg_color (str): Background color.
|
129
|
+
fg_color (str): Foreground/text color.
|
130
|
+
active_color (str): Highlight or selected color.
|
131
|
+
inactive_color (str): Secondary background color.
|
132
|
+
|
133
|
+
Returns:
|
134
|
+
dict: Style parameters used, including resolved font and color values.
|
135
|
+
"""
|
88
136
|
if active_color == 'teal':
|
89
137
|
active_color = '#008080'
|
90
138
|
if inactive_color == 'dark_gray':
|
@@ -163,7 +211,7 @@ class spacrFont:
|
|
163
211
|
"""
|
164
212
|
Initializes the FontLoader class.
|
165
213
|
|
166
|
-
|
214
|
+
Args:
|
167
215
|
- font_name: str, the name of the font (e.g., 'OpenSans').
|
168
216
|
- font_style: str, the style of the font (e.g., 'Regular', 'Bold').
|
169
217
|
- font_size: int, the size of the font (default: 12).
|
@@ -182,7 +230,7 @@ class spacrFont:
|
|
182
230
|
"""
|
183
231
|
Returns the font path based on the font name and style.
|
184
232
|
|
185
|
-
|
233
|
+
Args:
|
186
234
|
- font_name: str, the name of the font.
|
187
235
|
- font_style: str, the style of the font.
|
188
236
|
|
@@ -221,7 +269,7 @@ class spacrFont:
|
|
221
269
|
"""
|
222
270
|
Returns the font in the specified size.
|
223
271
|
|
224
|
-
|
272
|
+
Args:
|
225
273
|
- size: int, the size of the font (optional).
|
226
274
|
|
227
275
|
Returns:
|
@@ -232,7 +280,24 @@ class spacrFont:
|
|
232
280
|
return font.Font(family=self.font_name, size=size)
|
233
281
|
|
234
282
|
class spacrContainer(tk.Frame):
|
283
|
+
"""
|
284
|
+
A custom container widget that manages multiple resizable panes arranged
|
285
|
+
either vertically or horizontally, separated by draggable sashes.
|
286
|
+
|
287
|
+
Args:
|
288
|
+
parent (tk.Widget): The parent widget.
|
289
|
+
orient (str): Orientation of the layout ('tk.VERTICAL' or 'tk.HORIZONTAL'). Default is vertical.
|
290
|
+
bg (str): Background color of the container and sashes. Defaults to 'lightgrey'.
|
291
|
+
"""
|
235
292
|
def __init__(self, parent, orient=tk.VERTICAL, bg=None, *args, **kwargs):
|
293
|
+
"""
|
294
|
+
Initialize the spacrContainer with the specified orientation and background color.
|
295
|
+
|
296
|
+
Args:
|
297
|
+
parent (tk.Widget): Parent widget.
|
298
|
+
orient (str): Layout orientation (tk.VERTICAL or tk.HORIZONTAL).
|
299
|
+
bg (str, optional): Background color. Defaults to 'lightgrey'.
|
300
|
+
"""
|
236
301
|
super().__init__(parent, *args, **kwargs)
|
237
302
|
self.orient = orient
|
238
303
|
self.bg = bg if bg else 'lightgrey'
|
@@ -246,6 +311,13 @@ class spacrContainer(tk.Frame):
|
|
246
311
|
self.grid_columnconfigure(0, weight=1)
|
247
312
|
|
248
313
|
def add(self, widget, stretch='always'):
|
314
|
+
"""
|
315
|
+
Add a new widget as a pane to the container.
|
316
|
+
|
317
|
+
Args:
|
318
|
+
widget (tk.Widget): Widget to add.
|
319
|
+
stretch (str): Stretch policy (currently unused).
|
320
|
+
"""
|
249
321
|
print(f"Adding widget: {widget} with stretch: {stretch}")
|
250
322
|
pane = tk.Frame(self, bg=self.bg)
|
251
323
|
pane.grid_propagate(False)
|
@@ -258,6 +330,9 @@ class spacrContainer(tk.Frame):
|
|
258
330
|
self.reposition_panes()
|
259
331
|
|
260
332
|
def create_sash(self):
|
333
|
+
"""
|
334
|
+
Create a draggable sash between panes.
|
335
|
+
"""
|
261
336
|
sash = tk.Frame(self, bg=self.bg, cursor='sb_v_double_arrow' if self.orient == tk.VERTICAL else 'sb_h_double_arrow', height=self.sash_thickness, width=self.sash_thickness)
|
262
337
|
sash.bind("<Enter>", self.on_enter_sash)
|
263
338
|
sash.bind("<Leave>", self.on_leave_sash)
|
@@ -265,6 +340,9 @@ class spacrContainer(tk.Frame):
|
|
265
340
|
self.sashes.append(sash)
|
266
341
|
|
267
342
|
def reposition_panes(self):
|
343
|
+
"""
|
344
|
+
Reposition panes and sashes within the container based on orientation.
|
345
|
+
"""
|
268
346
|
if not self.panes:
|
269
347
|
return
|
270
348
|
|
@@ -286,22 +364,52 @@ class spacrContainer(tk.Frame):
|
|
286
364
|
sash.grid(row=0, column=(i * 2) + 1, sticky="ns")
|
287
365
|
|
288
366
|
def on_configure(self, event):
|
367
|
+
"""
|
368
|
+
Event handler triggered on container resize.
|
369
|
+
|
370
|
+
Args:
|
371
|
+
event (tk.Event): Tkinter event object.
|
372
|
+
"""
|
289
373
|
print(f"Configuring container: {self}")
|
290
374
|
self.reposition_panes()
|
291
375
|
|
292
376
|
def on_enter_sash(self, event):
|
377
|
+
"""
|
378
|
+
Change sash color on mouse enter.
|
379
|
+
|
380
|
+
Args:
|
381
|
+
event (tk.Event): Tkinter event object.
|
382
|
+
"""
|
293
383
|
event.widget.config(bg='blue')
|
294
384
|
|
295
385
|
def on_leave_sash(self, event):
|
386
|
+
"""
|
387
|
+
Reset sash color on mouse leave.
|
388
|
+
|
389
|
+
Args:
|
390
|
+
event (tk.Event): Tkinter event object.
|
391
|
+
"""
|
296
392
|
event.widget.config(bg=self.bg)
|
297
393
|
|
298
394
|
def start_resize(self, event):
|
395
|
+
"""
|
396
|
+
Initiate resizing behavior when mouse press begins on a sash.
|
397
|
+
|
398
|
+
Args:
|
399
|
+
event (tk.Event): Tkinter event object.
|
400
|
+
"""
|
299
401
|
sash = event.widget
|
300
402
|
self.start_pos = event.y_root if self.orient == tk.VERTICAL else event.x_root
|
301
403
|
self.start_size = sash.winfo_y() if self.orient == tk.VERTICAL else sash.winfo_x()
|
302
404
|
sash.bind("<B1-Motion>", self.perform_resize)
|
303
405
|
|
304
406
|
def perform_resize(self, event):
|
407
|
+
"""
|
408
|
+
Adjust pane sizes during mouse drag on a sash.
|
409
|
+
|
410
|
+
Args:
|
411
|
+
event (tk.Event): Tkinter event object.
|
412
|
+
"""
|
305
413
|
sash = event.widget
|
306
414
|
delta = (event.y_root - self.start_pos) if self.orient == tk.VERTICAL else (event.x_root - self.start_pos)
|
307
415
|
new_size = self.start_size + delta
|
@@ -325,7 +433,20 @@ class spacrContainer(tk.Frame):
|
|
325
433
|
self.reposition_panes()
|
326
434
|
|
327
435
|
class spacrEntry(tk.Frame):
|
436
|
+
"""
|
437
|
+
A custom Tkinter entry widget with rounded corners, dark theme styling, and active/inactive color handling.
|
438
|
+
|
439
|
+
Args:
|
440
|
+
parent (tk.Widget): Parent widget.
|
441
|
+
textvariable (tk.StringVar, optional): Tkinter textvariable to bind to the entry.
|
442
|
+
outline (bool, optional): Whether to show an outline. Currently unused. Defaults to False.
|
443
|
+
width (int, optional): Width of the entry widget. Defaults to 220 if not provided.
|
444
|
+
\*args, \*\*kwargs: Additional arguments passed to the parent Frame.
|
445
|
+
"""
|
328
446
|
def __init__(self, parent, textvariable=None, outline=False, width=None, *args, **kwargs):
|
447
|
+
"""
|
448
|
+
Initialize the custom entry widget with dark theme and rounded styling.
|
449
|
+
"""
|
329
450
|
super().__init__(parent, *args, **kwargs)
|
330
451
|
|
331
452
|
# Set dark style
|
@@ -364,6 +485,12 @@ class spacrEntry(tk.Frame):
|
|
364
485
|
self.draw_rounded_rectangle(self.bg_color)
|
365
486
|
|
366
487
|
def draw_rounded_rectangle(self, color):
|
488
|
+
"""
|
489
|
+
Draws a rounded rectangle with the given color as background.
|
490
|
+
|
491
|
+
Args:
|
492
|
+
color (str): Fill color for the rounded rectangle.
|
493
|
+
"""
|
367
494
|
radius = 15 # Increased radius for more rounded corners
|
368
495
|
x0, y0 = 10, 5
|
369
496
|
x1, y1 = self.canvas_width - 10, self.canvas_height - 5
|
@@ -376,15 +503,33 @@ class spacrEntry(tk.Frame):
|
|
376
503
|
self.canvas.create_rectangle((x0, y0 + radius / 2, x1, y1 - radius / 2), fill=color, outline=color)
|
377
504
|
|
378
505
|
def on_focus_in(self, event):
|
506
|
+
"""
|
507
|
+
Event handler for focus in. Changes the background to the active color.
|
508
|
+
"""
|
379
509
|
self.draw_rounded_rectangle(self.active_color)
|
380
510
|
self.entry.config(bg=self.active_color)
|
381
511
|
|
382
512
|
def on_focus_out(self, event):
|
513
|
+
"""
|
514
|
+
Event handler for focus out. Reverts the background to the inactive color.
|
515
|
+
"""
|
383
516
|
self.draw_rounded_rectangle(self.bg_color)
|
384
517
|
self.entry.config(bg=self.bg_color)
|
385
518
|
|
386
519
|
class spacrCheck(tk.Frame):
|
520
|
+
"""
|
521
|
+
A custom checkbox widget with rounded square appearance and dark style.
|
522
|
+
|
523
|
+
Args:
|
524
|
+
parent (tk.Widget): Parent widget.
|
525
|
+
text (str, optional): Label text (currently unused).
|
526
|
+
variable (tk.BooleanVar): Tkinter variable to bind the checkbox state.
|
527
|
+
\*args, \*\*kwargs: Additional arguments passed to the parent Frame.
|
528
|
+
"""
|
387
529
|
def __init__(self, parent, text="", variable=None, *args, **kwargs):
|
530
|
+
"""
|
531
|
+
Initializes the custom checkbox widget and binds visual updates to variable state.
|
532
|
+
"""
|
388
533
|
super().__init__(parent, *args, **kwargs)
|
389
534
|
|
390
535
|
style_out = set_dark_style(ttk.Style())
|
@@ -412,6 +557,12 @@ class spacrCheck(tk.Frame):
|
|
412
557
|
self.canvas.bind("<Button-1>", self.toggle_variable)
|
413
558
|
|
414
559
|
def draw_rounded_square(self, color):
|
560
|
+
"""
|
561
|
+
Draws a rounded square with border and fill.
|
562
|
+
|
563
|
+
Args:
|
564
|
+
color (str): The fill color based on the current checkbox state.
|
565
|
+
"""
|
415
566
|
radius = 5 # Adjust the radius for more rounded corners
|
416
567
|
x0, y0 = 2, 2
|
417
568
|
x1, y1 = 18, 18
|
@@ -428,13 +579,43 @@ class spacrCheck(tk.Frame):
|
|
428
579
|
self.canvas.create_line(x1, y0 + radius / 2, x1, y1 - radius / 2, fill=self.fg_color)
|
429
580
|
|
430
581
|
def update_check(self, *args):
|
582
|
+
"""
|
583
|
+
Redraws the checkbox based on the current value of the associated variable.
|
584
|
+
"""
|
431
585
|
self.draw_rounded_square(self.active_color if self.variable.get() else self.inactive_color)
|
432
586
|
|
433
587
|
def toggle_variable(self, event):
|
588
|
+
"""
|
589
|
+
Toggles the value of the associated variable when the checkbox is clicked.
|
590
|
+
|
591
|
+
Args:
|
592
|
+
event (tk.Event): The mouse click event.
|
593
|
+
"""
|
434
594
|
self.variable.set(not self.variable.get())
|
435
595
|
|
436
596
|
class spacrCombo(tk.Frame):
|
597
|
+
"""
|
598
|
+
A custom styled combo box widget with rounded edges and dropdown functionality.
|
599
|
+
|
600
|
+
This widget mimics a modern dropdown menu with dark-themed styling, allowing
|
601
|
+
users to select from a list of values in a visually appealing interface.
|
602
|
+
|
603
|
+
Args:
|
604
|
+
parent (tk.Widget): Parent widget.
|
605
|
+
textvariable (tk.StringVar, optional): Variable linked to the combo box selection.
|
606
|
+
values (list, optional): List of selectable values. Defaults to empty list.
|
607
|
+
width (int, optional): Width of the combo box in pixels. Defaults to 220.
|
608
|
+
"""
|
437
609
|
def __init__(self, parent, textvariable=None, values=None, width=None, *args, **kwargs):
|
610
|
+
"""
|
611
|
+
Initialize the combo box UI and style settings.
|
612
|
+
|
613
|
+
Args:
|
614
|
+
parent (tk.Widget): The parent widget.
|
615
|
+
textvariable (tk.StringVar, optional): A Tkinter StringVar linked to the selected value.
|
616
|
+
values (list, optional): List of values to populate the dropdown.
|
617
|
+
width (int, optional): Width of the combo box. Defaults to 220 pixels.
|
618
|
+
"""
|
438
619
|
super().__init__(parent, *args, **kwargs)
|
439
620
|
|
440
621
|
# Set dark style
|
@@ -474,6 +655,12 @@ class spacrCombo(tk.Frame):
|
|
474
655
|
self.dropdown_menu = None
|
475
656
|
|
476
657
|
def draw_rounded_rectangle(self, color):
|
658
|
+
"""
|
659
|
+
Draw a rounded rectangle on the canvas with the specified background color.
|
660
|
+
|
661
|
+
Args:
|
662
|
+
color (str): The fill color for the rounded rectangle.
|
663
|
+
"""
|
477
664
|
radius = 15 # Increased radius for more rounded corners
|
478
665
|
x0, y0 = 10, 5
|
479
666
|
x1, y1 = self.canvas_width - 10, self.canvas_height - 5
|
@@ -487,12 +674,21 @@ class spacrCombo(tk.Frame):
|
|
487
674
|
self.label.config(bg=color) # Update label background to match rectangle color
|
488
675
|
|
489
676
|
def on_click(self, event):
|
677
|
+
"""
|
678
|
+
Handle click event on the combo box to toggle the dropdown menu.
|
679
|
+
|
680
|
+
Args:
|
681
|
+
event (tk.Event): The mouse click event.
|
682
|
+
"""
|
490
683
|
if self.dropdown_menu is None:
|
491
684
|
self.open_dropdown()
|
492
685
|
else:
|
493
686
|
self.close_dropdown()
|
494
687
|
|
495
688
|
def open_dropdown(self):
|
689
|
+
"""
|
690
|
+
Display the dropdown menu with available values.
|
691
|
+
"""
|
496
692
|
self.draw_rounded_rectangle(self.active_color)
|
497
693
|
|
498
694
|
self.dropdown_menu = tk.Toplevel(self)
|
@@ -513,6 +709,9 @@ class spacrCombo(tk.Frame):
|
|
513
709
|
item.bind("<Leave>", lambda e, w=item: w.config(bg=self.inactive_color))
|
514
710
|
|
515
711
|
def close_dropdown(self):
|
712
|
+
"""
|
713
|
+
Close the dropdown menu if it is open.
|
714
|
+
"""
|
516
715
|
self.draw_rounded_rectangle(self.inactive_color)
|
517
716
|
|
518
717
|
if self.dropdown_menu:
|
@@ -520,6 +719,12 @@ class spacrCombo(tk.Frame):
|
|
520
719
|
self.dropdown_menu = None
|
521
720
|
|
522
721
|
def on_select(self, value):
|
722
|
+
"""
|
723
|
+
Update the displayed label and internal variable when a value is selected.
|
724
|
+
|
725
|
+
Args:
|
726
|
+
value (str): The selected value from the dropdown.
|
727
|
+
"""
|
523
728
|
display_text = value if value is not None else 'None'
|
524
729
|
self.var.set(value)
|
525
730
|
self.label.config(text=display_text)
|
@@ -527,14 +732,50 @@ class spacrCombo(tk.Frame):
|
|
527
732
|
self.close_dropdown()
|
528
733
|
|
529
734
|
def set(self, value):
|
735
|
+
"""
|
736
|
+
Programmatically set the combo box selection to the specified value.
|
737
|
+
|
738
|
+
Args:
|
739
|
+
value (str): The value to set in the combo box.
|
740
|
+
"""
|
530
741
|
display_text = value if value is not None else 'None'
|
531
742
|
self.var.set(value)
|
532
743
|
self.label.config(text=display_text)
|
533
744
|
self.selected_value = value
|
534
745
|
|
535
746
|
class spacrDropdownMenu(tk.Frame):
|
536
|
-
|
747
|
+
"""
|
748
|
+
A custom dark-themed dropdown menu widget with rounded edges and hover interaction.
|
749
|
+
|
750
|
+
This widget displays a labeled button that reveals a menu of selectable options
|
751
|
+
when clicked. It supports external callback functions, styling updates, and dynamic
|
752
|
+
highlighting of active menu items.
|
753
|
+
|
754
|
+
Args:
|
755
|
+
parent (tk.Widget): Parent widget in which the dropdown menu is placed.
|
756
|
+
variable (tk.StringVar): A Tkinter variable to store the selected option.
|
757
|
+
options (list): A list of option labels to populate the dropdown menu.
|
758
|
+
command (callable, optional): A function to call when an option is selected.
|
759
|
+
font (tuple or tk.Font, optional): Font used for the button label.
|
760
|
+
size (int, optional): Height of the button in pixels. Defaults to 50.
|
761
|
+
**kwargs: Additional keyword arguments passed to the `tk.Frame` base class.
|
762
|
+
"""
|
537
763
|
def __init__(self, parent, variable, options, command=None, font=None, size=50, **kwargs):
|
764
|
+
"""
|
765
|
+
Initialize the spacrDropdownMenu with a canvas-based button and popup menu.
|
766
|
+
|
767
|
+
Sets up the button appearance, binds mouse interaction events,
|
768
|
+
and constructs the dropdown menu with the given options.
|
769
|
+
|
770
|
+
Args:
|
771
|
+
parent (tk.Widget): Parent container.
|
772
|
+
variable (tk.StringVar): Variable that stores the selected option.
|
773
|
+
options (list): List of strings representing the dropdown options.
|
774
|
+
command (callable, optional): Callback function when an option is selected.
|
775
|
+
font (tk.Font or tuple, optional): Font used for the button text.
|
776
|
+
size (int, optional): Button height in pixels. Defaults to 50.
|
777
|
+
**kwargs: Additional keyword arguments for the Frame.
|
778
|
+
"""
|
538
779
|
super().__init__(parent, **kwargs)
|
539
780
|
self.variable = variable
|
540
781
|
self.options = options
|
@@ -588,6 +829,20 @@ class spacrDropdownMenu(tk.Frame):
|
|
588
829
|
self.menu.add_command(label=option, command=lambda opt=option: self.on_select(opt))
|
589
830
|
|
590
831
|
def create_rounded_rectangle(self, x1, y1, x2, y2, radius=20, **kwargs):
|
832
|
+
"""
|
833
|
+
Draw a rounded rectangle polygon on the internal canvas.
|
834
|
+
|
835
|
+
Args:
|
836
|
+
x1 (int): Top-left x coordinate.
|
837
|
+
y1 (int): Top-left y coordinate.
|
838
|
+
x2 (int): Bottom-right x coordinate.
|
839
|
+
y2 (int): Bottom-right y coordinate.
|
840
|
+
radius (int, optional): Radius of the corners. Defaults to 20.
|
841
|
+
**kwargs: Canvas polygon configuration options (fill, outline, etc.).
|
842
|
+
|
843
|
+
Returns:
|
844
|
+
int: Canvas item ID of the created polygon.
|
845
|
+
"""
|
591
846
|
points = [
|
592
847
|
x1 + radius, y1,
|
593
848
|
x2 - radius, y1,
|
@@ -610,23 +865,57 @@ class spacrDropdownMenu(tk.Frame):
|
|
610
865
|
return self.canvas.create_polygon(points, **kwargs, smooth=True)
|
611
866
|
|
612
867
|
def on_enter(self, event=None):
|
868
|
+
"""
|
869
|
+
Handle mouse enter event by updating the button's background color.
|
870
|
+
|
871
|
+
Args:
|
872
|
+
event (tk.Event, optional): The event object. Defaults to None.
|
873
|
+
"""
|
613
874
|
self.canvas.itemconfig(self.button_bg, fill=self.active_color)
|
614
875
|
|
615
876
|
def on_leave(self, event=None):
|
877
|
+
"""
|
878
|
+
Handle mouse leave event by resetting the button's background color.
|
879
|
+
|
880
|
+
Args:
|
881
|
+
event (tk.Event, optional): The event object. Defaults to None.
|
882
|
+
"""
|
616
883
|
self.canvas.itemconfig(self.button_bg, fill=self.inactive_color)
|
617
884
|
|
618
885
|
def on_click(self, event=None):
|
886
|
+
"""
|
887
|
+
Handle button click event to display the dropdown menu.
|
888
|
+
|
889
|
+
Args:
|
890
|
+
event (tk.Event, optional): The event object. Defaults to None.
|
891
|
+
"""
|
619
892
|
self.post_menu()
|
620
893
|
|
621
894
|
def post_menu(self):
|
895
|
+
"""
|
896
|
+
Display the dropdown menu below the button using screen coordinates.
|
897
|
+
"""
|
622
898
|
x, y, width, height = self.winfo_rootx(), self.winfo_rooty(), self.winfo_width(), self.winfo_height()
|
623
899
|
self.menu.post(x, y + height)
|
624
900
|
|
625
901
|
def on_select(self, option):
|
902
|
+
"""
|
903
|
+
Callback when an option is selected from the dropdown menu.
|
904
|
+
|
905
|
+
Args:
|
906
|
+
option (str): The selected option label.
|
907
|
+
"""
|
626
908
|
if self.command:
|
627
909
|
self.command(option)
|
628
910
|
|
629
911
|
def update_styles(self, active_categories=None):
|
912
|
+
"""
|
913
|
+
Update the styles of the dropdown menu entries based on active categories.
|
914
|
+
|
915
|
+
Args:
|
916
|
+
active_categories (list or None): List of option labels to highlight
|
917
|
+
with the active color. If None, all entries are styled as inactive.
|
918
|
+
"""
|
630
919
|
style_out = set_dark_style(ttk.Style(), widgets=[self.menu])
|
631
920
|
|
632
921
|
if active_categories is not None:
|
@@ -638,6 +927,21 @@ class spacrDropdownMenu(tk.Frame):
|
|
638
927
|
self.menu.entryconfig(idx, background=style_out['bg_color'], foreground=style_out['fg_color'])
|
639
928
|
|
640
929
|
class spacrCheckbutton(ttk.Checkbutton):
|
930
|
+
"""
|
931
|
+
A dark-themed styled Checkbutton widget for use in the SpaCr GUI.
|
932
|
+
|
933
|
+
This class wraps a `ttk.Checkbutton` with a custom style and binds it to a
|
934
|
+
`BooleanVar`, allowing it to integrate seamlessly into dark-themed interfaces.
|
935
|
+
|
936
|
+
Args:
|
937
|
+
parent (tk.Widget): The parent widget in which to place the checkbutton.
|
938
|
+
text (str, optional): The label text displayed next to the checkbutton.
|
939
|
+
variable (tk.BooleanVar, optional): Variable linked to the checkbutton's state.
|
940
|
+
If None, a new `BooleanVar` is created.
|
941
|
+
command (callable, optional): A callback function to execute when the checkbutton is toggled.
|
942
|
+
*args: Additional positional arguments passed to `ttk.Checkbutton`.
|
943
|
+
**kwargs: Additional keyword arguments passed to `ttk.Checkbutton`.
|
944
|
+
"""
|
641
945
|
def __init__(self, parent, text="", variable=None, command=None, *args, **kwargs):
|
642
946
|
super().__init__(parent, *args, **kwargs)
|
643
947
|
self.text = text
|
@@ -648,7 +952,30 @@ class spacrCheckbutton(ttk.Checkbutton):
|
|
648
952
|
_ = set_dark_style(style, widgets=[self])
|
649
953
|
|
650
954
|
class spacrProgressBar(ttk.Progressbar):
|
955
|
+
"""
|
956
|
+
A dark-themed progress bar widget with optional progress label display.
|
957
|
+
|
958
|
+
This class extends `ttk.Progressbar` and applies a dark visual theme consistent
|
959
|
+
with SpaCr GUI styling. It also provides an optional label that displays real-time
|
960
|
+
progress, operation type, and additional information.
|
961
|
+
|
962
|
+
Args:
|
963
|
+
parent (tk.Widget): The parent widget in which to place the progress bar.
|
964
|
+
label (bool, optional): Whether to show a label below the progress bar. Defaults to True.
|
965
|
+
*args: Additional positional arguments passed to `ttk.Progressbar`.
|
966
|
+
**kwargs: Additional keyword arguments passed to `ttk.Progressbar`.
|
967
|
+
"""
|
651
968
|
def __init__(self, parent, label=True, *args, **kwargs):
|
969
|
+
"""
|
970
|
+
Initialize the progress bar and optional label with dark theme styling.
|
971
|
+
|
972
|
+
Sets the initial value to 0, applies custom style attributes, and creates
|
973
|
+
a label for displaying progress information.
|
974
|
+
|
975
|
+
Args:
|
976
|
+
parent (tk.Widget): Parent container for the widget.
|
977
|
+
label (bool, optional): Whether to show a label for progress updates. Defaults to True.
|
978
|
+
"""
|
652
979
|
super().__init__(parent, *args, **kwargs)
|
653
980
|
|
654
981
|
# Get the style colors
|
@@ -705,6 +1032,11 @@ class spacrProgressBar(ttk.Progressbar):
|
|
705
1032
|
self.additional_info = None
|
706
1033
|
|
707
1034
|
def set_label_position(self):
|
1035
|
+
"""
|
1036
|
+
Place the progress label one row below the progress bar in the grid layout.
|
1037
|
+
|
1038
|
+
Should be called after the progress bar has been placed with `.grid(...)`.
|
1039
|
+
"""
|
708
1040
|
if self.label and self.progress_label:
|
709
1041
|
row_info = self.grid_info().get('rowID', 0)
|
710
1042
|
col_info = self.grid_info().get('columnID', 0)
|
@@ -712,6 +1044,14 @@ class spacrProgressBar(ttk.Progressbar):
|
|
712
1044
|
self.progress_label.grid(row=row_info + 1, column=col_info, columnspan=col_span, pady=5, padx=5, sticky='ew')
|
713
1045
|
|
714
1046
|
def update_label(self):
|
1047
|
+
"""
|
1048
|
+
Update the progress label with current progress, operation type, and extra info.
|
1049
|
+
|
1050
|
+
Constructs a single-line status message with:
|
1051
|
+
- Current progress value
|
1052
|
+
- Operation type (if set)
|
1053
|
+
- Additional info (if set)
|
1054
|
+
"""
|
715
1055
|
if self.label and self.progress_label:
|
716
1056
|
# Start with the base progress information
|
717
1057
|
label_text = f"Processing: {self['value']}/{self['maximum']}"
|
@@ -733,7 +1073,45 @@ class spacrProgressBar(ttk.Progressbar):
|
|
733
1073
|
self.progress_label.config(text=label_text)
|
734
1074
|
|
735
1075
|
class spacrSlider(tk.Frame):
|
1076
|
+
"""
|
1077
|
+
A custom slider widget with dark-themed styling, optional numeric entry, and mouse interaction.
|
1078
|
+
|
1079
|
+
This slider is designed for GUI applications where numeric control is needed,
|
1080
|
+
supporting dynamic resizing, labeled value entry, and a callback on release.
|
1081
|
+
|
1082
|
+
Args:
|
1083
|
+
master (tk.Widget): Parent widget.
|
1084
|
+
length (int, optional): Fixed pixel length of the slider. If None, adapts to canvas width.
|
1085
|
+
thickness (int, optional): Thickness of the slider bar in pixels. Defaults to 2.
|
1086
|
+
knob_radius (int, optional): Radius of the slider knob in pixels. Defaults to 10.
|
1087
|
+
position (str, optional): Alignment of slider within the frame. One of "left", "center", "right".
|
1088
|
+
from_ (float): Minimum slider value.
|
1089
|
+
to (float): Maximum slider value.
|
1090
|
+
value (float, optional): Initial value. Defaults to `from_`.
|
1091
|
+
show_index (bool, optional): Whether to show an entry for numeric value. Defaults to False.
|
1092
|
+
command (Callable, optional): Function to call with the final value upon knob release.
|
1093
|
+
**kwargs: Additional options passed to `tk.Frame`.
|
1094
|
+
"""
|
736
1095
|
def __init__(self, master=None, length=None, thickness=2, knob_radius=10, position="center", from_=0, to=100, value=None, show_index=False, command=None, **kwargs):
|
1096
|
+
"""
|
1097
|
+
Initialize a custom dark-themed slider widget.
|
1098
|
+
|
1099
|
+
This slider supports mouse interaction, optional direct numeric input via an Entry,
|
1100
|
+
and dynamically adapts its layout based on container resizing unless a fixed `length` is specified.
|
1101
|
+
|
1102
|
+
Args:
|
1103
|
+
master (tk.Widget, optional): Parent widget.
|
1104
|
+
length (int, optional): Fixed length of the slider in pixels. If None, dynamically resizes with the container.
|
1105
|
+
thickness (int, optional): Thickness of the slider bar in pixels. Default is 2.
|
1106
|
+
knob_radius (int, optional): Radius of the draggable knob in pixels. Default is 10.
|
1107
|
+
position (str, optional): Alignment of the slider within the frame. One of {"left", "center", "right"}. Default is "center".
|
1108
|
+
from_ (float): Minimum value of the slider.
|
1109
|
+
to (float): Maximum value of the slider.
|
1110
|
+
value (float, optional): Initial value of the slider. Defaults to `from_` if not specified.
|
1111
|
+
show_index (bool, optional): If True, displays a text entry box to manually input the slider value. Default is False.
|
1112
|
+
command (Callable, optional): Optional function to be called with the final value when the knob is released.
|
1113
|
+
**kwargs: Additional keyword arguments passed to the `tk.Frame` initializer.
|
1114
|
+
"""
|
737
1115
|
super().__init__(master, **kwargs)
|
738
1116
|
|
739
1117
|
self.specified_length = length # Store the specified length, if any
|
@@ -793,6 +1171,12 @@ class spacrSlider(tk.Frame):
|
|
793
1171
|
self.canvas.bind("<ButtonRelease-1>", self.release_knob) # Trigger command on release
|
794
1172
|
|
795
1173
|
def resize_slider(self, event):
|
1174
|
+
"""
|
1175
|
+
Recalculate slider dimensions and redraw upon resizing.
|
1176
|
+
|
1177
|
+
Args:
|
1178
|
+
event (tk.Event): Resize event.
|
1179
|
+
"""
|
796
1180
|
if self.specified_length is not None:
|
797
1181
|
self.length = self.specified_length
|
798
1182
|
else:
|
@@ -811,18 +1195,42 @@ class spacrSlider(tk.Frame):
|
|
811
1195
|
self.draw_slider(inactive=True)
|
812
1196
|
|
813
1197
|
def value_to_position(self, value):
|
1198
|
+
"""
|
1199
|
+
Convert a numerical slider value to a pixel position on the canvas.
|
1200
|
+
|
1201
|
+
Args:
|
1202
|
+
value (float): The numerical value to convert.
|
1203
|
+
|
1204
|
+
Returns:
|
1205
|
+
float: Corresponding position in pixels on the slider.
|
1206
|
+
"""
|
814
1207
|
if self.to == self.from_:
|
815
1208
|
return self.knob_radius
|
816
1209
|
relative_value = (value - self.from_) / (self.to - self.from_)
|
817
1210
|
return self.knob_radius + relative_value * (self.length - 2 * self.knob_radius)
|
818
1211
|
|
819
1212
|
def position_to_value(self, position):
|
1213
|
+
"""
|
1214
|
+
Convert a pixel position on the slider to a numerical value.
|
1215
|
+
|
1216
|
+
Args:
|
1217
|
+
position (float): Pixel position on the slider.
|
1218
|
+
|
1219
|
+
Returns:
|
1220
|
+
float: Corresponding numerical slider value.
|
1221
|
+
"""
|
820
1222
|
if self.to == self.from_:
|
821
1223
|
return self.from_
|
822
1224
|
relative_position = (position - self.knob_radius) / (self.length - 2 * self.knob_radius)
|
823
1225
|
return self.from_ + relative_position * (self.to - self.from_)
|
824
1226
|
|
825
1227
|
def draw_slider(self, inactive=False):
|
1228
|
+
"""
|
1229
|
+
Draw the slider bar and knob on the canvas.
|
1230
|
+
|
1231
|
+
Args:
|
1232
|
+
inactive (bool): If True, draw knob in inactive color. Otherwise, use active color.
|
1233
|
+
"""
|
826
1234
|
self.canvas.delete("all")
|
827
1235
|
|
828
1236
|
self.slider_line = self.canvas.create_line(
|
@@ -845,6 +1253,12 @@ class spacrSlider(tk.Frame):
|
|
845
1253
|
)
|
846
1254
|
|
847
1255
|
def move_knob(self, event):
|
1256
|
+
"""
|
1257
|
+
Move the knob in response to mouse drag, updating internal value and position.
|
1258
|
+
|
1259
|
+
Args:
|
1260
|
+
event (tk.Event): Motion event.
|
1261
|
+
"""
|
848
1262
|
new_position = min(max(event.x - self.offset, self.knob_radius), self.length - self.knob_radius)
|
849
1263
|
self.knob_position = new_position
|
850
1264
|
self.value = self.position_to_value(self.knob_position)
|
@@ -859,24 +1273,53 @@ class spacrSlider(tk.Frame):
|
|
859
1273
|
self.index_var.set(str(int(self.value)))
|
860
1274
|
|
861
1275
|
def activate_knob(self, event):
|
1276
|
+
"""
|
1277
|
+
Highlight knob and respond to click by positioning knob at mouse location.
|
1278
|
+
|
1279
|
+
Args:
|
1280
|
+
event (tk.Event): Click event.
|
1281
|
+
"""
|
862
1282
|
self.draw_slider(inactive=False)
|
863
1283
|
self.move_knob(event)
|
864
1284
|
|
865
1285
|
def release_knob(self, event):
|
1286
|
+
"""
|
1287
|
+
Finalize knob movement and call the `command` callback with final value.
|
1288
|
+
|
1289
|
+
Args:
|
1290
|
+
event (tk.Event): Mouse release event.
|
1291
|
+
"""
|
866
1292
|
self.draw_slider(inactive=True)
|
867
1293
|
if self.command:
|
868
1294
|
self.command(self.value) # Call the command with the final value when the knob is released
|
869
1295
|
|
870
1296
|
def set_to(self, new_to):
|
1297
|
+
"""
|
1298
|
+
Change the maximum value (`to`) of the slider.
|
1299
|
+
|
1300
|
+
Args:
|
1301
|
+
new_to (float): New upper bound of the slider.
|
1302
|
+
"""
|
871
1303
|
self.to = new_to
|
872
1304
|
self.knob_position = self.value_to_position(self.value)
|
873
1305
|
self.draw_slider(inactive=False)
|
874
1306
|
|
875
1307
|
def get(self):
|
1308
|
+
"""
|
1309
|
+
Get the current slider value.
|
1310
|
+
|
1311
|
+
Returns:
|
1312
|
+
float: Current value of the slider.
|
1313
|
+
"""
|
876
1314
|
return self.value
|
877
1315
|
|
878
1316
|
def set(self, value):
|
879
|
-
"""
|
1317
|
+
"""
|
1318
|
+
Set the slider to a specific value and redraw.
|
1319
|
+
|
1320
|
+
Args:
|
1321
|
+
value (float): New value to set (clipped to bounds).
|
1322
|
+
"""
|
880
1323
|
self.value = max(self.from_, min(value, self.to)) # Ensure the value is within bounds
|
881
1324
|
self.knob_position = self.value_to_position(self.value)
|
882
1325
|
self.draw_slider(inactive=False)
|
@@ -884,10 +1327,21 @@ class spacrSlider(tk.Frame):
|
|
884
1327
|
self.index_var.set(str(int(self.value)))
|
885
1328
|
|
886
1329
|
def jump_to_click(self, event):
|
1330
|
+
"""
|
1331
|
+
Move the knob directly to the mouse click position.
|
1332
|
+
|
1333
|
+
Args:
|
1334
|
+
event (tk.Event): Click event.
|
1335
|
+
"""
|
887
1336
|
self.activate_knob(event)
|
888
1337
|
|
889
1338
|
def update_slider_from_entry(self, event):
|
890
|
-
"""
|
1339
|
+
"""
|
1340
|
+
Update the slider from a value entered manually in the index entry.
|
1341
|
+
|
1342
|
+
Args:
|
1343
|
+
event (tk.Event): Return key press event.
|
1344
|
+
"""
|
891
1345
|
try:
|
892
1346
|
index = int(self.index_var.get())
|
893
1347
|
self.set(index)
|
@@ -897,6 +1351,19 @@ class spacrSlider(tk.Frame):
|
|
897
1351
|
pass
|
898
1352
|
|
899
1353
|
def spacrScrollbarStyle(style, inactive_color, active_color):
|
1354
|
+
"""
|
1355
|
+
Applies a custom vertical scrollbar style using the given colors.
|
1356
|
+
|
1357
|
+
This function defines a new ttk scrollbar style named 'Custom.Vertical.TScrollbar'.
|
1358
|
+
It reuses the base elements from the 'clam' theme and sets the colors for active
|
1359
|
+
and inactive states accordingly. If the required elements do not exist, it creates
|
1360
|
+
them from the base theme.
|
1361
|
+
|
1362
|
+
Args:
|
1363
|
+
style (ttk.Style): The ttk Style object to configure.
|
1364
|
+
inactive_color (str): Hex or color name for the scrollbar in its default (inactive) state.
|
1365
|
+
active_color (str): Hex or color name for the scrollbar when hovered or active.
|
1366
|
+
"""
|
900
1367
|
# Check if custom elements already exist to avoid duplication
|
901
1368
|
if not style.element_names().count('custom.Vertical.Scrollbar.trough'):
|
902
1369
|
style.element_create('custom.Vertical.Scrollbar.trough', 'from', 'clam')
|
@@ -921,7 +1388,28 @@ def spacrScrollbarStyle(style, inactive_color, active_color):
|
|
921
1388
|
darkcolor=[('!active', inactive_color), ('active', active_color)])
|
922
1389
|
|
923
1390
|
class spacrFrame(ttk.Frame):
|
1391
|
+
"""
|
1392
|
+
A styled frame with optional rounded background, vertical scrollbar, and embedded content area (text or widgets).
|
1393
|
+
|
1394
|
+
This frame supports both scrollable `ttk.Frame` containers and scrollable `tk.Text` areas, with a dark custom theme.
|
1395
|
+
|
1396
|
+
Attributes:
|
1397
|
+
scrollable_frame (Union[ttk.Frame, tk.Text]): The inner content widget.
|
1398
|
+
"""
|
924
1399
|
def __init__(self, container, width=None, *args, bg='black', radius=20, scrollbar=True, textbox=False, **kwargs):
|
1400
|
+
"""
|
1401
|
+
Initialize the spacrFrame.
|
1402
|
+
|
1403
|
+
Args:
|
1404
|
+
container (tk.Widget): The parent container for this frame.
|
1405
|
+
width (int, optional): Width of the frame in pixels. Defaults to 1/4 of screen width if None.
|
1406
|
+
*args: Additional positional arguments for ttk.Frame.
|
1407
|
+
bg (str): Background color of the frame. Defaults to 'black'.
|
1408
|
+
radius (int): Radius of the rounded rectangle background. Defaults to 20.
|
1409
|
+
scrollbar (bool): Whether to include a vertical scrollbar. Defaults to True.
|
1410
|
+
textbox (bool): If True, use a scrollable `tk.Text` widget. Otherwise, use a `ttk.Frame`. Defaults to False.
|
1411
|
+
**kwargs: Additional keyword arguments for ttk.Frame.
|
1412
|
+
"""
|
925
1413
|
super().__init__(container, *args, **kwargs)
|
926
1414
|
self.configure(style='TFrame')
|
927
1415
|
if width is None:
|
@@ -973,6 +1461,21 @@ class spacrFrame(ttk.Frame):
|
|
973
1461
|
_ = set_dark_style(style, widgets=[scrollbar_widget])
|
974
1462
|
|
975
1463
|
def rounded_rectangle(self, canvas, x1, y1, x2, y2, radius=20, **kwargs):
|
1464
|
+
"""
|
1465
|
+
Draw a rounded rectangle on a canvas.
|
1466
|
+
|
1467
|
+
Args:
|
1468
|
+
canvas (tk.Canvas): The canvas to draw on.
|
1469
|
+
x1 (int): Left coordinate.
|
1470
|
+
y1 (int): Top coordinate.
|
1471
|
+
x2 (int): Right coordinate.
|
1472
|
+
y2 (int): Bottom coordinate.
|
1473
|
+
radius (int): Radius of the rounded corners. Defaults to 20.
|
1474
|
+
**kwargs: Options passed to the canvas `create_polygon` method.
|
1475
|
+
|
1476
|
+
Returns:
|
1477
|
+
int: ID of the created polygon.
|
1478
|
+
"""
|
976
1479
|
points = [
|
977
1480
|
x1 + radius, y1,
|
978
1481
|
x2 - radius, y1,
|
@@ -995,7 +1498,24 @@ class spacrFrame(ttk.Frame):
|
|
995
1498
|
return canvas.create_polygon(points, **kwargs, smooth=True)
|
996
1499
|
|
997
1500
|
class spacrLabel(tk.Frame):
|
1501
|
+
"""
|
1502
|
+
A custom label widget with optional dark styling, alignment options, and support for both `ttk.Label` and `Canvas`-rendered text.
|
1503
|
+
|
1504
|
+
The label adapts to screen size or a given height, and can display text either centered or right-aligned.
|
1505
|
+
"""
|
998
1506
|
def __init__(self, parent, text="", font=None, style=None, align="right", height=None, **kwargs):
|
1507
|
+
"""
|
1508
|
+
Initialize the spacrLabel widget.
|
1509
|
+
|
1510
|
+
Args:
|
1511
|
+
parent (tk.Widget): The parent widget.
|
1512
|
+
text (str): The text to display on the label. Defaults to "".
|
1513
|
+
font (tkFont.Font, optional): A custom font to use if not using the default style. Defaults to None.
|
1514
|
+
style (str, optional): A ttk style name to apply to the label. If set, uses a `ttk.Label` instead of `Canvas` text.
|
1515
|
+
align (str): Text alignment, either "right" or "center". Defaults to "right".
|
1516
|
+
height (int, optional): Height of the label. If None, scales based on screen height.
|
1517
|
+
**kwargs: Additional keyword arguments for the outer frame (excluding font/background/anchor-specific ones).
|
1518
|
+
"""
|
999
1519
|
valid_kwargs = {k: v for k, v in kwargs.items() if k not in ['foreground', 'background', 'font', 'anchor', 'justify', 'wraplength']}
|
1000
1520
|
super().__init__(parent, **valid_kwargs)
|
1001
1521
|
|
@@ -1050,14 +1570,43 @@ class spacrLabel(tk.Frame):
|
|
1050
1570
|
_ = set_dark_style(ttk.Style(), containers=[self], widgets=[self.canvas])
|
1051
1571
|
|
1052
1572
|
def set_text(self, text):
|
1573
|
+
"""
|
1574
|
+
Update the label text.
|
1575
|
+
|
1576
|
+
Args:
|
1577
|
+
text (str): The new text to display.
|
1578
|
+
"""
|
1053
1579
|
if self.style:
|
1054
1580
|
self.label_text.config(text=text)
|
1055
1581
|
else:
|
1056
1582
|
self.canvas.itemconfig(self.label_text, text=text)
|
1057
1583
|
|
1058
1584
|
class spacrButton(tk.Frame):
|
1585
|
+
"""
|
1586
|
+
A custom animated button widget with icon and optional text, styled with dark mode and zoom animation on hover.
|
1587
|
+
|
1588
|
+
The button is rendered using a Canvas to support rounded corners and icon embedding. Optional description
|
1589
|
+
display is supported via parent methods `show_description` and `clear_description`.
|
1590
|
+
"""
|
1059
1591
|
def __init__(self, parent, text="", command=None, font=None, icon_name=None, size=50, show_text=True, outline=False, animation=True, *args, **kwargs):
|
1592
|
+
"""
|
1593
|
+
Initialize the spacrButton.
|
1594
|
+
|
1595
|
+
Args:
|
1596
|
+
parent (tk.Widget): The parent container.
|
1597
|
+
text (str): Button text to display.
|
1598
|
+
command (callable, optional): Function to call when button is clicked.
|
1599
|
+
font (tkFont.Font or tuple, optional): Font to use if font loader is unavailable.
|
1600
|
+
icon_name (str, optional): Name of icon (without extension) to load from resources/icons.
|
1601
|
+
size (int): Button height (and icon size reference). Defaults to 50.
|
1602
|
+
show_text (bool): Whether to show text next to the icon. Defaults to True.
|
1603
|
+
outline (bool): Whether to draw a border around the button. Defaults to False.
|
1604
|
+
animation (bool): Whether to animate the icon on hover. Defaults to True.
|
1605
|
+
*args: Additional positional arguments for the Frame.
|
1606
|
+
**kwargs: Additional keyword arguments for the Frame.
|
1607
|
+
"""
|
1060
1608
|
super().__init__(parent, *args, **kwargs)
|
1609
|
+
|
1061
1610
|
|
1062
1611
|
self.text = text.capitalize() # Capitalize only the first letter of the text
|
1063
1612
|
self.command = command
|
@@ -1112,6 +1661,11 @@ class spacrButton(tk.Frame):
|
|
1112
1661
|
self.is_zoomed_in = False # Track zoom state for smooth transitions
|
1113
1662
|
|
1114
1663
|
def load_icon(self):
|
1664
|
+
"""
|
1665
|
+
Load and resize the icon from the icon folder.
|
1666
|
+
|
1667
|
+
Falls back to 'default.png' if the specified icon cannot be found.
|
1668
|
+
"""
|
1115
1669
|
icon_path = self.get_icon_path(self.icon_name)
|
1116
1670
|
try:
|
1117
1671
|
icon_image = Image.open(icon_path)
|
@@ -1131,26 +1685,59 @@ class spacrButton(tk.Frame):
|
|
1131
1685
|
self.canvas.image = self.icon_photo # Keep a reference to avoid garbage collection
|
1132
1686
|
|
1133
1687
|
def get_icon_path(self, icon_name):
|
1688
|
+
"""
|
1689
|
+
Get the full path to the icon file.
|
1690
|
+
|
1691
|
+
Args:
|
1692
|
+
icon_name (str): Icon name without extension.
|
1693
|
+
|
1694
|
+
Returns:
|
1695
|
+
str: Full path to the icon file.
|
1696
|
+
"""
|
1134
1697
|
icon_dir = os.path.join(os.path.dirname(__file__), 'resources', 'icons')
|
1135
1698
|
return os.path.join(icon_dir, f"{icon_name}.png")
|
1136
1699
|
|
1137
1700
|
def on_enter(self, event=None):
|
1701
|
+
"""
|
1702
|
+
Handle mouse hover enter event.
|
1703
|
+
|
1704
|
+
Changes button color, shows description, and animates zoom-in if enabled.
|
1705
|
+
"""
|
1138
1706
|
self.canvas.itemconfig(self.button_bg, fill=self.active_color)
|
1139
1707
|
self.update_description(event)
|
1140
1708
|
if self.animation and not self.is_zoomed_in:
|
1141
1709
|
self.animate_zoom(0.85) # Zoom in the icon to 85% of button size
|
1142
1710
|
|
1143
1711
|
def on_leave(self, event=None):
|
1712
|
+
"""
|
1713
|
+
Handle mouse hover leave event.
|
1714
|
+
|
1715
|
+
Resets button color, clears description, and animates zoom-out if enabled.
|
1716
|
+
"""
|
1144
1717
|
self.canvas.itemconfig(self.button_bg, fill=self.inactive_color)
|
1145
1718
|
self.clear_description(event)
|
1146
1719
|
if self.animation and self.is_zoomed_in:
|
1147
1720
|
self.animate_zoom(0.65) # Reset the icon size to 65% of button size
|
1148
1721
|
|
1149
1722
|
def on_click(self, event=None):
|
1723
|
+
"""
|
1724
|
+
Trigger the button's command callback when clicked.
|
1725
|
+
"""
|
1150
1726
|
if self.command:
|
1151
1727
|
self.command()
|
1152
1728
|
|
1153
1729
|
def create_rounded_rectangle(self, x1, y1, x2, y2, radius=20, **kwargs):
|
1730
|
+
"""
|
1731
|
+
Create a rounded rectangle on the canvas.
|
1732
|
+
|
1733
|
+
Args:
|
1734
|
+
x1, y1, x2, y2 (int): Coordinates of the rectangle.
|
1735
|
+
radius (int): Radius of the corners.
|
1736
|
+
**kwargs: Passed to `create_polygon`.
|
1737
|
+
|
1738
|
+
Returns:
|
1739
|
+
int: Canvas item ID of the rounded rectangle.
|
1740
|
+
"""
|
1154
1741
|
points = [
|
1155
1742
|
x1 + radius, y1,
|
1156
1743
|
x2 - radius, y1,
|
@@ -1173,6 +1760,10 @@ class spacrButton(tk.Frame):
|
|
1173
1760
|
return self.canvas.create_polygon(points, **kwargs, smooth=True)
|
1174
1761
|
|
1175
1762
|
def update_description(self, event):
|
1763
|
+
"""
|
1764
|
+
Call parent container’s `show_description()` if available,
|
1765
|
+
passing the description based on `main_buttons` or `additional_buttons` maps.
|
1766
|
+
"""
|
1176
1767
|
parent = self.master
|
1177
1768
|
while parent:
|
1178
1769
|
if hasattr(parent, 'show_description'):
|
@@ -1181,6 +1772,9 @@ class spacrButton(tk.Frame):
|
|
1181
1772
|
parent = parent.master
|
1182
1773
|
|
1183
1774
|
def clear_description(self, event):
|
1775
|
+
"""
|
1776
|
+
Call parent container’s `clear_description()` if available.
|
1777
|
+
"""
|
1184
1778
|
parent = self.master
|
1185
1779
|
while parent:
|
1186
1780
|
if hasattr(parent, 'clear_description'):
|
@@ -1189,11 +1783,28 @@ class spacrButton(tk.Frame):
|
|
1189
1783
|
parent = parent.master
|
1190
1784
|
|
1191
1785
|
def animate_zoom(self, target_scale, steps=10, delay=10):
|
1786
|
+
"""
|
1787
|
+
Animate zoom effect by resizing icon incrementally.
|
1788
|
+
|
1789
|
+
Args:
|
1790
|
+
target_scale (float): Final scale factor relative to base icon size.
|
1791
|
+
steps (int): Number of animation steps. Defaults to 10.
|
1792
|
+
delay (int): Delay between steps in milliseconds. Defaults to 10.
|
1793
|
+
"""
|
1192
1794
|
current_scale = 0.85 if self.is_zoomed_in else 0.65
|
1193
1795
|
step_scale = (target_scale - current_scale) / steps
|
1194
1796
|
self._animate_step(current_scale, step_scale, steps, delay)
|
1195
1797
|
|
1196
1798
|
def _animate_step(self, current_scale, step_scale, steps, delay):
|
1799
|
+
"""
|
1800
|
+
Helper method to perform recursive icon zoom animation.
|
1801
|
+
|
1802
|
+
Args:
|
1803
|
+
current_scale (float): Current zoom scale.
|
1804
|
+
step_scale (float): Incremental change per step.
|
1805
|
+
steps (int): Steps remaining.
|
1806
|
+
delay (int): Delay per step in ms.
|
1807
|
+
"""
|
1197
1808
|
if steps > 0:
|
1198
1809
|
new_scale = current_scale + step_scale
|
1199
1810
|
self.zoom_icon(new_scale)
|
@@ -1202,6 +1813,12 @@ class spacrButton(tk.Frame):
|
|
1202
1813
|
self.is_zoomed_in = not self.is_zoomed_in
|
1203
1814
|
|
1204
1815
|
def zoom_icon(self, scale_factor):
|
1816
|
+
"""
|
1817
|
+
Resize and update the icon image on the canvas.
|
1818
|
+
|
1819
|
+
Args:
|
1820
|
+
scale_factor (float): Scaling factor relative to base icon size.
|
1821
|
+
"""
|
1205
1822
|
# Resize the original icon image
|
1206
1823
|
new_size = int(self.size * scale_factor)
|
1207
1824
|
resized_icon = self.original_icon_image.resize((new_size, new_size), Image.Resampling.LANCZOS)
|
@@ -1212,7 +1829,23 @@ class spacrButton(tk.Frame):
|
|
1212
1829
|
self.canvas.image = self.icon_photo
|
1213
1830
|
|
1214
1831
|
class spacrSwitch(ttk.Frame):
|
1832
|
+
"""
|
1833
|
+
A custom toggle switch widget with animated transitions and label, styled using the spacr dark theme.
|
1834
|
+
|
1835
|
+
This switch mimics a physical toggle with animated motion of the switch knob and changes in color.
|
1836
|
+
"""
|
1215
1837
|
def __init__(self, parent, text="", variable=None, command=None, *args, **kwargs):
|
1838
|
+
"""
|
1839
|
+
Initialize the spacrSwitch widget.
|
1840
|
+
|
1841
|
+
Args:
|
1842
|
+
parent (tk.Widget): Parent container.
|
1843
|
+
text (str): Label displayed to the left of the switch.
|
1844
|
+
variable (tk.BooleanVar, optional): Tkinter BooleanVar linked to the switch state.
|
1845
|
+
command (callable, optional): Function to call when the switch is toggled.
|
1846
|
+
*args: Additional positional arguments for the Frame.
|
1847
|
+
**kwargs: Additional keyword arguments for the Frame.
|
1848
|
+
"""
|
1216
1849
|
super().__init__(parent, *args, **kwargs)
|
1217
1850
|
self.text = text
|
1218
1851
|
self.variable = variable if variable else tk.BooleanVar()
|
@@ -1232,12 +1865,20 @@ class spacrSwitch(ttk.Frame):
|
|
1232
1865
|
_ = set_dark_style(style, containers=[self], widgets=[self.canvas, self.label])
|
1233
1866
|
|
1234
1867
|
def toggle(self, event=None):
|
1868
|
+
"""
|
1869
|
+
Toggle the state of the switch.
|
1870
|
+
|
1871
|
+
Updates the linked variable, animates the movement, and calls the `command` callback if defined.
|
1872
|
+
"""
|
1235
1873
|
self.variable.set(not self.variable.get())
|
1236
1874
|
self.animate_switch()
|
1237
1875
|
if self.command:
|
1238
1876
|
self.command()
|
1239
1877
|
|
1240
1878
|
def update_switch(self):
|
1879
|
+
"""
|
1880
|
+
Immediately update the switch position and color based on the current value of the variable.
|
1881
|
+
"""
|
1241
1882
|
if self.variable.get():
|
1242
1883
|
self.canvas.itemconfig(self.switch, fill="#008080")
|
1243
1884
|
self.canvas.coords(self.switch, 24, 4, 36, 16)
|
@@ -1246,6 +1887,9 @@ class spacrSwitch(ttk.Frame):
|
|
1246
1887
|
self.canvas.coords(self.switch, 4, 4, 16, 16)
|
1247
1888
|
|
1248
1889
|
def animate_switch(self):
|
1890
|
+
"""
|
1891
|
+
Trigger an animated transition of the switch knob between on and off states.
|
1892
|
+
"""
|
1249
1893
|
if self.variable.get():
|
1250
1894
|
start_x, end_x = 4, 24
|
1251
1895
|
final_color = "#008080"
|
@@ -1256,6 +1900,14 @@ class spacrSwitch(ttk.Frame):
|
|
1256
1900
|
self.animate_movement(start_x, end_x, final_color)
|
1257
1901
|
|
1258
1902
|
def animate_movement(self, start_x, end_x, final_color):
|
1903
|
+
"""
|
1904
|
+
Animate the horizontal movement of the switch knob.
|
1905
|
+
|
1906
|
+
Args:
|
1907
|
+
start_x (int): Starting x-coordinate of the knob.
|
1908
|
+
end_x (int): Ending x-coordinate of the knob.
|
1909
|
+
final_color (str): Fill color of the knob at the end of the animation.
|
1910
|
+
"""
|
1259
1911
|
step = 1 if start_x < end_x else -1
|
1260
1912
|
for i in range(start_x, end_x, step):
|
1261
1913
|
self.canvas.coords(self.switch, i, 4, i + 12, 16)
|
@@ -1264,13 +1916,36 @@ class spacrSwitch(ttk.Frame):
|
|
1264
1916
|
self.canvas.itemconfig(self.switch, fill=final_color)
|
1265
1917
|
|
1266
1918
|
def get(self):
|
1919
|
+
"""
|
1920
|
+
Get the current Boolean value of the switch.
|
1921
|
+
|
1922
|
+
Returns:
|
1923
|
+
bool: True if switch is on, False otherwise.
|
1924
|
+
"""
|
1267
1925
|
return self.variable.get()
|
1268
1926
|
|
1269
1927
|
def set(self, value):
|
1928
|
+
"""
|
1929
|
+
Set the switch to a given Boolean value.
|
1930
|
+
|
1931
|
+
Args:
|
1932
|
+
value (bool): New state for the switch.
|
1933
|
+
"""
|
1270
1934
|
self.variable.set(value)
|
1271
1935
|
self.update_switch()
|
1272
1936
|
|
1273
1937
|
def create_rounded_rectangle(self, x1, y1, x2, y2, radius=9, **kwargs):
|
1938
|
+
"""
|
1939
|
+
Draw a rounded rectangle polygon on the canvas.
|
1940
|
+
|
1941
|
+
Args:
|
1942
|
+
x1, y1, x2, y2 (int): Coordinates of the rectangle bounds.
|
1943
|
+
radius (int): Radius of corner curvature.
|
1944
|
+
**kwargs: Options passed to `create_polygon`.
|
1945
|
+
|
1946
|
+
Returns:
|
1947
|
+
int: ID of the created polygon item on the canvas.
|
1948
|
+
"""
|
1274
1949
|
points = [x1 + radius, y1,
|
1275
1950
|
x1 + radius, y1,
|
1276
1951
|
x2 - radius, y1,
|
@@ -1295,7 +1970,17 @@ class spacrSwitch(ttk.Frame):
|
|
1295
1970
|
return self.canvas.create_polygon(points, **kwargs, smooth=True)
|
1296
1971
|
|
1297
1972
|
class spacrToolTip:
|
1973
|
+
"""
|
1974
|
+
A simple tooltip widget for displaying hover text in a Tkinter application using spacr dark styling.
|
1975
|
+
"""
|
1298
1976
|
def __init__(self, widget, text):
|
1977
|
+
"""
|
1978
|
+
Initialize the tooltip for a given widget.
|
1979
|
+
|
1980
|
+
Args:
|
1981
|
+
widget (tk.Widget): The widget to attach the tooltip to.
|
1982
|
+
text (str): The text to display in the tooltip.
|
1983
|
+
"""
|
1299
1984
|
self.widget = widget
|
1300
1985
|
self.text = text
|
1301
1986
|
self.tooltip_window = None
|
@@ -1303,6 +1988,9 @@ class spacrToolTip:
|
|
1303
1988
|
widget.bind("<Leave>", self.hide_tooltip)
|
1304
1989
|
|
1305
1990
|
def show_tooltip(self, event):
|
1991
|
+
"""
|
1992
|
+
Display the tooltip near the cursor when mouse enters the widget.
|
1993
|
+
"""
|
1306
1994
|
x = event.x_root + 20
|
1307
1995
|
y = event.y_root + 10
|
1308
1996
|
self.tooltip_window = tk.Toplevel(self.widget)
|
@@ -1315,11 +2003,31 @@ class spacrToolTip:
|
|
1315
2003
|
_ = set_dark_style(style, containers=[self.tooltip_window], widgets=[label])
|
1316
2004
|
|
1317
2005
|
def hide_tooltip(self, event):
|
2006
|
+
"""
|
2007
|
+
Hide and destroy the tooltip when mouse leaves the widget.
|
2008
|
+
"""
|
1318
2009
|
if self.tooltip_window:
|
1319
2010
|
self.tooltip_window.destroy()
|
1320
2011
|
self.tooltip_window = None
|
1321
2012
|
|
1322
2013
|
def standardize_figure(fig):
|
2014
|
+
"""
|
2015
|
+
Apply standardized appearance settings to a matplotlib figure using spaCR GUI style preferences.
|
2016
|
+
|
2017
|
+
This includes:
|
2018
|
+
- Setting font size and font family based on spaCR's theme
|
2019
|
+
- Setting text and spine colors to match spaCR foreground color
|
2020
|
+
- Applying OpenSans font via `font_loader`
|
2021
|
+
- Removing top and right spines
|
2022
|
+
- Setting line and spine widths
|
2023
|
+
- Adjusting background and grid colors
|
2024
|
+
|
2025
|
+
Args:
|
2026
|
+
fig (matplotlib.figure.Figure): The matplotlib figure to standardize.
|
2027
|
+
|
2028
|
+
Returns:
|
2029
|
+
matplotlib.figure.Figure: The updated figure with standardized style.
|
2030
|
+
"""
|
1323
2031
|
from .gui_elements import set_dark_style
|
1324
2032
|
from matplotlib.font_manager import FontProperties
|
1325
2033
|
|
@@ -1398,7 +2106,7 @@ def modify_figure_properties(fig, scale_x=None, scale_y=None, line_width=None, f
|
|
1398
2106
|
"""
|
1399
2107
|
Modifies the properties of the figure, including scaling, line widths, font sizes, axis limits, x-axis label rotation, background color, text color, line color, and other common options.
|
1400
2108
|
|
1401
|
-
|
2109
|
+
Args:
|
1402
2110
|
- fig: The Matplotlib figure object to modify.
|
1403
2111
|
- scale_x: Scaling factor for the width of subplots (optional).
|
1404
2112
|
- scale_y: Scaling factor for the height of subplots (optional).
|
@@ -1497,6 +2205,19 @@ def modify_figure_properties(fig, scale_x=None, scale_y=None, line_width=None, f
|
|
1497
2205
|
fig.canvas.draw_idle()
|
1498
2206
|
|
1499
2207
|
def save_figure_as_format(fig, file_format):
|
2208
|
+
"""
|
2209
|
+
Opens a file dialog to save a matplotlib figure in the specified format.
|
2210
|
+
|
2211
|
+
Prompts the user to choose a save location and filename using a file dialog.
|
2212
|
+
The figure is saved using the provided format if a valid path is selected.
|
2213
|
+
|
2214
|
+
Args:
|
2215
|
+
fig (matplotlib.figure.Figure): The figure to save.
|
2216
|
+
file_format (str): The file format to save as (e.g., 'png', 'pdf', 'svg').
|
2217
|
+
|
2218
|
+
Returns:
|
2219
|
+
None
|
2220
|
+
"""
|
1500
2221
|
file_path = filedialog.asksaveasfilename(defaultextension=f".{file_format}", filetypes=[(f"{file_format.upper()} files", f"*.{file_format}"), ("All files", "*.*")])
|
1501
2222
|
if file_path:
|
1502
2223
|
try:
|
@@ -1506,6 +2227,26 @@ def save_figure_as_format(fig, file_format):
|
|
1506
2227
|
print(f"Error saving figure: {e}")
|
1507
2228
|
|
1508
2229
|
def modify_figure(fig):
|
2230
|
+
"""
|
2231
|
+
Opens a GUI window for interactively modifying various properties of a matplotlib figure.
|
2232
|
+
|
2233
|
+
This function allows users to:
|
2234
|
+
- Rescale the X and Y axes
|
2235
|
+
- Change line width and font size
|
2236
|
+
- Set axis limits and title
|
2237
|
+
- Customize colors (background, text, line)
|
2238
|
+
- Rotate x-axis labels
|
2239
|
+
- Enable/disable grid, legend, and axes
|
2240
|
+
- Toggle spine visibility ("spleens")
|
2241
|
+
|
2242
|
+
Once modifications are entered and applied, the figure is updated and re-rendered via `display_figure`.
|
2243
|
+
|
2244
|
+
Args:
|
2245
|
+
fig (matplotlib.figure.Figure): The matplotlib figure to modify.
|
2246
|
+
|
2247
|
+
Returns:
|
2248
|
+
None
|
2249
|
+
"""
|
1509
2250
|
from .gui_core import display_figure
|
1510
2251
|
def apply_modifications():
|
1511
2252
|
try:
|
@@ -1620,9 +2361,27 @@ def modify_figure(fig):
|
|
1620
2361
|
|
1621
2362
|
def generate_dna_matrix(output_path='dna_matrix.gif', canvas_width=1500, canvas_height=1000, duration=30, fps=20, base_size=20, transition_frames=30, font_type='arial.ttf', enhance=[1.1, 1.5, 1.2, 1.5], lowercase_prob=0.3):
|
1622
2363
|
"""
|
1623
|
-
|
2364
|
+
Generates an animated matrix-style DNA sequence visual and saves it as a GIF or video.
|
2365
|
+
|
2366
|
+
The animation simulates vertical streams of random DNA bases ('A', 'T', 'C', 'G') cascading down the screen.
|
2367
|
+
Each column has independently scrolling strings of bases. The latest base in each stream is highlighted,
|
2368
|
+
and fading effects, coloring, and random lowercase transformations are applied for visual flair.
|
2369
|
+
|
2370
|
+
Args:
|
2371
|
+
output_path (str): Path to the output file (should end in .gif, .mp4, or .avi).
|
2372
|
+
canvas_width (int): Width of the canvas in pixels.
|
2373
|
+
canvas_height (int): Height of the canvas in pixels.
|
2374
|
+
duration (int): Duration of the animation in seconds.
|
2375
|
+
fps (int): Frames per second of the animation.
|
2376
|
+
base_size (int): Font size (in pixels) for the bases.
|
2377
|
+
transition_frames (int): Number of frames for the looping transition.
|
2378
|
+
font_type (str): Path to a .ttf font to use. Defaults to Arial.
|
2379
|
+
enhance (list): List of four enhancement multipliers for [brightness, sharpness, contrast, saturation].
|
2380
|
+
lowercase_prob (float): Probability that a given base will be drawn in lowercase.
|
2381
|
+
|
2382
|
+
Returns:
|
2383
|
+
None
|
1624
2384
|
"""
|
1625
|
-
|
1626
2385
|
def save_output(frames, output_path, fps, output_format):
|
1627
2386
|
"""Save the animation based on output format."""
|
1628
2387
|
if output_format in ['.mp4', '.avi']:
|