Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a flood gauge style progressbar with label #38

Closed
israel-dryer opened this issue Apr 24, 2021 · 16 comments
Closed

Add a flood gauge style progressbar with label #38

israel-dryer opened this issue Apr 24, 2021 · 16 comments
Labels
enhancement New feature or request help wanted Extra attention is needed

Comments

@israel-dryer
Copy link
Owner

israel-dryer commented Apr 24, 2021

The idea behind this widget style is that it is similar to a card you would see commonly on a dashboard that includes text. It is essentially a progressbar with text in the middle. And, it can be used exactly as a progressbar, with some adjustments to the text size and thickness.

This style can be created by adding a custom layout based on the progressbar and the label.

image

I've decided to use a brightened and desaturated color for the progressbar background so that I have no need to change the text color when the gauge is filled past the 50% mark.

The method below would be added to the StylerTTK class. The code currently reflects the primary color styles. The other colors would be the same logic, with just an iteration over the other available colors.

def _style_floodgauge(self):
    """
    Create a style configuration for the *ttk.Progressbar* that makes it into a floodgauge. Which is essentially
    a very large progress bar with text in the middle.

    The options available in this widget include:

        - Floodgauge.trough: borderwidth, troughcolor, troughrelief
        - Floodgauge.pbar: orient, thickness, barsize, pbarrelief, borderwidth, background
        - Floodgauge.text: 'text', 'font', 'foreground', 'underline', 'width', 'anchor', 'justify', 'wraplength',
            'embossed'
    """
    self.settings.update({
        'Floodgauge.trough': {'element create': ('from', 'clam')},
        'Floodgauge.pbar': {'element create': ('from', 'default')},
        'Horizontal.TFloodgauge': {
            'layout': [('Floodgauge.trough', {'children': [
                ('Floodgauge.pbar', {'side': 'left', 'sticky': 'ns'}),
                ("Floodgauge.label", {"sticky": ""})],
                'sticky': 'nswe'})],
            'configure': {
                'thickness': 100,
                'borderwidth': 1,
                'bordercolor': self.theme.colors.primary,
                'lightcolor': self.theme.colors.primary,
                'pbarrelief': 'flat',
                'troughcolor': Colors.update_hsv(self.theme.colors.primary, sd=-0.3, vd=0.8),
                'background': self.theme.colors.primary,
                'foreground': self.theme.colors.selectfg,
                'justify': 'center',
                'anchor': 'center',
                'font': 'helvetica 16'}},
        'Vertical.TFloodgauge': {
            'layout': [('Floodgauge.trough', {'children': [
                ('Floodgauge.pbar', {'side': 'bottom', 'sticky': 'we'}),
                ("Floodgauge.label", {"sticky": ""})],
                'sticky': 'nswe'})],
            'configure': {
                'thickness': 100,
                'borderwidth': 1,
                'bordercolor': self.theme.colors.primary,
                'lightcolor': self.theme.colors.primary,
                'pbarrelief': 'flat',
                'troughcolor': Colors.update_hsv(self.theme.colors.primary, sd=-0.3, vd=0.8),
                'background': self.theme.colors.primary,
                'foreground': self.theme.colors.selectfg,
                'justify': 'center',
                'anchor': 'center',
                'font': 'helvetica 16'}
        }})

Below is a prototype of the Floodgauge class. I will have to handle the label options separately because TCL doesn't understand a hybrid widget. The progressbar takes priority, so an error will occur if I try to pass through the text options to the superclass constructor. However, these options can still manipulated in the style since they actually exist in the layout. To get around this, I've created a hack that generates a unique style based on the one passed into the constructor that can be altered continuously. I believe this is similar to the approach taken by other developers (eg. PySimpleGUI) for creating custom styles on each button.

In the case of changing the text. I've set a trace on the textvariable so that it updates the widget every time the textvariable changes.

import tkinter as tk
from tkinter import ttk
from ttkbootstrap import Style
from uuid import uuid4


class Floodgauge(ttk.Progressbar):

    def __init__(self, parent, **kw):
        _style = kw.get('style') or 'TFloodgauge'
        _id = uuid4()
        _orient = kw.get('orient').title() or 'Horizontal'
        self._widgetstyle = f'{_id}.{_orient}.{_style}'
        parent.tk.call("ttk::style", "configure", self._widgetstyle, '-%s' % None, None, None)

        kwargs = {k: v for k, v in kw.items() if k not in ['text']}

        self.textvariable = kw.get('textvariable') or tk.StringVar(value=kw.get('text'))
        self.textvariable.trace_add('write', self._textvariable_write)
        self.variable = kw.get('variable') or tk.IntVar(value=kw.get('value') or 0)

        super().__init__(parent, class_='Floodgauge', style=self._widgetstyle, variable=self.variable, **kwargs)

    @property
    def text(self):
        return self.textvariable.get()

    @text.setter
    def text(self, value):
        self.textvariable.set(value)

    @property
    def value(self):
        return self.variable.get()

    @value.setter
    def value(self, value):
        self.variable.set(value)

    def _textvariable_write(self, *args):
        """
        Update the label text when there is a `write` action on the textvariable
        """
        self.tk.call("ttk::style", "configure", self._widgetstyle, '-%s' % 'text', self.textvariable.get(), None)


