israel-dryer / ttkbootstrap

A supercharged theme extension for tkinter that enables on-demand modern flat style themes inspired by Bootstrap.
MIT License
1.79k stars 363 forks source link

Allow negative values for meter widget #464

Open Cr4ckC4t opened 1 year ago

Cr4ckC4t commented 1 year ago

Is your feature request related to a problem? Please describe.

Currently, the minimum value of the meter appears to be hardcoded to zero. Trying to create a temperature scale for example that includes negative values results in unexpected behavior.

Describe the solution you'd like

Allow the user to specify a min and max value for the meter widget - thus making the range completely customizable and more versatile.

For example:

ttk.Meter(
    # ...
    amountmin = -20,
    amountmax = 40,
    amountused = -12,
    # ...
)

example-meter

The modifications made to widgets.py weren't too complicated and don't seem to break the expected behavior.

Describe alternatives you've considered

Alternatives may include allowing the user to set the amountused text in the center of the meter to something different than the set value (for example, applying an offset or conversion of some sort). However, this is more dirty in my opinion.

Additional context

Here are my modifications to widgets.py that were used to create the meter seen above.

Please note that this is merely a monkeypatch. I did not get to test this extensively and although I tried to keep the impact on the existing API as minimal as possible, the amountotal parameter is currently not available in the __init__. But this could easily be fixed by someone with more time and better understanding of the source code.

diff --git a/src/ttkbootstrap/widgets.py b/src/ttkbootstrap/widgets.py
index 41b293c..53753f7 100644
--- a/src/ttkbootstrap/widgets.py
+++ b/src/ttkbootstrap/widgets.py
@@ -588,7 +588,8 @@ class Meter(ttk.Frame):
         bootstyle=DEFAULT,
         arcrange=None,
         arcoffset=None,
-        amounttotal=100,
+        amountmin=0,
+        amountmax=100,
         amountused=0,
         wedgesize=0,
         metersize=200,
@@ -691,9 +692,11 @@ class Meter(ttk.Frame):
         super().__init__(master=master, **kwargs)

         # widget variables
+        self.amountmaxvar = tk.IntVar(value=amountmax)
+        self.amountminvar = tk.IntVar(value=amountmin)
         self.amountusedvar = tk.IntVar(value=amountused)
         self.amountusedvar.trace_add("write", self._draw_meter)
-        self.amounttotalvar = tk.IntVar(value=amounttotal)
+        self.amounttotalvar = tk.IntVar(value=amountmax-amountmin)
         self.labelvar = tk.StringVar(value=subtext)

         # misc settings
@@ -704,7 +707,7 @@ class Meter(ttk.Frame):
         self._stripethickness = stripethickness
         self._showtext = showtext
         self._wedgesize = wedgesize
-        self._stepsize = stepsize        
+        self._stepsize = stepsize
         self._textleft = textleft
         self._textright = textright
         self._textfont = textfont
@@ -944,7 +947,7 @@ class Meter(ttk.Frame):
         """Calculate the value to be used to draw the arc length of the
         progress meter."""
         value = int(
-            (self["amountused"] / self["amounttotal"]) * self._arcrange
+            ((self["amountused"]-self["amountmin"]) / self["amounttotal"]) * self._arcrange
             + self._arcoffset
         )
         return value
@@ -999,8 +1002,12 @@ class Meter(ttk.Frame):
             return self._arcrange
         elif cnf == "arcoffset":
             return self._arcoffset
+        elif cnf == "amountmin":
+            return self.amountminvar.get()
+        elif cnf == "amountmax":
+            return self.amountmaxvar.get()
         elif cnf == "amounttotal":
-            return self.amounttotalvar.get()
+            return self.amountmaxvar.get() - self.amountminvar.get()
         elif cnf == "amountused":
             return self.amountusedvar.get()
         elif cnf == "interactive":
@@ -1047,6 +1054,15 @@ class Meter(ttk.Frame):
         if "amounttotal" in kwargs:
             amounttotal = kwargs.pop("amounttotal")
             self.amounttotalvar.set(amounttotal)
+            self.amountminvar.set(0)
+        if "amountmin" in kwargs:
+            amountmin = kwargs.pop("amountmin")
+            self.amountmivvar.set(amountmin)
+            self.amounttotalvar.set(self.amountmaxvar.get()-amountmin)
+        if "amountmax" in kwargs:
+            amountmax = kwargs.pop("amountmax")
+            self.amountmaxvar.set(amountmax)
+            self.amounttotalvar.set(amountmax-self.amountminvar.get())
         if "amountused" in kwargs:
             amountused = kwargs.pop("amountused")
             self.amountusedvar.set(amountused)

If this turns out to be a much requested enhancement I can offer to rework this and publish a pull request. For the time being - this works for me and I'll leave it here for anyone else who just wants to quickly incorporate negative values in their meter widget.