Skip to content

Commit 67c4f2f

Browse files
radio styling change.
1 parent 5c71565 commit 67c4f2f

5 files changed

Lines changed: 133 additions & 39 deletions

File tree

Python_Engine/Python/src/python_toolkit/bhom_tkinter/theming/bhom_dark_theme.tcl

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,51 @@ namespace eval ttk::theme::bhom_dark {
444444
active $colors(-hover-bg) \
445445
disabled $colors(-disabled-bg)]
446446

447+
# Larger radiobutton variant used by RadioSelection widget
448+
ttk::style layout Radio.TRadiobutton {
449+
Radiobutton.padding -sticky nswe -children {
450+
Radiobutton.indicator -side left -sticky {}
451+
Radiobutton.label -side left -sticky w
452+
}
453+
}
454+
455+
ttk::style configure Radio.TRadiobutton \
456+
-font {{Segoe UI} 11} \
457+
-padding {6 8} \
458+
-indicatormargin {0 0 10 0} \
459+
-indicatorsize 15 \
460+
-borderwidth 0 \
461+
-relief flat \
462+
-focusthickness 0 \
463+
-indicatorbackground $colors(-inputbg) \
464+
-indicatorforeground $colors(-inputbg) \
465+
-upperbordercolor $colors(-border) \
466+
-lowerbordercolor $colors(-border)
467+
468+
ttk::style map Radio.TRadiobutton \
469+
-background [list \
470+
active $colors(-bg) \
471+
selected $colors(-bg)] \
472+
-foreground [list \
473+
active $colors(-primary) \
474+
disabled $colors(-disabled-fg)] \
475+
-indicatorbackground [list \
476+
selected $colors(-primary) \
477+
active $colors(-inputbg) \
478+
disabled $colors(-disabled-bg)] \
479+
-indicatorforeground [list \
480+
selected $colors(-primary) \
481+
active $colors(-inputbg) \
482+
disabled $colors(-disabled-bg)] \
483+
-upperbordercolor [list \
484+
selected $colors(-primary) \
485+
active $colors(-border) \
486+
disabled $colors(-disabled-bg)] \
487+
-lowerbordercolor [list \
488+
selected $colors(-primary) \
489+
active $colors(-border) \
490+
disabled $colors(-disabled-bg)]
491+
447492
# Scrollbar - minimal sleek design without arrows
448493
ttk::style configure TScrollbar \
449494
-background $colors(-border) \

Python_Engine/Python/src/python_toolkit/bhom_tkinter/theming/bhom_light_theme.tcl

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,51 @@ namespace eval ttk::theme::bhom_light {
447447
active $colors(-hover-bg) \
448448
disabled $colors(-disabled-bg)]
449449

450+
# Larger radiobutton variant used by RadioSelection widget
451+
ttk::style layout Radio.TRadiobutton {
452+
Radiobutton.padding -sticky nswe -children {
453+
Radiobutton.indicator -side left -sticky {}
454+
Radiobutton.label -side left -sticky w
455+
}
456+
}
457+
458+
ttk::style configure Radio.TRadiobutton \
459+
-font {{Segoe UI} 11} \
460+
-padding {6 8} \
461+
-indicatormargin {0 0 10 0} \
462+
-indicatorsize 15 \
463+
-borderwidth 0 \
464+
-relief flat \
465+
-focusthickness 0 \
466+
-indicatorbackground $colors(-inputbg) \
467+
-indicatorforeground $colors(-inputbg) \
468+
-upperbordercolor $colors(-border) \
469+
-lowerbordercolor $colors(-border)
470+
471+
ttk::style map Radio.TRadiobutton \
472+
-background [list \
473+
active $colors(-bg) \
474+
selected $colors(-bg)] \
475+
-foreground [list \
476+
active $colors(-primary) \
477+
disabled $colors(-disabled-fg)] \
478+
-indicatorbackground [list \
479+
selected $colors(-primary) \
480+
active $colors(-inputbg) \
481+
disabled $colors(-disabled-bg)] \
482+
-indicatorforeground [list \
483+
selected $colors(-primary) \
484+
active $colors(-inputbg) \
485+
disabled $colors(-disabled-bg)] \
486+
-upperbordercolor [list \
487+
selected $colors(-primary) \
488+
active $colors(-border) \
489+
disabled $colors(-disabled-bg)] \
490+
-lowerbordercolor [list \
491+
selected $colors(-primary) \
492+
active $colors(-border) \
493+
disabled $colors(-disabled-bg)]
494+
450495
# Scrollbar - minimal sleek design without arrows
451496
ttk::style configure TScrollbar \
452497
-background $colors(-border) \

