HSV colorPicker

Here's code for an HSV colorPicker.

Please feel free to improve

catch {namespace delete ::gColorNS}
namespace eval ::gColorNS {
    # Variables du namespace
    variable canvas_size 300
    variable radius [expr {$canvas_size / 2.1}]
    variable ring_thickness [expr {$radius / 8}]
    variable cx [expr {$canvas_size / 2}]
    variable cy [expr {$canvas_size / 2}]
    variable triangle_coords {}
    variable triangle_id
    variable hue_marker_id
    variable sv_marker_id
    variable cumulative_angle 0
    variable current_saturation 0.5
    variable current_value 0.5
    variable current_hue 0
    variable hue_marker_coords {}
    variable hue_ring_image
    variable active_tag ""
    variable canvas {}
    variable info_frame {}
    variable info_bg {}
    variable last_update_time 0
    variable update_threshold_ms 16
    variable pending_update_after_id 0

    # Fonctions de conversion de coordonnées et couleurs
    proc polar_to_cartesian {radius angle cx cy} {
        set radian [expr {$angle * acos(-1) / 180}]
        set x [expr {$cx + $radius * cos($radian)}]
        set y [expr {$cy - $radius * sin($radian)}]
        return [list [format "%.5f" $x] [format "%.5f" $y]]
    }

    proc hsv_to_rgb {h s v} {
        set h [expr {fmod($h, 360.0)}]
        set c [expr {$v * $s}]
        set x [expr {$c * (1 - abs(fmod($h / 60.0, 2) - 1))}]
        set m [expr {$v - $c}]

        if {$h < 60} {
            lassign [list $c $x 0] r g b
        } elseif {$h < 120} {
            lassign [list $x $c 0] r g b
        } elseif {$h < 180} {
            lassign [list 0 $c $x] r g b
        } elseif {$h < 240} {
            lassign [list 0 $x $c] r g b
        } elseif {$h < 300} {
            lassign [list $x 0 $c] r g b
        } else {
            lassign [list $c 0 $x] r g b
        }

        return [list [expr {round(($r + $m) * 255)}] [expr {round(($g + $m) * 255)}] [expr {round(($b + $m) * 255)}]]
    }

    proc rgb_to_hsv {r g b} {
        set r [expr {$r / 255.0}]
        set g [expr {$g / 255.0}]
        set b [expr {$b / 255.0}]
        
        set cmax [expr {max($r, max($g, $b))}]
        set cmin [expr {min($r, min($g, $b))}]
        set diff [expr {$cmax - $cmin}]
        
        if {abs($diff) < 0.00001} {
            set h 0
        } elseif {abs($cmax - $r) < 0.00001} {
            set h [expr {60 * fmod(($g - $b)/$diff, 6)}]
        } elseif {abs($cmax - $g) < 0.00001} {
            set h [expr {60 * (($b - $r)/$diff + 2)}]
        } else {
            set h [expr {60 * (($r - $g)/$diff + 4)}]
        }
        
        if {$h < 0} {
            set h [expr {$h + 360}]
        }
        
        set s [expr {$cmax == 0 ? 0 : $diff/$cmax}]
        return [list $h $s $cmax]
    }

#geometry functions
    proc point_in_triangle {px py t1 t2 t3} {
        lassign $t1 x1 y1
        lassign $t2 x2 y2
        lassign $t3 x3 y3

        set denom [expr {($y2 - $y3)*($x1 - $x3) + ($x3 - $x2)*($y1 - $y3)}]
        if {$denom == 0} {return 0}

        set a [expr {(($y2 - $y3)*($px - $x3) + ($x3 - $x2)*($py - $y3)) / $denom}]
        set b [expr {(($y3 - $y1)*($px - $x3) + ($x1 - $x3)*($py - $y3)) / $denom}]
        set c [expr {1.0 - $a - $b}]

        return [expr {$a >= 0 && $b >= 0 && $c >= 0}]
    }

    proc calculate_edge_distance {px py t1 t2 t3} {
        set edges [list [list $t1 $t2] [list $t2 $t3] [list $t3 $t1]]
        set min_distance Inf
        
        foreach edge $edges {
            lassign $edge p1 p2
            lassign $p1 x1 y1
            lassign $p2 x2 y2
            
            set A [expr {$y2 - $y1}]
            set B [expr {$x1 - $x2}]
            set C [expr {$x2 * $y1 - $x1 * $y2}]
            
            set distance [expr {abs($A * $px + $B * $py + $C) / hypot($A, $B)}]
            if {$distance < $min_distance} {
                set min_distance $distance
            }
        }
        
        return [expr {[point_in_triangle $px $py $t1 $t2 $t3] ? -$min_distance : $min_distance}]
    }

    proc project_point_to_edge {px py p1 p2} {
        lassign $p1 x1 y1
        lassign $p2 x2 y2

        set dx [expr {$x2 - $x1}]
        set dy [expr {$y2 - $y1}]

        if {$dx == 0 && $dy == 0} {return $p1}

        set t [expr {((($px - $x1) * $dx) + (($py - $y1) * $dy)) / (($dx * $dx) + ($dy * $dy))}]
        set t [expr {max(0, min(1, $t))}]

        return [list [expr {$x1 + $t * $dx}] [expr {$y1 + $t * $dy}]]
    }

    proc constrain_to_triangle {px py t1 t2 t3} {
        if {[point_in_triangle $px $py $t1 $t2 $t3]} {
            return [list $px $py]
        }

        set edges [list [list $t1 $t2] [list $t2 $t3] [list $t3 $t1]]
        set min_dist 1e9
        set closest_point {}

        foreach edge $edges {
            lassign $edge p1 p2
            set proj [project_point_to_edge $px $py $p1 $p2]
            set dist [expr {hypot([lindex $proj 0] - $px, [lindex $proj 1] - $py)}]

            if {$dist < $min_dist} {
                set min_dist $dist
                set closest_point $proj
            }
        }

        return $closest_point
    }

    proc calculate_saturation {px py t1 t2 t3} {
        lassign $t1 x1 y1
        lassign $t2 x2 y2
        lassign $t3 x3 y3

        set denom [expr {($y2 - $y3)*($x1 - $x3) + ($x3 - $x2)*($y1 - $y3)}]
        if {$denom == 0} {return 0.0}

        set alpha [expr {(($y2 - $y3)*($px - $x3) + ($x3 - $x2)*($py - $y3)) / $denom}]
        return [expr {max(0.0, min(1.0, $alpha))}]
    }

    proc calculate_value {px py t1 t2 t3} {
        lassign $t1 x1 y1
        lassign $t2 x2 y2
        lassign $t3 x3 y3

        set denom [expr {($y2 - $y3)*($x1 - $x3) + ($x3 - $x2)*($y1 - $y3)}]
        if {$denom == 0} {return 0.0}

        set alpha [expr {(($y2 - $y3)*($px - $x3) + ($x3 - $x2)*($py - $y3)) / $denom}]
        set beta [expr {(($y3 - $y1)*($px - $x3) + ($x1 - $x3)*($py - $y3)) / $denom}]
        set gamma [expr {1.0 - $alpha - $beta}]

        return [expr {max(0.0, min(1.0, 1.0 - $gamma))}]
    }
    
#UI drawings
    proc point_in_ring {x y} {
        variable cx
        variable cy
        variable radius
        variable ring_thickness
        
        set dx [expr {$x - $cx}]
        set dy [expr {$y - $cy}]
        set distance [expr {hypot($dx, $dy)}]
        
        return [expr {$distance <= $radius && $distance >= ($radius - $ring_thickness)}]
    }


proc create_hue_ring_image {} {
    variable canvas_size
    variable radius
    variable ring_thickness
    variable cx
    variable cy
    variable info_bg
    variable canvas
    
    set img [image create photo -width $canvas_size -height $canvas_size]
    
    # Convertir la couleur de fond en valeurs RGB
    lassign [winfo rgb . $info_bg] bg_r bg_g bg_b
    set bg_r [expr {$bg_r / 256}]
    set bg_g [expr {$bg_g / 256}]
    set bg_b [expr {$bg_b / 256}]
    
    set inner_radius [expr {$radius - $ring_thickness}]
    set outer_radius $radius
    
    for {set y 0} {$y < $canvas_size} {incr y} {
        set row {}
        for {set x 0} {$x < $canvas_size} {incr x} {
            set dx [expr {$x - $cx}]
            set dy [expr {$y - $cy}]
            set distance [expr {hypot($dx, $dy)}]
            
            if {$distance <= $outer_radius && $distance >= $inner_radius} {
                set angle [expr {atan2(-$dy, $dx) * 180 / acos(-1)}]
                if {$angle < 0} {
                    set angle [expr {$angle + 360}]
                }
                lassign [hsv_to_rgb $angle 1.0 1.0] r g b
                
                # Calculer l'alpha pour le lissage des bords
                set alpha 1.0
                if {$distance > $outer_radius - 1.5 || $distance < $inner_radius + 1.5} {
                    if {$distance > $outer_radius - 1.5} {
                        set alpha [expr {1.0 - ($distance - ($outer_radius - 1.5)) / 1.5}]
                    } else {
                        set alpha [expr {($distance - $inner_radius) / 1.5}]
                    }
                }
                
                # Mélanger avec la couleur de fond
                set r [expr {round($alpha * $r + (1.0 - $alpha) * $bg_r)}]
                set g [expr {round($alpha * $g + (1.0 - $alpha) * $bg_g)}]
                set b [expr {round($alpha * $b + (1.0 - $alpha) * $bg_b)}]
                
                lappend row [format "#%02x%02x%02x" $r $g $b]
            } else {
                lappend row "#FFFFFF00"
            }
        }
        $img put [list [join $row " "]] -to 0 $y
    }
    
    $canvas create image 0 0 -anchor nw -image $img -tags hue_ring
}

proc fill_triangle {hue} {
    variable triangle_coords
    variable sv_marker_id
    variable current_saturation
    variable current_value
    variable canvas
    variable info_bg

    set t1 [lrange $triangle_coords 0 1]
    set t2 [lrange $triangle_coords 2 3]
    set t3 [lrange $triangle_coords 4 5]

    foreach item [$canvas find withtag triangle_fill] {
        $canvas delete $item
    }

    set xs [list [lindex $t1 0] [lindex $t2 0] [lindex $t3 0]]
    set ys [list [lindex $t1 1] [lindex $t2 1] [lindex $t3 1]]

    set minX [expr {int(floor([lindex [lsort -real $xs] 0]))}]
    set maxX [expr {int(ceil([lindex [lsort -real $xs] end]))}]
    set minY [expr {int(floor([lindex [lsort -real $ys] 0]))}]
    set maxY [expr {int(ceil([lindex [lsort -real $ys] end]))}]

    set width [expr {$maxX - $minX}]
    set height [expr {$maxY - $minY}]

    if {$width <= 0 || $height <= 0} {return}

    set triangleImage [image create photo -width $width -height $height]

   for {set j 0} {$j < $height} {incr j} {
        set pixelRow {}
        for {set i 0} {$i < $width} {incr i} {
            set px [expr {$minX + $i}]
            set py [expr {$minY + $j}]

            set distance_to_edge [calculate_edge_distance $px $py $t1 $t2 $t3]
            
            if {$distance_to_edge <= 1.5} {
                set alpha [expr {$distance_to_edge <= 0 ? 1.0 : 1.0 - ($distance_to_edge / 1.5)}]
                
                set s [calculate_saturation $px $py $t1 $t2 $t3]
                set v [calculate_value $px $py $t1 $t2 $t3]
                lassign [hsv_to_rgb $hue $s $v] r g b
                
                # Utiliser l'alpha pour mélanger avec la couleur de fond
                lassign [winfo rgb $canvas $info_bg] bg_r bg_g bg_b
                set bg_r [expr {$bg_r / 256}]
                set bg_g [expr {$bg_g / 256}]
                set bg_b [expr {$bg_b / 256}]
                
                set r [expr {round($alpha * $r + (1.0 - $alpha) * $bg_r)}]
                set g [expr {round($alpha * $g + (1.0 - $alpha) * $bg_g)}]
                set b [expr {round($alpha * $b + (1.0 - $alpha) * $bg_b)}]
                
                lappend pixelRow [format "#%02x%02x%02x" $r $g $b]
            } else {
                lappend pixelRow $info_bg
            }
        }
        $triangleImage put [list [join $pixelRow " "]] -to 0 $j
    }
    
    $canvas create image $minX $minY -anchor nw -image $triangleImage -tags triangle_fill
    $canvas raise hue_ring
    $canvas raise hue_marker

    if {$sv_marker_id ne ""} {
        $canvas delete $sv_marker_id
    }

    set marker_x [expr {
        [lindex $t1 0] * ($current_saturation * $current_value)
      + [lindex $t2 0] * ($current_value * (1 - $current_saturation))
      + [lindex $t3 0] * (1 - $current_value)
    }]
    set marker_y [expr {
        [lindex $t1 1] * ($current_saturation * $current_value)
      + [lindex $t2 1] * ($current_value * (1 - $current_saturation))
      + [lindex $t3 1] * (1 - $current_value)
    }]

    set marker_x [expr {int(round($marker_x))}]
    set marker_y [expr {int(round($marker_y))}]

    set sv_marker_id [$canvas create oval \
        [expr {$marker_x - 5}] [expr {$marker_y - 5}] \
        [expr {$marker_x + 5}] [expr {$marker_y + 5}] \
        -fill "black" -outline "white" \
        -tags sv_marker
    ]
}

#color management
proc set_color_from_hex {hex_color} {
        variable current_hue
        variable current_saturation
        variable current_value
        variable triangle_coords
        variable hue_marker_id
        variable sv_marker_id
        variable radius
        variable ring_thickness
        variable cx
        variable cy
        variable cumulative_angle
        variable canvas
        
        if {[string length $hex_color] == 7 && [string index $hex_color 0] eq "#"} {
            scan [string range $hex_color 1 end] "%2x%2x%2x" r g b
        } else {
            error "Format de couleur invalide. Utilisez le format #RRGGBB"
        }
        
        lassign [rgb_to_hsv $r $g $b] h s v
        
        set current_hue $h
        set current_saturation $s
        set current_value $v
        set cumulative_angle $h
        
        # Calcul de l'angle pour la rotation du triangle
        set current_angle [expr {atan2(-([lindex $triangle_coords 1] - $cy),
                               [lindex $triangle_coords 0] - $cx) * 180 / acos(-1)}]
        if {$current_angle < 0} {
            set current_angle [expr {$current_angle + 360}]
        }
        
        set angle_diff [expr {$h - $current_angle}]
        if {$angle_diff > 180} {
            set angle_diff [expr {$angle_diff - 360}]
        } elseif {$angle_diff < -180} {
            set angle_diff [expr {$angle_diff + 360}]
        }
        
        rotate_triangle $angle_diff 0

        set inner_pt [polar_to_cartesian [expr {$radius - $ring_thickness}] $h $cx $cy]
        set outer_pt [polar_to_cartesian $radius $h $cx $cy]
        $canvas coords $hue_marker_id {*}$inner_pt {*}$outer_pt
        
        set t1 [lrange $triangle_coords 0 1]
        set t2 [lrange $triangle_coords 2 3]
        set t3 [lrange $triangle_coords 4 5]
        
        set marker_x [expr {
            [lindex $t1 0] * ($s * $v)
          + [lindex $t2 0] * ($v * (1 - $s))
          + [lindex $t3 0] * (1 - $v)
        }]
        set marker_y [expr {
            [lindex $t1 1] * ($s * $v)
          + [lindex $t2 1] * ($v * (1 - $s))
          + [lindex $t3 1] * (1 - $v)
        }]
        
        $canvas coords $sv_marker_id \
            [expr {$marker_x - 5}] [expr {$marker_y - 5}] \
            [expr {$marker_x + 5}] [expr {$marker_y + 5}]
            
        fill_triangle $h
        update_color_display
    }

    # Procédures de contrôle HSV
    proc increment_hue {step} {
        variable current_hue
        variable cumulative_angle
        variable radius
        variable ring_thickness
        variable cx
        variable cy
        variable hue_marker_id
        variable canvas
        
        set new_hue [expr {$current_hue + $step}]
        if {$new_hue >= 360} {
            set new_hue [expr {$new_hue - 360}]
        }
        
        set angle_diff [expr {$new_hue - $current_hue}]
        set cumulative_angle $new_hue
        
        # Mise à jour du marqueur de teinte
        set inner_pt [polar_to_cartesian [expr {$radius - $ring_thickness}] $new_hue $cx $cy]
        set outer_pt [polar_to_cartesian $radius $new_hue $cx $cy]
        $canvas coords $hue_marker_id {*}$inner_pt {*}$outer_pt
        
        rotate_triangle $angle_diff 1
        update_color_display
    }

    proc decrement_hue {step} {
        variable current_hue
        variable cumulative_angle
        variable radius
        variable ring_thickness
        variable cx
        variable cy
        variable hue_marker_id
        variable canvas

        set new_hue [expr {$current_hue - $step}]
        if {$new_hue < 0} {
            set new_hue [expr {$new_hue + 360}]
        }
        
        set angle_diff [expr {$new_hue - $current_hue}]
        set cumulative_angle $new_hue
        
        # Mise à jour du marqueur de teinte
        set inner_pt [polar_to_cartesian [expr {$radius - $ring_thickness}] $new_hue $cx $cy]
        set outer_pt [polar_to_cartesian $radius $new_hue $cx $cy]
        $canvas coords $hue_marker_id {*}$inner_pt {*}$outer_pt
        
        rotate_triangle $angle_diff 1
        update_color_display
    }

    proc increment_saturation {step} {
        variable current_saturation
        variable current_value
        variable current_hue
        variable triangle_coords
        variable sv_marker_id
        variable canvas

        set new_saturation [expr {min(1.0, $current_saturation + $step)}]
        if {$new_saturation != $current_saturation} {
            set current_saturation $new_saturation
            
            # Mise à jour de la position du marqueur SV
            set t1 [lrange $triangle_coords 0 1]
            set t2 [lrange $triangle_coords 2 3]
            set t3 [lrange $triangle_coords 4 5]
            
            set marker_x [expr {
                [lindex $t1 0] * ($current_saturation * $current_value)
              + [lindex $t2 0] * ($current_value * (1 - $current_saturation))
              + [lindex $t3 0] * (1 - $current_value)
            }]
            set marker_y [expr {
                [lindex $t1 1] * ($current_saturation * $current_value)
              + [lindex $t2 1] * ($current_value * (1 - $current_saturation))
              + [lindex $t3 1] * (1 - $current_value)
            }]
            
            $canvas coords $sv_marker_id \
                [expr {$marker_x - 5}] [expr {$marker_y - 5}] \
                [expr {$marker_x + 5}] [expr {$marker_y + 5}]
            
            fill_triangle $current_hue
            update_color_display
        }
    }

    proc decrement_saturation {step} {
        variable current_saturation
        variable current_value
        variable current_hue
        variable triangle_coords
        variable sv_marker_id
        variable canvas

        set new_saturation [expr {max(0.0, $current_saturation - $step)}]
        if {$new_saturation != $current_saturation} {
            set current_saturation $new_saturation
            
            # Mise à jour de la position du marqueur SV
            set t1 [lrange $triangle_coords 0 1]
            set t2 [lrange $triangle_coords 2 3]
            set t3 [lrange $triangle_coords 4 5]
            
            set marker_x [expr {
                [lindex $t1 0] * ($current_saturation * $current_value)
              + [lindex $t2 0] * ($current_value * (1 - $current_saturation))
              + [lindex $t3 0] * (1 - $current_value)
            }]
            set marker_y [expr {
                [lindex $t1 1] * ($current_saturation * $current_value)
              + [lindex $t2 1] * ($current_value * (1 - $current_saturation))
              + [lindex $t3 1] * (1 - $current_value)
            }]
            
            $canvas coords $sv_marker_id \
                [expr {$marker_x - 5}] [expr {$marker_y - 5}] \
                [expr {$marker_x + 5}] [expr {$marker_y + 5}]
            
            fill_triangle $current_hue
            update_color_display
        }
    }

    proc increment_value {step} {
        variable current_value
        variable current_saturation
        variable current_hue
        variable triangle_coords
        variable sv_marker_id
        variable canvas

        set new_value [expr {min(1.0, $current_value + $step)}]
        if {$new_value != $current_value} {
            set current_value $new_value
            
            # Mise à jour de la position du marqueur SV
            set t1 [lrange $triangle_coords 0 1]
            set t2 [lrange $triangle_coords 2 3]
            set t3 [lrange $triangle_coords 4 5]
            
            set marker_x [expr {
                [lindex $t1 0] * ($current_saturation * $current_value)
              + [lindex $t2 0] * ($current_value * (1 - $current_saturation))
              + [lindex $t3 0] * (1 - $current_value)
            }]
            set marker_y [expr {
                [lindex $t1 1] * ($current_saturation * $current_value)
              + [lindex $t2 1] * ($current_value * (1 - $current_saturation))
              + [lindex $t3 1] * (1 - $current_value)
            }]
            
            $canvas coords $sv_marker_id \
                [expr {$marker_x - 5}] [expr {$marker_y - 5}] \
                [expr {$marker_x + 5}] [expr {$marker_y + 5}]
            
            fill_triangle $current_hue
            update_color_display
        }
    }

    proc decrement_value {step} {
        variable current_value
        variable current_saturation
        variable current_hue
        variable triangle_coords
        variable sv_marker_id
        variable canvas

        set new_value [expr {max(0.0, $current_value - $step)}]
        if {$new_value != $current_value} {
            set current_value $new_value
            
            # Mise à jour de la position du marqueur SV
            set t1 [lrange $triangle_coords 0 1]
            set t2 [lrange $triangle_coords 2 3]
            set t3 [lrange $triangle_coords 4 5]
            
            set marker_x [expr {
                [lindex $t1 0] * ($current_saturation * $current_value)
              + [lindex $t2 0] * ($current_value * (1 - $current_saturation))
              + [lindex $t3 0] * (1 - $current_value)
            }]
            set marker_y [expr {
                [lindex $t1 1] * ($current_saturation * $current_value)
              + [lindex $t2 1] * ($current_value * (1 - $current_saturation))
              + [lindex $t3 1] * (1 - $current_value)
            }]
            
            $canvas coords $sv_marker_id \
                [expr {$marker_x - 5}] [expr {$marker_y - 5}] \
                [expr {$marker_x + 5}] [expr {$marker_y + 5}]
            
            fill_triangle $current_hue
            update_color_display
        }
    }

    # Procédure pour avoir les valeurs HSV courantes
    proc get_current_hsv {} {
        variable current_hue
        variable current_saturation
        variable current_value
        
        return [list $current_hue $current_saturation $current_value]
    }

    proc rotate_triangle {angle {update_hue 0}} {
        variable triangle_coords
        variable cx
        variable cy
        variable current_hue
        variable sv_marker_id
        variable hue_marker_coords
        variable triangle_id
        variable canvas

        set rotated {}
        for {set i 0} {$i < [llength $triangle_coords]} {incr i 2} {
            set x [lindex $triangle_coords $i]
            set y [lindex $triangle_coords [expr {$i + 1}]]
            set dx [expr {$x - $cx}]
            set dy [expr {$y - $cy}]
            set distance [expr {hypot($dx, $dy)}]
            set current_angle [expr {atan2(-$dy, $dx) * 180 / acos(-1)}]
            set new_angle [expr {$current_angle + $angle}]
            if {$new_angle < 0} {
                set new_angle [expr {$new_angle + 360}]
            } elseif {$new_angle >= 360} {
                set new_angle [expr {$new_angle - 360}]
            }
            lappend rotated {*}[polar_to_cartesian $distance $new_angle $cx $cy]
        }
        
        set triangle_coords $rotated
        $canvas coords $triangle_id {*}$rotated

        if {$update_hue} {
            set current_hue [expr {$current_hue + $angle}]
            if {$current_hue < 0} {
                set current_hue [expr {$current_hue + 360}]
            } elseif {$current_hue >= 360} {
                set current_hue [expr {$current_hue - 360}]
            }
        }

        set t_h [lrange $triangle_coords 0 1]
        set hue_marker_coords $t_h

        if {$sv_marker_id ne ""} {
            set sv_coords [$canvas coords $sv_marker_id]
            set sv_x [expr {([lindex $sv_coords 0] + [lindex $sv_coords 2]) / 2}]
            set sv_y [expr {([lindex $sv_coords 1] + [lindex $sv_coords 3]) / 2}]

            set dx [expr {$sv_x - $cx}]
            set dy [expr {$sv_y - $cy}]
            set distance [expr {hypot($dx, $dy)}]
            set current_angle [expr {atan2(-$dy, $dx) * 180 / acos(-1)}]
            set new_angle [expr {$current_angle + $angle}]
            if {$new_angle < 0} {
                set new_angle [expr {$new_angle + 360}]
            } elseif {$new_angle >= 360} {
                set new_angle [expr {$new_angle - 360}]
            }
            
            set new_coords [polar_to_cartesian $distance $new_angle $cx $cy]
            $canvas coords $sv_marker_id \
                [expr {[lindex $new_coords 0] - 5}] \
                [expr {[lindex $new_coords 1] - 5}] \
                [expr {[lindex $new_coords 0] + 5}] \
                [expr {[lindex $new_coords 1] + 5}]
        }

        fill_triangle $current_hue
    }
    
#ui update
    proc throttle_update_hue_marker {x y} {
        variable last_update_time
        variable update_threshold_ms

        set current_time [clock milliseconds]
        if {![info exists last_update_time] || \
            ($current_time - $last_update_time) >= $update_threshold_ms} {
            update_hue_marker $x $y
            set last_update_time $current_time
        }
    }

    proc update_hue_marker {x y} {
        variable cx
        variable cy
        variable radius
        variable ring_thickness
        variable hue_marker_id
        variable current_hue
        variable cumulative_angle
        variable canvas
        variable pending_update_after_id

        # Mettre à jour uniquement le marqueur de teinte immédiatement
        set dx [expr {$x - $cx}]
        set dy [expr {$y - $cy}]
        set new_angle [expr {atan2(-$dy, $dx) * 180 / acos(-1)}]
        if {$new_angle < 0} {
            set new_angle [expr {$new_angle + 360}]
        }
        
        set inner_pt [polar_to_cartesian [expr {$radius - $ring_thickness}] $new_angle $cx $cy]
        set outer_pt [polar_to_cartesian $radius $new_angle $cx $cy]
        $canvas coords $hue_marker_id {*}$inner_pt {*}$outer_pt

        # Annuler toute mise à jour en attente
        if {[info exists pending_update_after_id]} {
            after cancel $pending_update_after_id
        }

        # Programmer la mise à jour complète après un délai
        set pending_update_after_id [after 16 [list ::gColorNS::complete_hue_update $new_angle $cumulative_angle]]
    }

    proc complete_hue_update {new_angle old_angle} {
        variable cumulative_angle
        
        set delta_angle [expr {$new_angle - $old_angle}]
        if {$delta_angle > 180} {
            set delta_angle [expr {$delta_angle - 360}]
        } elseif {$delta_angle < -180} {
            set delta_angle [expr {$delta_angle + 360}]
        }
        
        set cumulative_angle $new_angle
        
        rotate_triangle $delta_angle 1
        update_color_display
    }

    
   proc update_color_display {} {
        variable current_hue
        variable current_saturation
        variable current_value
        variable info_frame

        set rgb [hsv_to_rgb $current_hue $current_saturation $current_value]
        set hex [format "#%02x%02x%02x" {*}$rgb]

        $info_frame.color_display configure -bg $hex
        $info_frame.label_rgb configure -text "RGB: [join $rgb {, }]"
        $info_frame.label_hex configure -text "HEX: $hex"
        
    }



    proc update_sv_marker {x y} {
        variable triangle_coords
        variable sv_marker_id
        variable current_saturation
        variable current_value
        variable canvas

        set t1 [lrange $triangle_coords 0 1]
        set t2 [lrange $triangle_coords 2 3]
        set t3 [lrange $triangle_coords 4 5]
        
        set constrained_point [constrain_to_triangle $x $y $t1 $t2 $t3]
        set px [lindex $constrained_point 0]
        set py [lindex $constrained_point 1]
        
        $canvas coords $sv_marker_id \
            [expr {$px - 5}] [expr {$py - 5}] \
            [expr {$px + 5}] [expr {$py + 5}]
        
        set current_saturation [calculate_saturation $px $py $t1 $t2 $t3]
        set current_value [calculate_value $px $py $t1 $t2 $t3]
        
        update_color_display
    }
    
   # Procédure principale de configuration de l'interface utilisateur
    proc setup_ui {parent_frame} {
        variable canvas_size
        variable radius
        variable ring_thickness
        variable cx
        variable cy
        variable triangle_coords
        variable triangle_id
        variable hue_marker_id
        variable sv_marker_id
        variable current_hue
        variable current_value
        variable current_saturation
        variable canvas
        variable info_frame
        variable info_bg
        variable cumulative_angle
        
        set main [ttk::frame $parent_frame.m]
        set info [ttk::frame $parent_frame.i]
        set info_frame $info

        set canvas $main.canvas
        
            set info_bg white

        canvas $canvas -width $canvas_size -height $canvas_size -bg $info_bg -bd 0 -highlightthickness 0
        
        if {[tk windowingsystem] in {aqua win32}} {
            label $info.color_display -width 14 -height 3 -bg "#ffffff"
        } else {
            label $info.color_display -width 16 -height 2 -bg "#ffffff"
        }
        label $info.label_rgb -text "RGB: " -anchor w
        label $info.label_hex -text "HEX: " -anchor w


        # Frame pour les contrôles HSV
        ttk::frame $info.hsv_controls
         # Contrôles pour H, S, V
        foreach {component label} {hue H saturation S value V} {
            ttk::frame $info.hsv_controls.$component
            ttk::label $info.hsv_controls.$component.label -text "$label:"
            ttk::button $info.hsv_controls.$component.minus -command [list ::gColorNS::decrement_$component [expr {$component eq "hue" ? 1 : 0.01}]] -text "-"
            ttk::button $info.hsv_controls.$component.plus -command [list ::gColorNS::increment_$component [expr {$component eq "hue" ? 1 : 0.01}]] -text "+"
            pack $info.hsv_controls.$component -side top -fill x
            pack $info.hsv_controls.$component.label -side left
            pack $info.hsv_controls.$component.minus -side left -padx 3
            pack $info.hsv_controls.$component.plus -side left -padx 3
        }

        set current_hue 0
        set cumulative_angle 0
        
        create_hue_ring_image

        set inner_radius [expr {$radius - $ring_thickness * 1.5}]
        set t1 [polar_to_cartesian $inner_radius 0 $cx $cy]
        set t2 [polar_to_cartesian $inner_radius 120 $cx $cy]
        set t3 [polar_to_cartesian $inner_radius -120 $cx $cy]

        set triangle_coords [list {*}$t1 {*}$t2 {*}$t3]
        set triangle_id [$canvas create polygon $triangle_coords -outline $info_bg -tags triangle]

        set inner_coords [polar_to_cartesian [expr {$radius - $ring_thickness}] 0 $cx $cy]
        set outer_coords [polar_to_cartesian $radius 0 $cx $cy]
        set hue_marker_id [$canvas create line \
            [lindex $inner_coords 0] [lindex $inner_coords 1] \
            [lindex $outer_coords 0] [lindex $outer_coords 1] \
            -fill black -width 2 -tags hue_marker]

        set sv_x [expr {([lindex $t1 0] + [lindex $t2 0] + [lindex $t3 0]) / 3}]
        set sv_y [expr {([lindex $t1 1] + [lindex $t2 1] + [lindex $t3 1]) / 3}]
        set marker_size [expr {$ring_thickness / 2}]
        set sv_marker_id [$canvas create oval \
            [expr {$sv_x - $marker_size}] [expr {$sv_y - $marker_size}] \
            [expr {$sv_x + $marker_size}] [expr {$sv_y + $marker_size}] \
            -fill black -outline white -tags sv_marker]
    
        bind $canvas <Button-1> {
            set x %x
            set y %y
            
            if {[::gColorNS::point_in_ring $x $y]} {
                set ::gColorNS::active_tag "hue_ring"
                ::gColorNS::update_hue_marker $x $y
            } elseif {[::gColorNS::point_in_triangle $x $y \
                [lrange $::gColorNS::triangle_coords 0 1] \
                [lrange $::gColorNS::triangle_coords 2 3] \
                [lrange $::gColorNS::triangle_coords 4 5]]} {
                set ::gColorNS::active_tag "sv_area"
                ::gColorNS::update_sv_marker $x $y
            }
        }

        bind $canvas <B1-Motion> {
            set x %x
            set y %y
            
            if {$::gColorNS::active_tag eq "hue_ring"} {
                ::gColorNS::throttle_update_hue_marker $x $y
            } elseif {$::gColorNS::active_tag eq "sv_area"} {
                ::gColorNS::update_sv_marker $x $y
            }
        }

        bind $canvas <ButtonRelease-1> {
            set ::gColorNS::active_tag ""
        }

        fill_triangle $current_hue
        
        grid $canvas
        
        grid $info.label_rgb -sticky ew -padx 10
        grid $info.label_hex -sticky ew -padx 10 -pady 5
        grid $info.hsv_controls -sticky ew -padx 10
        grid $info.color_display -sticky news -padx 10 -pady 10

        grid $main $info
        grid configure $main -sticky news
        grid configure $info -sticky ew
        grid rowconfigure $parent_frame all -weight 1
        grid columnconfigure $parent_frame all -weight 1

    }
}

ttk::frame .f
::gColorNS::setup_ui .f
grid .f -sticky news
grid rowconfigure .f all -weight 1
grid columnconfigure .f all -weight 1

TWu 2025-01-21 - Preview from nico [L1 ]
Change one line above (Hex-color from RGBA to RGB), see comment.
On ActiveTcl 8.6 on Windows 10 no triangle is shown, sometimes it appears for a brief moment on debugging with Tcl Dev Kit.

nico 2025-01-21: mmmm with Tk9 #FFFFFF00 is working fine on macOS, Linux && Windows

TWu 2025-01-22 - Thanks for checking and no problem to roll back, You are welcome. Up from version 8.7 and 9.0 RGBA it is just fine.
Any ideas, why the triangle isn't shown for version 8.6?

nico 2025-01-22: isn't it under stuff? maybe a raise call could show it up...