israel-dryer / ttkbootstrap

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

Am I doing something wrong, or is there a bug in the button theme (disabled buttons vs light theme)? #122

Closed antrrax closed 2 years ago

antrrax commented 2 years ago

In light themes the disabled buttons are 'invisible'. ttkbootstrap 1.3.1

I'm starting the app with this setting:

self.admin_search_next = ttk.Button(self.frm, image=self.img_next, command=self.admin_next_row)
self.admin_search_next.pack(side=RIGHT, padx=1)
self.admin_search_next.configure(state='disabled')

https://www.mediafire.com/file/w33t3rzfdn318wa/disabled_buttons_no_contrast.mp4/file see the video, in dark themes there is a contrast of colors between the normal and disabled buttons. But in light themes there could be this contrast as well. Mainly on buttons without text that have a white image.

disabled buttons - light theme -- invisible, mostly buttons just white images.

disabled-light

default buttons - light theme -- OK

default-light

disabled buttons - dark theme -- OK

disable-dark

default buttons - dark theme -- OK

default-dark

Am I doing something wrong?

israel-dryer commented 2 years ago

@antrrax, yes, I need to take a look at the colors for disabled buttons.

israel-dryer commented 2 years ago

@antrrax

I found some code that I was able to modify for my own purpose. I can simulate a transparency effect that I think may be useful for many things in this application but specifically for this disabled color situation.

from PIL import Image, ImageColor

def make_transparent(alpha, foreground, background='#ffffff'):
    """Simulate color transparency.

    Parameters:

        alpha (float):
            The amount of transparency; a number between 0 and 1.

        foreground (str):
            The foreground color.

        background (str):
            The background color.

    Returns:

        Tuple:
            An rgb color representing the "transparent" version of the
            foreground color against the background color.
    """
    fg = ImageColor.getrgb(foreground)
    bg = ImageColor.getrgb(background)
    new = [alpha * c1 + (1 - alpha) * c2 for (c1, c2) in zip(fg, bg)]
    return tuple([int(x) for x in new])

# testing
color1 = '#ff0000'
color2 = '#ffffff'
color3 = make_transparent(0.25, color1, color2)

img = Image.new('RGB', size=[100, 100], color=color3)
img.show()

color1 color1

color2 color2

color3 color3

israel-dryer commented 2 years ago

@antrrax @daniilS whare are you thoughts on using styled disabled buttons instead of using a standard gray color? Apparently there is a big debate on the topic (who knew).

I'm using 30% opacity on the light themes and 40% opacity on the dark themes in the example below python_ATsQn4ArNj

This is a scheme that could potentially be applied to all the widgets.

Also, having the new make_transparent method will be super helpful in making the hover and focus color calculations much more consistent and as expected.

antrrax commented 2 years ago

This contrast with transparency got much better.


If you need to determine if a color is light or dark you can use this formula: above 128 is considered light color

def get_color_luminosity(value):
    """
    above 128 the color is considered light
    below 128 the color is considered dark

    Args:
        value (tuple): RGB color tuple 
    """

    return int(((value[0] * 299) + (value[1] * 587) + (value[2] * 114)) / 1000)

Other Color Functions:

Maybe a calculation with tints will also help, see the 5th or 6th or 7th tint:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

import colorsys
import math

#https://www.color-hex.com/color/f44336

# Analogous Colors:     -30º
# Triadic Colors:       -120º
# Complementary Color:  -180*
# Shades:   color to 000000
# Tints:    color to ffffff

####################################################
# SHADES
####################################################
def calc_shades(Percent, color_item):
    return math.floor( (color_item) * ( (100-Percent) * 0.01 ) )

def get_ten_shades(tp_rgb_color):
    rgb_shades = []
    hex_shades = []
    #variações de cor de 10% em 10% 
    for j in range(0, 110, 10):
        r = calc_shades(j, tp_rgb_color[0])
        g = calc_shades(j, tp_rgb_color[1])
        b = calc_shades(j, tp_rgb_color[2])
        new_rgb_tint = (r,g,b)
        rgb_shades.append(new_rgb_tint)

        new_hex_tint = rgb_to_hex(new_rgb_tint)
        hex_shades.append(new_hex_tint)

    return rgb_shades, hex_shades