Python_Engine/Python/src/python_toolkit/bhom_tkinter/widgets/cmap_selector.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ def __init__(
6464
cmap_bins: int = 256,
6565
default_cmap: Optional[str] = None,
6666
plot_size: tuple[int, int] = (400, 50),
67+
dropdown_position: Literal["n", "e", "s", "w"] = "n",
6768
**kwargs
6869
) -> None:
6970
"""
@@ -76,6 +77,9 @@ def __init__(
7677
cmap_set: Preset colormap set to use when colormaps is None.
7778
Allowed values: "all", "continuous", "categorical".
7879
default_cmap: Optional default colormap to select.
80+
dropdown_position: Position of the dropdown relative to the plot.
81+
"n" = above, "s" = below, "w" = left, "e" = right.
82+
Defaults to "w".
7983
**kwargs: Additional Frame options
8084
"""
8185
super().__init__(parent, **kwargs)
@@ -105,11 +109,19 @@ def __init__(
105109
content.grid(row=0, column=0, padx=0, pady=4, sticky=self._grid_sticky)
106110
content.grid_propagate(False)
107111

108-
header = ttk.Frame(content)
109-
header.pack(fill=tk.X, anchor=self._pack_anchor, padx=0, pady=(8, 4))
110-
111112
self.cmap_set_var = tk.StringVar(value=cmap_set.lower())
112113

114+
pos = dropdown_position.lower()
115+
is_horizontal = pos in ("w", "e")
116+
pack_side_combo = {"n": tk.TOP, "s": tk.BOTTOM, "w": tk.LEFT, "e": tk.RIGHT}[pos]
117+
pack_side_figure = {"n": tk.TOP, "s": tk.TOP, "w": tk.LEFT, "e": tk.LEFT}[pos]
118+
119+
combo_padx = (0, 4) if is_horizontal else 0
120+
combo_pady = (8, 4) if not is_horizontal else 0
121+
122+
header = ttk.Frame(content)
123+
header.pack(side=pack_side_combo, anchor=self._pack_anchor, padx=combo_padx, pady=combo_pady)
124+
113125
self.cmap_combobox = ttk.Combobox(
114126
header,
115127
textvariable=self.colormap_var,
@@ -119,11 +131,12 @@ def __init__(
119131
self.cmap_combobox.pack(side=tk.TOP, anchor=self._pack_anchor, padx=0)
120132
self.cmap_combobox.bind("<<ComboboxSelected>>", self._on_cmap_selected)
121133

134+
fill_mode = tk.Y if is_horizontal else tk.X
122135
self.figure_widget = FigureContainer(
123136
content,
124137
width=plot_size[0],
125138
height=plot_size[1],
126-
build_options=PackingOptions(anchor=self._pack_anchor, padx=0, pady=(0, 8)),
139+
build_options=PackingOptions(side=pack_side_figure, anchor=self._pack_anchor, fill=fill_mode, padx=0, pady=(0, 8)),
127140
)
128141
self.figure_widget.build()
129142

Python_Engine/Python/src/python_toolkit/bhom_tkinter/widgets/radio_selection.py

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
"""Single-select radio-style widget built from clickable labels."""
1+
"""Single-select radio-style widget built from ttk Radiobuttons."""
22

33
import tkinter as tk
44
from tkinter import ttk
5-
from python_toolkit.bhom_tkinter.widgets.label import Label
65
from typing import Optional, Literal
76

87
from python_toolkit.bhom_tkinter.widgets._widgets_base import BHoMBaseWidget
@@ -65,22 +64,16 @@ def _build_buttons(self):
6564

6665
for index, field in enumerate(self.fields):
6766
sticky = self._grid_sticky
68-
button = Label(
67+
button = ttk.Radiobutton(
6968
self.buttons_frame,
70-
text=f"○ {field}",
71-
style="Body.TLabel",
69+
text=field,
70+
variable=self.value_var,
71+
value=field,
72+
style="Radio.TRadiobutton",
73+
command=lambda f=field: self._select_field(f),
7274
)
7375
self.align_child_text(button)
74-
# Bind clicks on both wrapper and inner label so user clicks register
75-
button.bind("<Button-1>", lambda _event, f=field: self._select_field(f))
76-
try:
77-
button.label.configure(cursor="hand2")
78-
except Exception:
79-
pass
80-
try:
81-
button.label.bind("<Button-1>", lambda _event, f=field: self._select_field(f))
82-
except Exception:
83-
pass
76+
8477
if self.max_per_line and self.max_per_line > 0:
8578
if self.orient == "horizontal":
8679
row = index // self.max_per_line
@@ -120,23 +113,10 @@ def _build_buttons(self):
120113
self._buttons.append(button)
121114

122115
def _select_field(self, field):
123-
"""Select a field when clicked."""
124-
self.value_var.set(field)
125-
self._update_visual_state()
116+
"""Handle radio button selection."""
126117
if self.command:
127118
self.command(self.get())
128119

129-
def _update_visual_state(self):
130-
"""Update visual indicators for all buttons."""
131-
selected_value = self.value_var.get()
132-
for button in self._buttons:
133-
button_text = button.get()
134-
current_field = button_text[2:]
135-
if current_field == selected_value:
136-
button.set(f"● {current_field}")
137-
else:
138-
button.set(f"○ {current_field}")
139-
140120
def get(self):
141121
"""Return the currently selected value.
142122
@@ -154,7 +134,6 @@ def set(self, value):
154134
value = str(value)
155135
if value in self.fields:
156136
self.value_var.set(value)
157-
self._update_visual_state()
158137

159138
def set_fields(self, fields, default=None):
160139
"""Replace the available fields and rebuild the widget.

Python_Engine/Python/src/python_toolkit/bhom_tkinter/widgets/spinbox.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class Spinbox(BHoMBaseWidget):
1313
def __init__(
1414
self,
1515
parent,
16-
values: Optional[List[str]] = None,
16+
values: Optional[Union[List[str], List[int], List[float]]] = None,
1717
from_: Optional[Union[int, float]] = None,
1818
to: Optional[Union[int, float]] = None,
1919
increment: Union[int, float] = 1,
@@ -43,6 +43,14 @@ def __init__(
4343
self.command = command
4444
self._value_var = tk.StringVar()
4545

46+
# Determine the native type for get() coercion
47+
if values and len(values) > 0:
48+
self._value_type = type(values[0])
49+
elif from_ is not None:
50+
self._value_type = type(from_)
51+
else:
52+
self._value_type = str
53+
4654
spinbox_kwargs: dict = dict(
4755
textvariable=self._value_var,
4856
width=width,
@@ -75,13 +83,17 @@ def _on_change(self, *_):
7583
if self.command:
7684
self.command(self.get())
7785

78-
def get(self) -> str:
79-
"""Return the current value as a string.
86+
def get(self) -> Union[str, int, float]:
87+
"""Return the current value cast to its original type.
8088
8189
Returns:
82-
str: Current spinbox value.
90+
str | int | float: Current spinbox value in its original type.
8391
"""
84-
return self._value_var.get()
92+
raw = self._value_var.get()
93+
try:
94+
return self._value_type(raw)
95+
except (ValueError, TypeError):
96+
return raw
8597

8698
def get_int(self) -> int:
8799
"""Return the current value as an integer.

0 commit comments

Comments
 (0)