if __name__ == '__main__':
    root = tk.Tk()
    root.geometry('400x400')
    s = Style()
    p = Floodgauge(root, value=55, text='55', orient='vertical')


    def auto(progress):
        p.text = f'Memory Usage\n{p.value}%'
        p.step(1)
        p.after(50, auto, p)


    p.pack(fill='both', padx=20, pady=20, expand='yes')
    auto(p)
    root.mainloop()
@israel-dryer israel-dryer added enhancement New feature or request help wanted Extra attention is needed labels Apr 24, 2021
@israel-dryer
Copy link
Owner Author

The Floodgauge is a lot more polished. I've published it in the ttkbootstrap.widgets module.

Here is an example of the Horizontal orientation. The style guide can be found here: https://ttkbootstrap.readthedocs.io/en/latest/widgets/floodgauge.html

image

import tkinter as tk
from tkinter import IntVar, StringVar
from tkinter import Tk
from tkinter.font import Font
from tkinter.ttk import Progressbar
from uuid import uuid4

from ttkbootstrap import Style


class Floodgauge(Progressbar):
    """A ``Floodgauge`` widget shows the status of a long-running operation with an optional text indicator.

    Similar to the ``ttk.Progressbar``, this widget can operate in two modes: **determinate** mode shows the amount
    completed relative to the total amount of work to be done, and **indeterminate** mode provides an animated display
    to let the user know that something is happening.

    Variable are generated automatically for this widget and can be linked to other widgets by referencing them via
    the ``textvariable`` and ``variable`` attributes.

    The ``text`` and ``value`` properties allow you to easily get and set the value of these variables without the need
    to call the ``get`` and ``set`` methods of the related tkinter variables. For example: ``Floodgauge.value`` or
    ``Floodgauge.value = 55`` will get or set the amount used on the widget.
    """

    def __init__(self,
                 parent,
                 cursor=None,
                 font=None,
                 length=None,
                 maximum=100,
                 mode='determinate',
                 orient='horizontal',
                 style='TFloodgauge',
                 takefocus=False,
                 text=None,
                 value=0,
                 **kw):
        """
        Args:
            parent (Tk): Parent widget
            cursor (str): The cursor that will appear when the mouse is over the progress bar.
            font (Font or str): The font to use for the progress bar label.
            length (int): Specifies the length of the long axis of the progress bar (width if horizontal, height if
                vertical); defaults to 300.
            maximum (float): A floating point number specifying the maximum ``value``. Defaults to 100.
            mode (str): One of **determinate** or **indeterminate**. Use `indeterminate` if you cannot accurately
                measure the relative progress of the underlying process. In this mode, a rectangle bounces back and
                forth between the ends of the widget once you use the ``.start()`` method.  Otherwise, use `determinate`
                if the relative progress can be calculated in advance. This is the default mode.
            orient (str): Specifies the orientation of the widget; either `horizontal` or `vertical`.
            style (str): The style used to render the widget; `TFloodgauge` by default.
            takefocus (bool): This widget is not included in focus traversal by default. To add the widget to focus
                traversal, use ``takefocus=True``.
            text (str): A string of text to be displayed in the progress bar. This is assigned to the ``textvariable``
                ``StringVar`` which is automatically generated on instantiation. This value can be get and set using the
                ``Floodgauge.text`` property without having to directly call the ``textvariable``.
            value: The current value of the progressbar. In `determinate` mode, this represents the amount of work
                completed. In `indeterminate` mode, it is interpreted modulo ``maximum``; that is, the progress bar
                completes one "cycle" when the ``value`` increases by ``maximum``.
            **kw: Other configuration options from the option database.
        """
        # create a custom style in order to adjust the text inside the progress bar layout
        if any(['Horizontal' in style, 'Vertical' in style]):
            self._widgetstyle = f'{uuid4()}.{style}'
        elif orient == 'vertical':
            self._widgetstyle = f'{uuid4()}.Vertical.TFloodgauge'
        else:
            self._widgetstyle = f'{uuid4()}.Horizontal.TFloodgauge'

        # progress bar value variable
        self.variable = IntVar(value=value)

        super().__init__(parent, class_='Floodgauge', cursor=cursor, length=length, maximum=maximum, mode=mode,
                         orient=orient, style=self._widgetstyle, takefocus=takefocus, variable=self.variable, **kw)

        # set the label font
        if font:
            self.tk.call("ttk::style", "configure", self._widgetstyle, '-%s' % 'font', font, None)

        # progress bar text variable
        self.textvariable = StringVar(value=text or '')
        self.textvariable.trace_add('write', self._textvariable_write)
        self._textvariable_write()

    @property
    def text(self):
        return self.textvariable.get()

    @text.setter
    def text(self, value):
        self.textvariable.set(value)

    @property
    def value(self):
        return self.variable.get()

    @value.setter
    def value(self, value):
        self.variable.set(value)

    def _textvariable_write(self, *args):
        """Callback to update the label text when there is a `write` action on the textvariable

        Args:
            *args: if triggered by a trace, will be `variable`, `index`, `mode`.
        """
        self.tk.call("ttk::style", "configure", self._widgetstyle, '-%s' % 'text', self.textvariable.get(), None)

