OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / ui_framework / form_ui / elements / button.py

387 lines · python ref:
import re
import bpy
from ....utility.screen import dpi_factor
from ... graphics.draw import render_text, draw_border_lines, render_quad, render_image
from ... graphics.load import load_image_file
from ... utils.geo import get_blf_text_dims
from ... utils.checks import is_mouse_in_quad
from . import Dims, Tips, clear_img_group, image_group


'''
obj / attr
    - Use this for simple boolean types
attr_index
    - if the item in the list is a (BOOL) use this to specify the slot to get / set
'''


class Button:
    def __init__(self,
        text="", shift_text="", highlight_text="", ctrl_text="", alt_text="",
        width=0, height=0, use_padding=True,
        tips=None, tip_size=12,
        font_size=12, font_primary=True,
        scroll_enabled=True,
        img="", glob_img_key=None, glob_img_key_update_func=None, glob_img_key_update_args=None,
        shift_img="",
        callback=None, pos_args=None, neg_args=None,
        alt_callback=None, alt_args=None,
        ctrl_callback=None, ctrl_args=None,
        highlight_hook=None, highlight_hook_args=None, highlight_hook_obj=None, highlight_hook_attr=None,
        shift_ctrl_callback=None, shift_ctrl_args=None,
        popup=None, popup_modifier_key='',
        obj=None, attr="", attr_index=None):
        
        self.dims = Dims()
        self.text = text
        self.shift_text = shift_text
        self.highlight_text = highlight_text
        self.alt_text = alt_text
        self.width = width * dpi_factor(min=0.5)
        self.height = height * dpi_factor(min=0.5)
        self.font_primary = font_primary
        self.font_size = font_size
        self.scroll_enabled = scroll_enabled
        self.img = img
        self.shift_img_name = shift_img
        self.shift_img = None
        self.loaded_img = None
        self.glob_img_key = glob_img_key
        self.glob_img_key_update_func = glob_img_key_update_func
        self.glob_img_key_update_args = glob_img_key_update_args
        self.callback = callback
        self.pos_args = pos_args
        self.neg_args = neg_args
        self.alt_callback = alt_callback
        self.alt_args = alt_args
        self.ctrl_callback = ctrl_callback
        self.ctrl_args = ctrl_args
        self.ctrl_text = ctrl_text
        self.tips = Tips(tips, tip_size) if tips else None
        self.highlight_hook = highlight_hook
        self.highlight_hook_args = highlight_hook_args
        self.highlight_hook_obj = highlight_hook_obj
        self.highlight_hook_attr = highlight_hook_attr
        self.use_padding = use_padding
        self.shift_ctrl_callback = shift_ctrl_callback
        self.shift_ctrl_args = shift_ctrl_args
        self.popup = popup
        self.popup_modifier_key = popup_modifier_key
        self.obj = obj
        self.attr = attr
        self.attr_index = attr_index
        self.font_color = (0,0,0,1)
        self.bg_color = (0,0,0,1)
        self.border_color = (0,0,0,1)
        self.mouse_over = False


    def build(self, db, x_offset, h_offset):
        self.font_color = db.color.text
        self.bg_color = db.color.cell_background
        self.border_color = db.color.border
        self.__highlight_hook_color(db)

        if self.img and not self.loaded_img:
            self.loaded_img = load_image_file(self.img)
        
        if self.shift_img_name:
            if not self.shift_img:
                self.shift_img = load_image_file(self.shift_img_name)

        pad = self.dims.padding

        text_w = 0
        text_h = 0
        if self.text:
            text_w = get_blf_text_dims(self.text, self.font_size)[0]
            text_h = get_blf_text_dims('SAMPLE', self.font_size)[1]

        # Image Group
        group = image_group()
        if self.glob_img_key and group:
            if self.glob_img_key in group:
                if type(group[self.glob_img_key]) == bpy.types.Image:
                    self.loaded_img = group[self.glob_img_key]

        # Image
        if self.loaded_img:
            if self.width:
                self.dims.max_width = self.width
                h = self.height if self.height else self.width
                self.dims.max_height = h
            else:
                y_dim = get_blf_text_dims("X", self.font_size)[1]
                self.dims.max_width = y_dim + pad * 2
                self.dims.max_height = y_dim + pad * 2

        # Manual Width
        elif self.width:
            self.dims.max_width = self.width
            h = self.height if self.height else text_h
            self.dims.max_height = h + pad * 2 if self.use_padding else h

        # Font Width
        else:
            self.dims.max_width = text_w + pad * 2
            h = self.height if self.height else text_h
            self.dims.max_height = h + pad * 2 if self.use_padding else h

        self.dims.bot_left  = (x_offset, h_offset)
        self.dims.top_left  = (x_offset, h_offset + self.dims.max_height)
        self.dims.top_right = (x_offset + self.dims.max_width, h_offset + self.dims.max_height)
        self.dims.bot_right = (x_offset + self.dims.max_width, h_offset)

        self.dims.x_pos = x_offset + pad
        self.dims.y_pos = h_offset + pad

        if self.tips:
            self.tips.build(db, self.dims.top_left[0], self.dims.top_left[1])


    def update(self, context, event, db):
        self.mouse_over = is_mouse_in_quad(self.dims.quad(), db.mouse_pos, tolerance=-1)
        if self.tips: self.tips.update()

        # Image Key
        self.__image_key_callback()

        # Color
        self.__set_color(event, db)

        # Not over button
        if not self.mouse_over: return

        # Attributes
        if db.clicked or db.increment:
            valid = True
            
            if self.scroll_enabled == False and db.increment:
                valid = False
            
            if valid:    
                if self.__attribute_setter(): return

        # No default call back
        if not self.callback: return

        # Scroll Event
        if db.increment:
            if self.scroll_enabled:
                self.__scroll_event(event, db)
        # Click Event
        elif db.clicked:
            self.__click_event(event, db)


    def __image_key_callback(self):
        if self.glob_img_key_update_func:
            key = None
            if self.glob_img_key_update_args:
                key = self.glob_img_key_update_func(*self.glob_img_key_update_args)
            else:
                key = self.glob_img_key_update_func()
            if type(key) == str:
                self.change_image_group_key(key=key)


    def __set_color(self, event, db):
        self.border_color = db.color.border

        # Attributes
        if self.__obj_attr_colors(db): return True
        
        if self.__highlight_hook_color(db): return

        if self.mouse_over:
            self.bg_color = db.color.mouse_over
            if event.shift:
                self.border_color = db.color.mods_highlight
        else:
            self.bg_color = db.color.cell_background


    def __highlight_hook_color(self, db):

        if self.highlight_hook:

            if self.highlight_hook_args:
                if self.highlight_hook(*self.highlight_hook_args):
                    self.border_color = db.color.mods_highlight
                    self.bg_color = db.color.cell_background
                    return True
            else:
                if self.highlight_hook():
                    self.border_color = db.color.mods_highlight
                    self.bg_color = db.color.cell_background
                    return True

        if self.highlight_hook_obj:
            if self.highlight_hook_attr:
                if hasattr(self.highlight_hook_obj, self.highlight_hook_attr):
                    val = getattr(self.highlight_hook_obj, self.highlight_hook_attr)
                    if val: self.border_color = db.color.mods_highlight
                    else: self.border_color = db.color.border

        if self.__obj_attr_colors(db): return True

        return False


    def __obj_attr_colors(self, db):
        if not self.obj: return False
    
        if not hasattr(self.obj, self.attr): return False
    
        val = getattr(self.obj, self.attr)
        if self.attr_index != None:
            val = val[self.attr_index]
        if val: self.border_color = db.color.mods_highlight
        else: self.border_color = db.color.border
        return True
    

    def __scroll_event(self, event, db):
        if db.increment > 0:
            if self.pos_args: self.callback(*self.pos_args)
            else: self.callback()
        elif db.increment < 0:
            if self.neg_args: self.callback(*self.neg_args)
            else: self.callback()


    def __click_event(self, event, db):

        if self.__popup(event, db): return

        # CTRL SHIFT
        if event.shift and event.ctrl:
            if self.shift_ctrl_callback:
                if self.shift_ctrl_args:
                    self.shift_ctrl_callback(*self.shift_ctrl_args)
                else:
                    self.shift_ctrl_callback()

        # SHIFT
        if event.shift:
            if self.neg_args:
                self.callback(*self.neg_args)
                return

        # ALT
        elif event.alt:
            if self.alt_callback:
                if self.alt_args:
                    self.alt_callback(*self.alt_args)
                else:
                    self.alt_callback()
                return

        # CTRL
        elif event.ctrl:
            if self.ctrl_callback:
                if self.ctrl_args:
                    self.ctrl_callback(*self.ctrl_args)
                else:
                    self.ctrl_callback()
                return

        # --- Not modifier keys --- #

        # Call positive version : with args
        if self.pos_args:
            self.callback(*self.pos_args)
            return

        # Call standard
        if not self.pos_args and not self.neg_args:
            self.callback()


    def __popup(self, event, db):
        if not self.popup: return False

        setup = False
        if self.popup_modifier_key == 'SHIFT':
            if event.shift == True and event.ctrl == False and event.alt == False:
                setup = True
        elif self.popup_modifier_key == 'CTRL':
            if event.shift == False and event.ctrl == True and event.alt == False:
                setup = True
        elif self.popup_modifier_key == 'ALT':
            if event.shift == False and event.ctrl == False and event.alt == True:
                setup = True
        elif self.popup_modifier_key == '':
            if event.shift == False and event.ctrl == False and event.alt == False:
                setup = True
        if not setup: return False

        self.popup.build(db, x_offset=db.mouse_pos[0], h_offset=db.mouse_pos[1])
        db.locked_popup = self.popup
        return True


    def __attribute_setter(self):
        if not self.obj: return False
        if not hasattr(self.obj, self.attr): return False
        val = getattr(self.obj, self.attr)

        if self.attr_index != None:
            val = val[self.attr_index]
            if type(val) != bool: return False
            item = getattr(self.obj, self.attr)
            item[self.attr_index] = not val
            setattr(self.obj, self.attr, item)
            return True

        else:
            if type(val) != bool: return False
            setattr(self.obj, self.attr, not val)
            return True
        

    def change_image_group_key(self, key=''):
        '''Update the active image'''

        group = image_group()
        if not group: return
        if key in group:
            if type(group[key]) == bpy.types.Image:
                self.loaded_img = group[key]
                self.glob_img_key = key


    def draw(self, db):
        render_quad(self.dims.quad(), color=self.bg_color)
        draw_border_lines(self.dims.quad(), color=self.border_color)

        if db.shift and self.shift_img:
            render_image(self.shift_img, self.dims.img_quad())

        elif self.loaded_img:
            render_image(self.loaded_img, self.dims.img_quad())

        text = self.shift_text if db.shift and self.shift_text else self.text
        text = self.highlight_text if self.border_color == db.color.mods_highlight and self.highlight_text else text
        text = self.ctrl_text if db.ctrl and self.ctrl_text else text
        text = self.alt_text if db.alt and self.alt_text else text

        render_text(text, (self.dims.x_pos, self.dims.y_pos), self.font_size, self.font_color)


    def draw_tips(self, db):
        if self.tips and self.mouse_over:
            self.tips.draw()


    def shut_down(self, context):
        clear_img_group()

        if self.shift_img:
            try: bpy.data.images.remove(self.shift_img)
            except: pass

        if not self.loaded_img: return
        try: bpy.data.images.remove(self.loaded_img)
        except: pass