####################################################

####################################################
# TINTS
####################################################
def calc_tints(Percent, color_item):
    return math.floor( (color_item) + ( (255 - color_item) * ((100-Percent) * 0.01) ) )

def get_ten_tints(tp_rgb_color):
    rgb_tints = []
    hex_tints = []
    #variações de cor de 10% em 10% 
    for j in range(100, -10, -10):
        r = calc_tints(j, tp_rgb_color[0])
        g = calc_tints(j, tp_rgb_color[1])
        b = calc_tints(j, tp_rgb_color[2])
        new_rgb_tint = (r,g,b)
        rgb_tints.append(new_rgb_tint)

        new_hex_tint = rgb_to_hex(new_rgb_tint)
        hex_tints.append(new_hex_tint)

    return rgb_tints, hex_tints
####################################################

####################################################
# ADJACENTS
####################################################
DEG30 = -30/360.    #Analogous Colors
DEG120 = -120/360.   #Triadic Colors
DEG180 = -180/360.   #Complementary Color
def adjacent_colors(r, g, b, d=DEG30): # Assumption: r, g, b in [0, 255]
    r, g, b = map(lambda x: x/255., [r, g, b]) # Convert to [0, 1]
    h, l, s = colorsys.rgb_to_hls(r, g, b)     # RGB -> HLS
    h = [(h+d) % 1 for d in (-d, d)]           # Rotation by d
    adjacent = [map(lambda x: int(round(x*255)), colorsys.hls_to_rgb(hi, l, s))
            for hi in h] # H'LS -> new RGB
    return adjacent
####################################################

####################################################
# LUMINOSITY
####################################################
def get_color_luminosity(value):
    """
    above 128 the color is considered light
    below 128 the color is considered dark

    Args:
        value (tuple): RGB color tuple 
    """

    return int(((value[0] * 299) + (value[1] * 587) + (value[2] * 114)) / 1000)
####################################################

def rgb_to_hex(colortuple):
    return '#' + ''.join(f'{i:02X}' for i in colortuple)

def hex_to_rgb(value):
    value = value.lstrip('#')
    lv = len(value)
    return tuple(int(value[i:i+lv//3], 16) for i in range(0, lv, lv//3))

#--------- Example:
c_hex = '#f44336'
c_rgb = hex_to_rgb(c_hex)

print(c_hex, c_rgb)

print('\nluminosity:', get_color_luminosity(c_rgb), '\n')

analogous = adjacent_colors(c_rgb[0], c_rgb[1], c_rgb[2], d=DEG30)
for item in analogous:
    print('Analogous:', tuple(item))

print('')
triadic = adjacent_colors(c_rgb[0], c_rgb[1], c_rgb[2], d=DEG120)
for item in triadic:
    print('Triadic:' , tuple(item))

print('')
complementary = adjacent_colors(c_rgb[0], c_rgb[1], c_rgb[2], d=DEG180)
for item in complementary:
    print('Complementary:' , tuple(item))

print('')
print('10 Tints:\n', get_ten_tints(c_rgb), '\n')

print('10 Shaders:\n', get_ten_shades(c_rgb))
israel-dryer commented 2 years ago

alternatively, if I stick with the grayish disabled color, using a 12% transparency on the fg color and a 38% transparency on the background color gives me this result for dark and light themes.

I've found that the 'colored' disabled doesn't translate well to all the widgets. So perhaps a simple gray-ish color will work? Not decided yet.

python_EhSeK8ZMOE python_tOy4WUu0Ur
antrrax commented 2 years ago

In my opinion this second option with grayscale is better. As it is the default normally used in OS. It's easier for the user to understand

daniilS commented 2 years ago

I also think the second option is more intuitive. If you do want to have a difference between colours, I would significantly reduce the saturation in the disabled state.

israel-dryer commented 2 years ago

@antrrax, let me know if these updates fixed your issue. I believe they did, but would be nice to double-check.

antrrax commented 2 years ago

It has improved a lot. Thanks

buttons