Skip to content

Commit

Permalink
Fix #1191: Replace various tk widgets with ttk versions
Browse files Browse the repository at this point in the history
  • Loading branch information
TeamSpen210 committed Oct 15, 2019
1 parent 5c3e64e commit cff148d
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 38 deletions.
65 changes: 43 additions & 22 deletions src/itemPropWin.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,12 @@ def is_editable(self) -> bool:
last_angle = '0'

is_open = False
# ttk.Scale works on floating point values,
# so it can be put partway. We need to suppress calling our callbacks
# whilst we fix it.
enable_tim_callback = True
enable_pist_callback = True


def callback(name):
"""Do nothing by default!"""
Expand Down Expand Up @@ -196,11 +201,11 @@ def save_angle(key, new_angle):
def save_tim(key, val):
global enable_tim_callback
if enable_tim_callback:
new_val = math.floor(float(val) + 0.5)
new_val = round(float(val))

# Lock to whole numbers
enable_tim_callback = False
widgets[key].set(new_val)
# Lock to whole numbers
enable_tim_callback = True

labels[key]['text'] = (
Expand All @@ -217,27 +222,45 @@ def save_tim(key, val):


def save_pist(key, val):
if widgets['toplevel'].get() == widgets['bottomlevel'].get():
"""The top and bottom positions are closely interrelated."""
global enable_pist_callback
if not enable_pist_callback:
return
try:
top_wid: ttk.Scale = widgets['toplevel']
btm_wid: ttk.Scale = widgets['bottomlevel']
except KeyError:
return # Both don't exist yet.

# The ttk Scale widget doesn't snap to integers, so we need to do that.
prev_top = top_wid.get()
new_top = round(prev_top)
prev_btm = btm_wid.get()
new_btm = round(prev_btm)

enable_pist_callback = False
top_wid.set(new_top)
btm_wid.set(new_btm)
enable_pist_callback = True

if top_wid.get() == btm_wid.get():
# user moved them to match, switch the other one around
sound.fx_blockable('swap')
widgets[
'toplevel' if key == 'bottomlevel' else 'bottomlevel'
].set(values[key])
else:
(top_wid if key == 'bottomlevel' else btm_wid).set(values[key])
elif prev_top != new_top or prev_btm != new_btm:
# Only play when we've actually changed.
sound.fx_blockable('move')

start_pos = widgets['toplevel'].get()
end_pos = widgets['bottomlevel'].get()

values['toplevel'] = start_pos
values['bottomlevel'] = end_pos
values['toplevel'] = start_pos = top_wid.get()
values['bottomlevel'] = end_pos = btm_wid.get()

values['startup'] = srctools.bool_as_int(start_pos > end_pos)
out_values['toplevel'] = str(max(start_pos, end_pos))
out_values['bottomlevel'] = str(min(start_pos, end_pos))


def save_rail(key):
def save_rail(key) -> None:
"""Rail oscillation prevents Start Active from having any effect."""
if values[key].get() == 0:
widgets['startactive'].state(['disabled'])
values['startactive'].set(False)
Expand All @@ -246,6 +269,7 @@ def save_rail(key):


def toggleCheck(key, var, e=None):
"""Toggle a checkbox."""
if var.get():
var.set(0)
else:
Expand All @@ -258,11 +282,7 @@ def set_check(key):
out_values[key] = str(values[key].get())


def paint_fx(e=None):
sound.fx_blockable('config')


def exit_win(e=None):
def exit_win(e=None) -> None:
"""Quit and save the new settings."""
global is_open
win.grab_release()
Expand Down Expand Up @@ -315,6 +335,8 @@ def init(cback):
'moveableModal',
''
)
# Stop our init from triggering UI sounds.
sound.block_fx()

frame = ttk.Frame(win, padding=10)
frame.grid(row=0, column=0, sticky='NSEW')
Expand Down Expand Up @@ -392,25 +414,24 @@ def init(cback):
out_values[key] = str(DEFAULTS[key])

