OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / ui_framework / window / panel / widget / scroll_widget.py

483 lines · python ref:
from .layout.grid.elements.background import Background_Element
from .layout.grid.elements.border import Border_Element
from ....utils.checks import is_mouse_in_quad

from ..widget.header_controller import Widget_Header_Controller

import copy
import time
import math

class Scroll_Widget():

    def __init__(self, db, show_bar=True):

        # Data
        self.db = db
        self.win_key = ""

        # Containers
        self.layout = None
        self.layout_copy = None

        # Header
        self.collapsable = False
        self.header_layout = None
        self.header_controller = Widget_Header_Controller(db=self.db)
        self.header_height_percent = 0
        self.header_height = 0
        self.min_max_height = (0,0)

        # Layout Dims
        self.layout_width = 0
        self.bottom_left = (0, 0)
        self.bottom_right = (0, 0)
        self.top_left = (0, 0)
        self.top_right = (0, 0)

        # Layout Subdivide
        self.split_count_override = False
        self.split_count = 1
        self.start_slice = 0
        self.end_slice = 0

        # Scroll Bar
        self.hide_scroll_bar_override = not show_bar
        self.scroll_bar = Scroll_Bar(db=self.db, show_bar=show_bar)
        self.bar_width = 0
        self.body_height = 0
        self.show_scroll_bar = True
        self.scroll_speed = 0


        # Scroll Bar Reset
        self.first_cycle = True
        self.last_row_count = 0

        # Scroll bar behaviors
        self.full_body_scroll_detection = True
        

    def setup(self, bottom_left=(0,0), bottom_right=(0,0), top_left=(0,0), top_right=(0,0)):
        '''The head function for the dimensions setup across the layouts.'''

        self.bottom_left = bottom_left
        self.bottom_right = bottom_right
        self.top_left = top_left
        self.top_right = top_right

        self.header_dims_setup()

        self.subdivide_body()

        # Body / Scroll Bar
        if self.layout != None:
            # Adjust the rendering layout
            self.layout_alter()
            self.body_setup()

            if self.show_scroll_bar:
                self.scroll_bar_setup()


    def header_dims_setup(self):
        
        self.header_height = (self.header_height_percent * (self.top_left[1] - self.bottom_left[1])) * .01

        if self.header_height > self.min_max_height[1]:
            self.header_height = self.min_max_height[1]

        elif self.header_height < self.min_max_height[0]:
            self.header_height = self.min_max_height[0]

        # Widget header controller adds the drop down toggle
        self.header_controller.header_layout = self.header_layout
        self.header_controller.collapsable = self.collapsable

        # Header
        if self.header_layout != None:
            self.header_setup()
        else:
            self.body_height = self.top_left[1] - self.bottom_left[1]


    def header_setup(self):
        '''Set the dimensions on the layout's header. '''

        self.body_height = (self.top_left[1] - self.bottom_left[1]) - self.header_height
        self.header_controller.body_height = self.body_height
        self.header_controller.bottom_left = self.bottom_left
        self.header_controller.bottom_right = self.bottom_right
        self.header_controller.top_left = self.top_left
        self.header_controller.top_right = self.top_right

        self.header_controller.setup()


    def subdivide_body(self):
        '''Set the split count for the body.'''

        # Subdivide the window
        height = self.top_left[1] - self.bottom_left[1]
        row_count = len(self.layout.rows)
        row_height = height / row_count if row_count > 0 else 1

        cell_height_split = self.db.ui_event.min_height * self.db.scale_factor

        if row_height > cell_height_split:
            if self.split_count_override == True:
                self.show_scroll_bar = True

            else:
                self.split_count = len(self.layout.rows)
                self.show_scroll_bar = False

        else:
            if self.split_count_override == False:
                self.split_count = int(height / cell_height_split)
            self.show_scroll_bar = True

        self.scroll_speed = row_height


    def layout_alter(self):
        '''Make a copy of the original layout. The copy will be drawn.'''

        self.layout_copy = copy.copy(self.layout)

        self.slice_rows()

        # Set row heights
        height_percent = 0
        if self.split_count < len(self.layout.rows):
            height_percent = 100 / self.split_count
        elif len(self.layout.rows) == 0:
            height_percent = 100
        else:
           height_percent = 100 / len(self.layout.rows)

        for row in self.layout_copy.rows:
            row.height_percent = height_percent


    def slice_rows(self):
        '''Slice the rows out of the original layout and insert them into the copy.'''

        self.reset_scroll_bar_on_change()

        row_height = self.body_height / len(self.layout.rows) if len(self.layout.rows) > 0 else self.body_height
        bottom_offset = self.scroll_bar.bottom_offset
        index = bottom_offset / row_height

        if index > int(index) + .5:
            index = math.ceil(index)
        else:
            index = int(index)

        # Prevent the scroll bar being at the top of the window slicing incorrectly when scaling window
        if self.split_count == len(self.layout.rows):
            index = 0

        self.start_slice = index
        self.end_slice = self.split_count + self.start_slice

        self.layout_copy.rows = self.layout_copy.rows[self.start_slice : self.end_slice]


    def reset_scroll_bar_on_change(self):
        '''Resets the scroll bar bottom offset if the list is rebuilt and the length is different.'''

        if self.first_cycle == False:
            if len(self.layout.rows) != self.last_row_count:
                self.last_row_count = len(self.layout.rows)
                self.scroll_bar.bottom_offset = 0

        if self.first_cycle == True:
            self.last_row_count = len(self.layout.rows)
            self.first_cycle = False


    def body_setup(self):
        '''Set the dimensions on the layouts body.'''

        self.layout_width = self.bottom_right[0] - self.bottom_left[0]

        if self.show_scroll_bar: 
            self.bar_width = (self.layout_width * self.scroll_bar.bar_width_percent) * .01

        else:
            self.bar_width = 0

        if self.hide_scroll_bar_override:
            self.layout_copy.setup(
                bottom_left  =(self.bottom_left[0], self.bottom_left[1]), 
                bottom_right =(self.bottom_right[0], self.bottom_right[1]),
                top_left     =(self.top_left[0], self.top_left[1] - self.header_height),
                top_right    =(self.top_right[0], self.top_right[1] - self.header_height))
        else:
            self.layout_copy.setup(
                bottom_left  =(self.bottom_left[0], self.bottom_left[1]), 
                bottom_right =(self.bottom_right[0] - self.bar_width, self.bottom_right[1]),
                top_left     =(self.top_left[0], self.top_left[1] - self.header_height),
                top_right    =(self.top_right[0] - self.bar_width, self.top_right[1] - self.header_height))


    def scroll_bar_setup(self):
        '''Set the dimensions on the scroll bar.'''

        body_width = self.layout_width - self.bar_width

        row_height = self.body_height / len(self.layout.rows) if len(self.layout.rows) > 0 else self.body_height

        bar_height = row_height * self.split_count if len(self.layout.rows) > self.split_count else self.body_height

        self.scroll_bar.bar_height = bar_height
        self.scroll_bar.bottom_left = (self.bottom_left[0] + body_width, self.bottom_left[1])
        self.scroll_bar.bottom_right = (self.bottom_right[0], self.bottom_right[1])
        self.scroll_bar.top_left = (self.top_left[0] + body_width, self.top_left[1] - self.header_height)
        self.scroll_bar.top_right = (self.top_right[0], self.top_right[1] - self.header_height)

        self.scroll_bar.setup()


    def event(self):
        '''Run the event layer on the layouts and the scroll bar.'''
        
        # Header layout
        self.header_controller.event()

        # Body layout
        if not self.header_controller.collapsed:
            if self.layout != None:
                self.layout.event()

        # Scroll Bar
        if not self.header_controller.collapsed:
            self.scroll_bar.event()

        self.mouse_scrolling()


    def mouse_scrolling(self):

        mouse_over_scroll_box = False

        # Does the mouse scroll work on the entire window.
        if self.full_body_scroll_detection:
            mouse_over_scroll_box = is_mouse_in_quad(
                quad=(self.top_left, self.bottom_left, self.top_right, self.bottom_right), 
                mouse_pos=self.db.event.mouse_pos, 
                tolerance=0)
        # Only scrolls with mouse over scroll bar
        else:
            mouse_over_scroll_box = is_mouse_in_quad(
                quad=(self.scroll_bar.top_left, self.scroll_bar.bottom_left, self.scroll_bar.top_right, self.scroll_bar.bottom_right), 
                mouse_pos=self.db.event.mouse_pos, 
                tolerance=0)

        if mouse_over_scroll_box:
            if self.db.event.wheel_up:
                self.scroll_bar.bottom_offset += self.scroll_speed
                self.scroll_bar.clamp_offset()
            elif self.db.event.wheel_down:
                self.scroll_bar.bottom_offset -= self.scroll_speed
                self.scroll_bar.clamp_offset()


    def draw(self):
        '''Draw all the layouts.'''

        # Header layout
        self.header_controller.draw()

        if not self.header_controller.collapsed:

            # Body layout
            if self.layout_copy != None:
                self.layout_copy.draw()

            # Scroll Bar
            if not self.hide_scroll_bar_override:
                if self.show_scroll_bar:
                    self.scroll_bar.draw()


