OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / ui_framework / form_ui / elements / text_input.py

176 lines · python ref:
import bpy, time, math, numpy
from math import sin
from mathutils import Vector
from ....utility.screen import dpi_factor
from ... graphics.load import load_image_file
from ... graphics.draw import render_text, draw_border_lines, render_quad, draw_2D_lines
from ... utils.geo import get_blf_text_dims
from ... utils.checks import is_mouse_in_quad
from . import Dims, Tips


class Text_Input:
    def __init__(self,
        obj=None, attr="", font_size=12, width=50, height=0,
        tips=None, tip_size=12,
        on_active_callback=None):
        
        self.dims = Dims()
        self.font_color = (0,0,0,1)
        self.bg_color = (0,0,0,1)
        self.border_color = (0,0,0,1)
        self.obj = obj
        self.attr = attr
        self.font_size = font_size
        self.width = width * dpi_factor(min=0.5)
        self.height = height * dpi_factor(min=0.5)
        self.text = ""
        self.tips = Tips(tips, tip_size) if tips else None
        self.mouse_over = False
        self.on_active_callback = on_active_callback
        # Input
        self.entry_string = ""
        # Notify
        self.timer = None
        self.locked = False
        self.alpha = 1


    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

        if self.obj == None or self.attr == "": return

        self.text = self.__get_value()
        dims = get_blf_text_dims(self.text, self.font_size)
        pad = self.dims.padding

        self.dims.max_width = self.width
        if self.height: self.dims.max_height = self.height
        else: self.dims.max_height = dims[1] + pad * 2

        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):
        if self.tips: self.tips.update()
        self.locked = False
        self.mouse_over = is_mouse_in_quad(self.dims.quad(), db.mouse_pos, tolerance=-1)
        self.__set_colors(db, self.mouse_over)
        if self.mouse_over:
            self.__mouse_over_actions(context, event, db)
        
        if self.obj == None or self.attr == "": return                
        self.text = self.__get_value()


    def __set_colors(self, db, mouse_over):
        self.border_color = db.color.border
        # Mouse over
        if mouse_over:
            self.bg_color = db.color.mouse_over
        # Standard color
        else:
            self.bg_color = db.color.cell_background


    def __mouse_over_actions(self, context, event, db):
        if db.locked_element: return

        # Clicked down
        if db.clicked == True:
            self.__on_active_callback()
            # Locked State
            db.locked_element = self
            self.entry_string = self.__get_value()
            if self.timer == None:
                self.timer = context.window_manager.event_timer_add(0.025, window=context.window)


    def __on_active_callback(self):
        if self.on_active_callback:
            self.on_active_callback()


    def locked_update(self, context, event, db):

        self.locked = True

        # Set highlight color during focus mode
        self.border_color = db.color.mods_highlight

        invalid = {'\\', '/', ':', '*', '?', '"', '<', '>', '|', '.'}

        # Append text
        if event.ascii not in invalid and event.value == 'PRESS':
            self.entry_string += str(event.ascii)

        # Finish
        if event.type in {'RET', 'NUMPAD_ENTER', 'SPACE', 'LEFTMOUSE', 'TAB'} and event.value == 'PRESS':
            self.__set_value(self.entry_string)

            # Unlock / Set
            self.text = self.__get_value()
            db.locked_element = None
            self.entry_string = ""
            self.__remove_timer(context)

        # Edit
        elif event.type == 'BACK_SPACE' and event.value == 'PRESS':
            if event.ctrl:
                self.entry_string = ""
            else:
                self.entry_string = self.entry_string[:-1]


    def __get_value(self):
        val = getattr(self.obj, self.attr)
        return str(val)


    def __set_value(self, val):
        setattr(self.obj, self.attr, val)


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

        color = self.__faded_color()
        draw_border_lines(self.dims.quad(), color=color)

        text = self.entry_string if self.locked else self.text
        render_text(text, (self.dims.x_pos, self.dims.y_pos), self.font_size, self.font_color)


    def __faded_color(self):
        if self.locked:
            self.alpha = sin(time.time() * 10)
            return (self.border_color[0], self.border_color[1], self.border_color[2], self.alpha)

        return self.border_color


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


    def shut_down(self, context):
        self.__remove_timer(context)


    def __remove_timer(self, context):
        if self.timer:
            context.window_manager.event_timer_remove(self.timer)
            self.timer = None