elif prop_type is PropTypes.PISTON:
widgets[key] = Scale(
widgets[key] = pist_scale = ttk.Scale(
frame,
from_=0,
to=4,
orient="horizontal",
showvalue=False,
command=func_partial(save_pist, key),
)
values[key] = DEFAULTS[key]
out_values[key] = str(DEFAULTS[key])
if ((key == 'toplevel' and DEFAULTS['startup']) or
(key == 'bottomlevel' and not DEFAULTS['startup'])):
widgets[key].set(max(
pist_scale.set(max(
DEFAULTS['toplevel'],
DEFAULTS['bottomlevel']
))
if ((key == 'toplevel' and not DEFAULTS['startup']) or
(key == 'bottomlevel' and DEFAULTS['startup'])):
widgets[key].set(min(
pist_scale.set(min(
DEFAULTS['toplevel'],
DEFAULTS['bottomlevel']))

Expand Down
90 changes: 74 additions & 16 deletions src/itemconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
import tkinter as tk
from tkinter import ttk
from tkinter.colorchooser import askcolor

from functools import lru_cache
import math

from srctools import Property, Vec, conv_int, conv_bool
from packageLoader import PakObject, ExportData, ParseData, desc_parse
import BEE2_config
from tooltip import add_tooltip
from tk_tools import ttk_Spinbox
import tkMarkdown
import utils
import srctools.logger
Expand Down Expand Up @@ -404,6 +405,16 @@ def widget_sfx(*args):
sound.fx_blockable('config')


def decimal_points(num: float) -> int:
"""Count the number of decimal points required to display a number."""
str_num = format(num, 'g')
if '.' in str_num:
whole, frac = str_num.split('.')
return len(frac)
else:
return 0


# ------------
# Widget types
# ------------
Expand Down Expand Up @@ -495,16 +506,63 @@ def widget_checkmark_multi(
@WidgetLookup('range', 'slider')
def widget_slider(parent: tk.Frame, var: tk.StringVar, conf: Property) -> tk.Widget:
"""Provides a slider for setting a number in a range."""
scale = tk.Scale(
parent,
limit_min = conf.float('min', 0)
limit_max = conf.float('max', 100)
step = conf.float('step', 1)

# We have to manually translate the UI position to a value.
ui_min = 0
ui_max = abs(math.ceil((limit_max - limit_min) / step))
ui_var = tk.StringVar()

# The formatting of the text display is a little complex.
# We want to keep the same number of decimal points for all values.
txt_format = '.{}f'.format(max(
decimal_points(limit_min + step * offset)
for offset in range(0, int(ui_max) + 1)
))
# Then we want to figure out the longest value with this format to set
# the widget width
widget_width = max(
len(format(limit_min + step * offset, txt_format))
for offset in range(0, int(ui_max) + 1)
)

def change_cmd(*args) -> None:
new_pos = format(limit_min + step * round(scale.get()), txt_format)
if var.get() != new_pos:
widget_sfx()
var.set(new_pos)

def trace_func(*args) -> None:
off = (float(var.get()) - limit_min) / step
ui_var.set(str(round(off)))

trace_func()
ui_var.trace_add('write', trace_func)

frame = ttk.Frame(parent)
frame.columnconfigure(1, weight=1)

disp = ttk.Label(
frame,
textvariable=var,
width=widget_width,
justify='right'
)
scale = ttk.Scale(
frame,
orient='horizontal',
from_=conf.float('min'),
to=conf.float('max', 100),
resolution=conf.float('step', 1),
variable=var,
command=widget_sfx,
from_=ui_min,
to=ui_max,
variable=ui_var,
command=change_cmd,
)
return scale

disp.grid(row=0, column=0)
scale.grid(row=0, column=1, sticky='ew')

return frame


@WidgetLookup('color', 'colour', 'rgb')
Expand All @@ -519,7 +577,7 @@ def widget_color_single(
"""
# Isolates the swatch so it doesn't resize.
frame = ttk.Frame(parent)
swatch = make_color_swatch(frame, var)
swatch = make_color_swatch(frame, var, 24)
swatch.grid(row=0, column=0, sticky='w')
return frame

Expand All @@ -529,12 +587,12 @@ def widget_color_multi(
parent: tk.Frame, values: List[Tuple[str, tk.StringVar]], conf: Property):
"""For color swatches, display in a more compact form."""
for row, column, tim_text, var in multi_grid(values):
swatch = make_color_swatch(parent, var)
swatch = make_color_swatch(parent, var, 16)
swatch.grid(row=row, column=column)
add_tooltip(swatch, tim_text, delay=0)


def make_color_swatch(parent: tk.Frame, var: tk.StringVar, size=16) -> ttk.Label:
def make_color_swatch(parent: tk.Frame, var: tk.StringVar, size: int) -> ttk.Label:
"""Make a single swatch."""
# Note: tkinter requires RGB as ints, not float!

Expand Down Expand Up @@ -566,10 +624,7 @@ def open_win(e):
r, g, b = map(int, new_color) # Returned as floats, which is wrong.
var.set('{} {} {}'.format(int(r), int(g), int(b)))

swatch = ttk.Label(
parent,
relief='raised',
)
swatch = ttk.Label(parent)

def update_image(var_name: str, var_index: str, operation: str):
r, g, b = get_color()
Expand Down Expand Up @@ -690,6 +745,9 @@ def validate(reason: str, operation_type: str, cur_value: str, new_char: str, ne

validate_cmd = parent.register(validate)

# Unfortunately we can't use ttk.Spinbox() here, it doesn't support
# the validation options.
# TODO: Update when possible.
spinbox = tk.Spinbox(
parent,
exportselection=False,
Expand Down

0 comments on commit cff148d

Please sign in to comment.