Home > OS >  Tkinter Text Widget: Multiline Blinking Caret For Box-Select
Tkinter Text Widget: Multiline Blinking Caret For Box-Select

Time:07-01

I've created a box-select feature for tk.Text. The widget gets the font height and concocts an .xbm image from it. The .xbm is used as a faux-caret, via image_create, for all selected lines except the line the real caret is on.

How do I make the faux-caret image instance(s) blink in time with the real caret?

or

What is another direction I can go to get these results?

CodePudding user response:

My solution was to get rid of the real caret visibly, draw a faux caret on every line, and then call a function that keeps swapping out an 'on' faux-caret with an 'off' faux-caret. Below are some relevant code snippets. Note: I actually have 3 faux-carets because I want the caret that is on the true active line to be a little darker.

    #FAUX-CARET
    def __loadcarets(self) -> None:
        #store insert on/off times for faux-caret `.after` calls
        self.__instime = (self['insertofftime'], self['insertontime'])
        
        fh = self.__fh    #font height from 'linespace'
        
        #make a temp xbm file
        with tempfile.NamedTemporaryFile(mode='w b', suffix='.xbm', delete=False) as f:
            #create prettyprint xbm data
            xbmdata = ',\n\t'.join(','.join('0xFF' for _ in range(min(8, fh-(8*i)))) for i in range(math.ceil(fh/8)))
            #write xbm
            f.write((f"#define image_width {INSWIDTH}\n#define image_height {fh}\n"
                     "static unsigned char image_bits[] = {\n\t"
                     f'{xbmdata}}};').encode())
                     
        #load xbm files for faux-caret ~ they have to be in this order            
        self.__fc  = (tk.BitmapImage(file=f.name, foreground='#999999'),                   #shadow caret 
                      tk.BitmapImage(file=f.name, foreground=self['background']),          #off caret
                      tk.BitmapImage(file=f.name, foreground=self['insertbackground']))    #main caret  
        
        #delete file
        os.unlink(f.name)
    
    #faux-caret create or config
    def __fauxcaret(self, index:str, on:bool=True, main:bool=False, cfg:bool=False) -> None:
        (self.image_create, self.image_configure)[cfg](index, image=self.__fc[(main<<on)|(on^1)])
    
    #blink the faux-caret(s)
    def __blink(self, on:bool=True):
        #nothing to do
        if not self.__boxselect: return
        
        #sort image indexes
        if idx:=sorted((self.index(n) for n in self.image_names()), key=lambda i: float(i)):
            #flip `on`
            on = not on
            #consider direction in forward perspective
            fw = not self.__lbounds.rv
            #reconfigure all carets
            for i in idx: self.__fauxcaret(i, on=on, cfg=True)
            #reconfigure "active line" caret, if off it will assign off again
            self.__fauxcaret(idx[-fw], on=on, main=True, cfg=True)
            #schedule next call
            self.after(self.__instime[on], self.__blink, on)
  • Related