class Scroll_Bar():

    def __init__(self, db, show_bar=True):

        self.db = db

        # Dims
        self.bar_width_percent = 6
        self.bar_height = 0
        self.bottom_offset = 0

        self.bottom_left = (0, 0)
        self.bottom_right = (0, 0)
        self.top_left = (0, 0)
        self.top_right = (0, 0)

        # Bar overrides
        self.show_bar = show_bar

        # Event
        self.dragging = False
        self.mouse_is_over = False

        # Triggers
        self.max_val = False
        self.min_val = False

        # Elements
        self.elements()

    
    def elements(self):

        self.border = Border_Element()
        self.border.db = self.db

        self.bar = Background_Element()
        self.bar.db = self.db

        self.bar_border = Border_Element()
        self.bar_border.db = self.db


    def setup(self):
        '''Setup the dimensions for the drawable elements.'''

        self.setup_border()
        self.setup_bar()


    def setup_border(self):

        self.border.top_left = self.top_left
        self.border.top_right = self.top_right
        self.border.bottom_left = self.bottom_left
        self.border.bottom_right = self.bottom_right
        self.border.line_width = 2


    def setup_bar(self):

        self.bar.top_left = (self.bottom_left[0], self.bottom_left[1] + self.bar_height + self.bottom_offset)
        self.bar.top_right = (self.bottom_right[0], self.bottom_right[1] + self.bar_height + self.bottom_offset)
        self.bar.bottom_left = (self.bottom_left[0], self.bottom_left[1] + self.bottom_offset)
        self.bar.bottom_right = (self.bottom_right[0], self.bottom_right[1] + self.bottom_offset)
        self.bar.primary = False

        self.bar_border.top_left = (self.bottom_left[0], self.bottom_left[1] + self.bar_height + self.bottom_offset)
        self.bar_border.top_right = (self.bottom_right[0], self.bottom_right[1] + self.bar_height + self.bottom_offset)
        self.bar_border.bottom_left = (self.bottom_left[0], self.bottom_left[1] + self.bottom_offset)
        self.bar_border.bottom_right = (self.bottom_right[0], self.bottom_right[1] + self.bottom_offset)

        self.clamp_bar()


    def clamp_bar(self):

        self.max_val = False
        self.min_val = False
        if self.bar.top_left[1] >= self.top_left[1]:
            self.max_val = True
        elif self.bar.bottom_left[1] <= self.bottom_left[1]:
            self.min_val = True

        if self.bar.top_left[1] > self.top_left[1]:

            self.bar.top_left = (self.bar.top_left[0], self.top_left[1])
            self.bar.top_right = (self.bar.top_right[0], self.top_right[1])
            self.bar.bottom_left = (self.bar.top_left[0], self.bar.top_left[1] - self.bar_height)
            self.bar.bottom_right = (self.bar.top_right[0], self.bar.top_right[1] - self.bar_height)

            self.bar_border.top_left = (self.bar.top_left[0], self.top_left[1])
            self.bar_border.top_right = (self.bar.top_right[0], self.top_right[1])
            self.bar_border.bottom_left = (self.bar.top_left[0], self.bar.top_left[1] - self.bar_height)
            self.bar_border.bottom_right = (self.bar.top_right[0], self.bar.top_right[1] - self.bar_height)

        elif self.bar.bottom_left[1] < self.bottom_left[1]:

            self.bar.top_left = (self.bottom_left[0], self.bottom_left[1] + self.bar_height)
            self.bar.top_right = (self.bottom_right[0], self.bottom_right[1] + self.bar_height)
            self.bar.bottom_left = self.bottom_left
            self.bar.bottom_right = self.bottom_right

            self.bar_border.top_left = (self.bottom_left[0], self.bottom_left[1] + self.bar_height)
            self.bar_border.top_right = (self.bottom_right[0], self.bottom_right[1] + self.bar_height)
            self.bar_border.bottom_left = self.bottom_left
            self.bar_border.bottom_right = self.bottom_right


    def event(self):

        if self.dragging:
            self.drag()

        else:

            mouse_over_scroll_box = False
            if not self.db.ui_event.window_transforming:
                mouse_over_scroll_box = is_mouse_in_quad(
                    quad=(self.top_left, self.bottom_left, self.top_right, self.bottom_right), 
                    mouse_pos=self.db.event.mouse_pos, 
                    tolerance=0)

            # Mouse is over the bar
            if mouse_over_scroll_box:
                if self.db.event.mouse_dragging:
                    self.db.ui_event.cell_blocking = True
                    self.bar.drag_highlight = True
                    self.bar.highlight = False
                    self.dragging = True

                else:
                    self.db.ui_event.cell_blocking = False
                    self.bar.drag_highlight = False
                    self.bar.highlight = True

            else:
                self.bar.highlight = False

            self.mouse_is_over = mouse_over_scroll_box

        if self.db.event.left_click_released:
            self.db.ui_event.cell_blocking = False
            self.bar.drag_highlight = False
            self.dragging = False

        # Prevent bottom offset from staying at its value before scaling
        if self.db.ui_event.window_transforming:
            self.clamp_offset()


    def drag(self):

        self.bottom_offset += self.db.event.mouse_frame_diff[1]
        self.clamp_offset()


    def clamp_offset(self):

        if self.bottom_offset > self.top_left[1] - self.bottom_left[1] - self.bar_height:
            self.bottom_offset = self.top_left[1] - self.bottom_left[1] - self.bar_height

        elif self.bottom_offset < 0:
            self.bottom_offset = 0


    def draw(self):
        
        if self.show_bar == False:
            if self.dragging or self.mouse_is_over:
                self.bar.draw()
                self.bar_border.draw()
                self.border.draw()

        else:
            self.bar.draw()
            self.bar_border.draw()
            self.border.draw()