playscii/ui_button.py

170 lines
5.7 KiB
Python

from ui_colors import UIColors
TEXT_LEFT = 0
TEXT_CENTER = 1
TEXT_RIGHT = 2
BUTTON_STATES = ["normal", "hovered", "clicked", "dimmed"]
class UIButton:
"clickable button that does something in a UIElement"
# x/y/width/height given in tile scale
x, y = 0, 0
width, height = 1, 1
caption = "TEST"
caption_justify = TEXT_LEFT
# paint caption from string, or not
should_draw_caption = True
caption_y = 0
callback = None
normal_fg_color = UIColors.black
normal_bg_color = UIColors.lightgrey
hovered_fg_color = UIColors.black
hovered_bg_color = UIColors.white
clicked_fg_color = UIColors.white
clicked_bg_color = UIColors.black
dimmed_fg_color = UIColors.black
dimmed_bg_color = UIColors.medgrey
# dimmed is a special, alternative-to-normal state
dimmed = False
can_hover = True
can_click = True
visible = True
# if true, this button is invisible and used for special trickery
never_draw = False
# weird (gross?) thing: other code can stash an argument to callback here
cb_arg = None
# if True, pass in mouse button #
pass_mouse_button = False
# if true, clear all characters before painting a new caption
clear_before_caption_draw = False
# if true, display a tooltip when hovered, and dismiss it when unhovered.
# contents set from get_tooltip_text and positioned by get_tooltip_location.
tooltip_on_hover = False
def __init__(self, element, starting_state=None):
self.element = element
self.state = starting_state or "normal"
def log_event(self, event_type):
"common code for button event logging"
if self.element.ui.logg:
self.element.ui.app.log(
"UIButton: {}'s {} {}".format(
self.element.__class__.__name__, self.__class__.__name__, event_type
)
)
def set_state(self, new_state):
if new_state not in BUTTON_STATES:
self.element.ui.app.log(
"Unrecognized state for button {}: {}".format(
self.__class__.__name__, new_state
)
)
return
self.dimmed = new_state == "dimmed"
self.state = new_state
self.set_state_colors()
def get_state_colors(self, state):
fg = getattr(self, "{}_fg_color".format(state))
bg = getattr(self, "{}_bg_color".format(state))
return fg, bg
def set_state_colors(self):
if self.never_draw:
return
# set colors for entire button area based on current state
if self.dimmed and self.state == "normal":
self.state = "dimmed"
# just bail if we're trying to draw something out of bounds
if (
self.x + self.width > self.element.art.width
or self.y + self.height > self.element.art.height
):
return
fg, bg = self.get_state_colors(self.state)
for y in range(self.height):
for x in range(self.width):
self.element.art.set_tile_at(0, 0, self.x + x, self.y + y, None, fg, bg)
def update_tooltip(self):
tt = self.element.ui.tooltip
tt.reset_art()
tt.set_text(self.get_tooltip_text())
tt.tile_x, tt.tile_y = self.get_tooltip_location()
tt.reset_loc()
def hover(self):
self.log_event("hovered")
self.set_state("hovered")
if self.tooltip_on_hover:
self.element.ui.tooltip.visible = True
self.update_tooltip()
def unhover(self):
self.log_event("unhovered")
if self.dimmed:
self.set_state("dimmed")
else:
self.set_state("normal")
if self.tooltip_on_hover:
# if two buttons are adjacent, we might be unhovering this one
# right after hovering the other in the same frame. if so,
# don't dismiss the tooltip
another_tooltip = False
for b in self.element.hovered_buttons:
if b is self:
continue
if b.tooltip_on_hover:
another_tooltip = True
if not another_tooltip:
self.element.ui.tooltip.visible = False
def click(self):
self.log_event("clicked")
self.set_state("clicked")
def unclick(self):
self.log_event("unclicked")
if self in self.element.hovered_buttons:
self.hover()
else:
self.unhover()
def get_tooltip_text(self):
"override in a subclass to define this button's tooltip text"
return "ERROR"
def get_tooltip_location(self):
"override in a subclass to define this button's tooltip screen location"
return 10, 10
def draw_caption(self):
y = self.y + self.caption_y
text = self.caption
# trim if too long
text = text[: self.width]
if self.caption_justify == TEXT_CENTER:
text = text.center(self.width)
elif self.caption_justify == TEXT_RIGHT:
text = text.rjust(self.width)
# just bail if we're trying to draw something out of bounds
if self.x + len(text) > self.element.art.width:
return
if self.clear_before_caption_draw:
for ty in range(self.height):
for tx in range(self.width):
self.element.art.set_char_index_at(0, 0, self.x + tx, y + ty, 0)
# leave FG color None; should already have been set
self.element.art.write_string(0, 0, self.x, y, text, None)
def draw(self):
if self.never_draw:
return
self.set_state_colors()
if self.should_draw_caption:
self.draw_caption()