@RocksWon
Copy link

RocksWon commented May 9, 2021

When i add the style parameter to the floodguage options i get this error

line 22, in init
super().init(parent, class_='Floodgauge', style=self._widgetstyle, variable=self.variable, **kwargs)
TypeError: tkinter.ttk.Progressbar.init() got multiple values for keyword argument 'style'

How can i fixed it.

@israel-dryer
Copy link
Owner Author

israel-dryer commented May 9, 2021

maybe I'm not understanding your issue... I wasn't able to replicate it by adding a style; the style option is already present, so you wouldn't need to add it to the constructor of the class if that is what you are doing.

This is also implemented in ttkbootstrap in the latest version.

@RocksWon
Copy link

guage = Floodgauge(parent, value=75, style='success.Horizontal.TFloodgauge')
guage.pack()

with this i get that error when the style parameter is added.

@israel-dryer
Copy link
Owner Author

@RocksWon, I'm not sure what would cause that. I'm using that code in this example and it's running fine.

Are you running it from your forked repository or from the pip installed version? If pip installed, which version? What is your OS?

I just released 0.5.1, and that is what I test this on in the link above.

@RocksWon
Copy link

RocksWon commented May 10, 2021

Firstly i used it from the forked repository but was not working and then i pip installed and was also giving me same error. But the 0.5.1 seems to work pretty well now now when i add the style parameter it doesn't throw an exception now. Thanks a lot man
your are a real champ and contributor to the tkinter community.

But by the way is there any new widgets available in this version or just some fixes?

@israel-dryer
Copy link
Owner Author

Glad to hear it's working. Added some fixes, and also added the calendar widgets.

https://ttkbootstrap.readthedocs.io/en/latest/widgets/calendar.html

@RocksWon
Copy link

I tried the calendar theme widgets and so far its working properly for me now. Thanks man you are awesome you've made my projects so awesome and easier for me. God richly bless you.

@RocksWon
Copy link

Helllo Mr Israel-dryer is the round scrollbar included in the version 0.5.2? If yes how can i use it i cant get to figure it out and if no what was the new things or fixes added to this version.

@israel-dryer
Copy link
Owner Author

@RocksWon the round scrollbar isn't in 0.5, mainly due to the limitation of having to create so many images. This is one of the things I changed in 1.0, where I am only creating the themes and styles that are used on-demand, instead of what is typically done with ttk themes, which is loading all the assets and building the styles first. However, by having so many pre-defined styles available, this caused memory issues.

Here is an issue that describes some of the changes made to 0.5 -> #82

@RocksWon
Copy link

Okay thanks very much

@RocksWon
Copy link

Hello please i need help with some. With the old ttkbootstrap version i can adjust the thickness of the progressbar like this

style = Style(theme="cosmo")
style.configure("secondary.Horizontal.TProgressbar", thickness=5, relief=tk.FLAT)

But with the new version i can't figure out how to do that can you please help me with that?

@israel-dryer
Copy link
Owner Author

That should still work.

Code_GSG8pHa6X7

@israel-dryer
Copy link
Owner Author

You can also try the new Window class, which can make things a bit more convenient. It also enables high dpi support on Windows by default.

Code_UTqBlHjpF2

@RocksWon
Copy link

Wow thanks very much bro you are awesome. But will the first method too work for the rounded scrollbar?

@RocksWon
Copy link

Hello how you doing? I just figured out that the ScrolledFrame does not scroll at all. Even with your example provided in the
documentation site does not work. Please help?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants