'Can we add clickable text or button to terminal/console in python?

For example this is my function:

def get_sum(item1, item2):
  print(int(item1) + int(item2))

But I want to trigger that function when user clicks at a certain item in command prompt or any terminal.
Main question:-
Is it possible to run functions by clicking in terminal?

Extra
And I also saw VS-Code extension development output in terminal. when I run yo code it shows very nice thing. And user can select the things with the arrow keys and press enter to run it. So could this also be possible in python? enter image description here
Thank you!



Solution 1:[1]

edit: you can't trigger functions by clicking stuff in python, but i think you can in c++

I'm gonna be using the curses module, there is also a ported version for windows, but i'm not sure thats gonna work...

This code is a bit complicated

import curses

# define the menu function
def menu(title, classes, color='white'):
  # define the curses wrapper
  def character(stdscr,):
    attributes = {}
    # stuff i copied from the internet that i'll put in the right format later
    icol = {
      1:'red',
      2:'green',
      3:'yellow',
      4:'blue',
      5:'magenta',
      6:'cyan',
      7:'white'
    }
    # put the stuff in the right format
    col = {v: k for k, v in icol.items()}

    # declare the background color

    bc = curses.COLOR_BLACK

    # make the 'normal' format
    curses.init_pair(1, 7, bc)
    attributes['normal'] = curses.color_pair(1)


    # make the 'highlighted' format
    curses.init_pair(2, col[color], bc)
    attributes['highlighted'] = curses.color_pair(2)


    # handle the menu
    c = 0
    option = 0
    while c != 10:

        stdscr.erase() # clear the screen (you can erase this if you want)

        # add the title
        stdscr.addstr(f"{title}\n", curses.color_pair(1))

        # add the options
        for i in range(len(classes)):
            # handle the colors
            if i == option:
                attr = attributes['highlighted']
            else:
                attr = attributes['normal']
            
            # actually add the options

            stdscr.addstr(f'> ', attr)
            stdscr.addstr(f'{classes[i]}' + '\n', attr)
        c = stdscr.getch()

        # handle the arrow keys
        if c == curses.KEY_UP and option > 0:
            option -= 1
        elif c == curses.KEY_DOWN and option < len(classes) - 1:
            option += 1
    return option
  return curses.wrapper(character)

that was the menu function, now here's an example of how to use the function

print(f"output:", menu('TEST', ['this will return 0','this will return 1', 'this is just to show that you can do more options then just two'], 'blue'))

the syntax of the function goes like this

menu('title', ['options', 'optionssssssss'], 'color (optional)')

here are the available colors:

red
green
yellow
blue
magenta
cyan
white

the output of the example code looks like this:
gif

you can install the version for windows using this command: python -m pip install windows-curses

but i don't think it even works with python 3

also sorry if this post is bad, its my first answer on stackoverflow

Solution 2:[2]

this can help somebody

from tkinter import colorchooser,Tk,messagebox
from colored import fg,bg,attr
import win32console
import threading
import win32gui
import win32api
import colorama
import atexit
import mouse
import time
import sys
import os

class console:
    def __init__(self):
        self._is_enabled = False
        self._last_pos = (0,0)
        self.hwnd = win32console.GetConsoleWindow()
        atexit.register(lambda:self.set_input(True))
    def _mouse_thread(self):
        while True:
            if self.get_mouse_position()[1] > 30:
                if self.is_input():
                    self._set_input(False)
            else:
                if not self.is_input():
                    self._set_input(True)
    def _focus_thread(self):
        while True:
            if win32gui.GetForegroundWindow() != self.hwnd:
                win32gui.SetForegroundWindow(self.hwnd)
    def run_focus_thread(self):
        threading.Thread(target=self._focus_thread,daemon=1).start()
    def run_moveable_thread(self):
        threading.Thread(target=self._mouse_thread,daemon=1).start()
    def get_position(self):
        return win32gui.GetWindowRect(self.hwnd)[:2]
    def get_mouse_position(self):
        try:
            pos = win32gui.GetCursorPos()
            self._last_pos = pos
        except:
            pos = self._last_pos
        return (pos[0]-self.get_position()[0]-7,
                pos[1]-self.get_position()[1]-30)
    def _set_input(self,value):
        win32gui.EnableWindow(self.hwnd,value)
    def set_input(self,value):
        self._is_enabled = value
        win32gui.EnableWindow(self.hwnd,value)
    def is_input(self):
        return win32gui.IsWindowEnabled(self.hwnd)
    def set_focus(self):
        win32gui.SetForegroundWindow(self.hwnd)
        win32gui.SetFocus(self.hwnd)
    def is_focus(self):
        return win32gui.GetForegroundWindow() == self.hwnd and \
               self.get_mouse_position()[1] >= 30 and \
               self.get_mouse_position()[0] > 0 and \
               self.get_mouse_position()[0] <= self.get_size()[0]-7 and \
               self.get_mouse_position()[1] <= self.get_size()[1]
    def get_size(self):
        return (win32gui.GetWindowRect(self.hwnd)[2]-7,
                win32gui.GetWindowRect(self.hwnd)[3]-30)
    def set_size(self,w,h):
        win32gui.MoveWindow(self.hwnd,*self.get_position(),int(w),int(h),True)
    def set_position(self,x,y):
        win32gui.MoveWindow(self.hwnd,int(x),int(y),*self.get_size(),True)
    def set_rect(self,x,y,w,h):
        win32gui.MoveWindow(self.hwnd,int(x),int(y),int(w),int(h),True)
    def set_center(self):
        self.set_position(get_screen_size()[0]/2-self.get_size()[0]/2,
                          get_screen_size()[1]/2-self.get_size()[1]/2)
    def set_title(self,title):
        win32gui.SetWindowText(self.hwnd,title)
console = console()

def get_screen_size():
    return (win32api.GetSystemMetrics(0),
            win32api.GetSystemMetrics(1))

font_size = [8.2,15.5]

class canvas:
    def __init__(self,width,height):
        self.sp = (width*font_size[0],height*font_size[1])
        self.width, self.height = width, height
        self.changed = True
        self.pixels = [[" " for i in range(self.width)] for i in range(self.height)]
        console.set_size(self.sp[0]+30,self.sp[1]+10)
    def clear(self):
        # os.system("cls")
        print("\033[H\033[J",end="")
    def fill(self,symbol=" ",update=True):
        self.pixels = [[symbol for i in range(self.width)] for i in range(self.height)]
        self.changed = True
        if update: self.update()
    def update(self):
        self.changed = False
        out = ""
        for y in self.pixels:
            for x in y:
                out += x
            out += "\n"
        self.clear()
        print(out,end="")
    def set(self,x,y,symbol,update=True):
        if x < self.width and y < self.height:
            self.pixels[y][x] = symbol
            self.changed = True
            if update: self.update()
    def write(self,x,y,text,update=True):
        self.changed = True
        for n,s in enumerate(text):
            self.set(x+n,y,s,False)
        if update: self.update()
    def get(self,x,y):
        return self.pixels[y][x]
    def all(self):
        return self.pixels
    def get_mouse_position(self):
        return (console.get_mouse_position()[0]/self.sp[0]*self.width,
                (console.get_mouse_position()[1]/(self.sp[1]-font_size[1]*4)*self.height))

def color_symbol(color):
    return fg(color)+bg(color)+"#"+attr('reset')


# -------   create canvas   -------

console.set_title("hello world button")  # set console title
console.run_moveable_thread()  # run thread that prohibits clicking on the console
win = canvas(64,34)            # creating canvas


# -------   create buttons   -------

btns = []
def btn(text,x,y,cb):
    btns.append([text,x,y,cb,False])

def btncallback():
    Tk().withdraw()
    messagebox.showinfo("hello world","Hello World!")

btn("click me!",25,15,btncallback)


# -------   create main view   -------

for i in btns:
    win.set(i[1],i[2],i[0][0],False)
    win.write(i[1]+1,i[2],i[0][1:][:-1],False)
    win.set(i[1]+len(i[0])-1,i[2],i[0][-1],False)
win.update()


# -------   main loop   -------


run = True
while run:
    if win.changed:
        win.update()
    if console.is_focus():
        pos = win.get_mouse_position()
        pos = (round(pos[0]),round(pos[1]))
        if mouse.is_pressed("left"):
            for i in btns:
                if pos[0] >= i[1] and pos[0] <= i[1]+len(i[0]) \
                            and pos[1] == i[2]+1 and not i[4]:
                    win.set(i[1],i[2],fg("black")+bg("white")+i[0][0],False)
                    win.write(i[1]+1,i[2],i[0][1:][:-1],False)
                    win.set(i[1]+len(i[0])-1,i[2],i[0][-1]+attr("reset"),False)
                    i[4] = True
        else:
            for i in btns:
                if i[4]:
                    win.set(i[1],i[2],i[0][0],False)
                    win.write(i[1]+1,i[2],i[0][1:][:-1],False)
                    win.set(i[1]+len(i[0])-1,i[2],i[0][-1],False)
                    i[4] = False
                    i[3]()

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2