568 lines
20 KiB
Python
568 lines
20 KiB
Python
import os.path
|
|
import time
|
|
from math import ceil
|
|
|
|
from art import uv_names
|
|
from renderable_line import UIRenderableX
|
|
from ui_button import TEXT_CENTER, TEXT_RIGHT, UIButton
|
|
from ui_colors import UIColors
|
|
from ui_element import UIArt, UIElement, UIRenderable
|
|
|
|
# buttons to toggle "affects" status / cycle through choices, respectively
|
|
|
|
|
|
class StatusBarToggleButton(UIButton):
|
|
caption_justify = TEXT_RIGHT
|
|
|
|
|
|
class StatusBarCycleButton(UIButton):
|
|
# do different stuff for left vs right click
|
|
pass_mouse_button = True
|
|
should_draw_caption = False
|
|
width = 3
|
|
|
|
|
|
class CharToggleButton(StatusBarToggleButton):
|
|
x = 0
|
|
caption = "ch:"
|
|
width = len(caption) + 1
|
|
tooltip_on_hover = True
|
|
|
|
def get_tooltip_text(self):
|
|
return "character index: %s" % self.element.ui.selected_char
|
|
|
|
def get_tooltip_location(self):
|
|
return 1, self.element.get_tile_y() - 1
|
|
|
|
|
|
class CharCycleButton(StatusBarCycleButton):
|
|
x = CharToggleButton.width
|
|
tooltip_on_hover = True
|
|
|
|
# reuse above
|
|
def get_tooltip_text(self):
|
|
return CharToggleButton.get_tooltip_text(self)
|
|
|
|
def get_tooltip_location(self):
|
|
return CharToggleButton.get_tooltip_location(self)
|
|
|
|
|
|
class FGToggleButton(StatusBarToggleButton):
|
|
x = CharCycleButton.x + CharCycleButton.width
|
|
caption = "fg:"
|
|
width = len(caption) + 1
|
|
tooltip_on_hover = True
|
|
|
|
def get_tooltip_text(self):
|
|
return "foreground color index: %s" % self.element.ui.selected_fg_color
|
|
|
|
def get_tooltip_location(self):
|
|
return 8, self.element.get_tile_y() - 1
|
|
|
|
|
|
class FGCycleButton(StatusBarCycleButton):
|
|
x = FGToggleButton.x + FGToggleButton.width
|
|
tooltip_on_hover = True
|
|
|
|
def get_tooltip_text(self):
|
|
return FGToggleButton.get_tooltip_text(self)
|
|
|
|
def get_tooltip_location(self):
|
|
return FGToggleButton.get_tooltip_location(self)
|
|
|
|
|
|
class BGToggleButton(StatusBarToggleButton):
|
|
x = FGCycleButton.x + FGCycleButton.width
|
|
caption = "bg:"
|
|
width = len(caption) + 1
|
|
tooltip_on_hover = True
|
|
|
|
def get_tooltip_text(self):
|
|
return "background color index: %s" % self.element.ui.selected_bg_color
|
|
|
|
def get_tooltip_location(self):
|
|
return 15, self.element.get_tile_y() - 1
|
|
|
|
|
|
class BGCycleButton(StatusBarCycleButton):
|
|
x = BGToggleButton.x + BGToggleButton.width
|
|
tooltip_on_hover = True
|
|
|
|
def get_tooltip_text(self):
|
|
return BGToggleButton.get_tooltip_text(self)
|
|
|
|
def get_tooltip_location(self):
|
|
return BGToggleButton.get_tooltip_location(self)
|
|
|
|
|
|
class XformToggleButton(StatusBarToggleButton):
|
|
x = BGCycleButton.x + BGCycleButton.width
|
|
caption = "xform:"
|
|
width = len(caption) + 1
|
|
|
|
|
|
# class for things like xform and tool whose captions you can cycle through
|
|
class StatusBarTextCycleButton(StatusBarCycleButton):
|
|
should_draw_caption = True
|
|
caption_justify = TEXT_CENTER
|
|
normal_fg_color = UIColors.lightgrey
|
|
normal_bg_color = UIColors.black
|
|
hovered_fg_color = UIColors.lightgrey
|
|
hovered_bg_color = UIColors.black
|
|
clicked_fg_color = UIColors.black
|
|
clicked_bg_color = UIColors.white
|
|
|
|
|
|
class XformCycleButton(StatusBarTextCycleButton):
|
|
x = XformToggleButton.x + XformToggleButton.width
|
|
width = len("Rotate 180")
|
|
caption = uv_names[0]
|
|
|
|
|
|
class ToolCycleButton(StatusBarTextCycleButton):
|
|
x = XformCycleButton.x + XformCycleButton.width + len("tool:") + 1
|
|
# width and caption are set during status bar init after button is created
|
|
|
|
|
|
class FileCycleButton(StatusBarTextCycleButton):
|
|
caption = "[nothing]"
|
|
|
|
|
|
class LayerCycleButton(StatusBarTextCycleButton):
|
|
caption = "X/Y"
|
|
width = len(caption)
|
|
|
|
|
|
class FrameCycleButton(StatusBarTextCycleButton):
|
|
caption = "X/Y"
|
|
width = len(caption)
|
|
|
|
|
|
class ZoomSetButton(StatusBarTextCycleButton):
|
|
caption = "100.0"
|
|
width = len(caption)
|
|
|
|
|
|
class StatusBarUI(UIElement):
|
|
snap_bottom = True
|
|
snap_left = True
|
|
always_consume_input = True
|
|
dim_color = 12
|
|
swatch_width = 3
|
|
char_swatch_x = CharCycleButton.x
|
|
fg_swatch_x = FGCycleButton.x
|
|
bg_swatch_x = BGCycleButton.x
|
|
tool_label = "tool:"
|
|
tool_label_x = XformCycleButton.x + XformCycleButton.width + 1
|
|
tile_label = "tile:"
|
|
layer_label = "layer:"
|
|
frame_label = "frame:"
|
|
zoom_label = "%"
|
|
right_items_width = (
|
|
len(tile_label)
|
|
+ len(layer_label)
|
|
+ len(frame_label)
|
|
+ (len("X/Y") + 2) * 2
|
|
+ len("XX/YY")
|
|
+ 2
|
|
+ len(zoom_label)
|
|
+ 10
|
|
)
|
|
button_names = {
|
|
CharToggleButton: "char_toggle",
|
|
CharCycleButton: "char_cycle",
|
|
FGToggleButton: "fg_toggle",
|
|
FGCycleButton: "fg_cycle",
|
|
BGToggleButton: "bg_toggle",
|
|
BGCycleButton: "bg_cycle",
|
|
XformToggleButton: "xform_toggle",
|
|
XformCycleButton: "xform_cycle",
|
|
ToolCycleButton: "tool_cycle",
|
|
FileCycleButton: "file_cycle",
|
|
LayerCycleButton: "layer_cycle",
|
|
FrameCycleButton: "frame_cycle",
|
|
ZoomSetButton: "zoom_set",
|
|
}
|
|
|
|
def __init__(self, ui):
|
|
art = ui.active_art
|
|
self.ui = ui
|
|
# create 3 custom Arts w/ source charset and palette, renderables for each
|
|
art_name = "%s_%s" % (int(time.time()), self.__class__.__name__)
|
|
self.char_art = UIArt(
|
|
art_name, ui.app, art.charset, art.palette, self.swatch_width, 1
|
|
)
|
|
self.char_renderable = UIRenderable(ui.app, self.char_art)
|
|
self.fg_art = UIArt(
|
|
art_name, ui.app, art.charset, art.palette, self.swatch_width, 1
|
|
)
|
|
self.fg_renderable = UIRenderable(ui.app, self.fg_art)
|
|
self.bg_art = UIArt(
|
|
art_name, ui.app, art.charset, art.palette, self.swatch_width, 1
|
|
)
|
|
self.bg_renderable = UIRenderable(ui.app, self.bg_art)
|
|
# "dimmed out" box
|
|
self.dim_art = UIArt(
|
|
art_name,
|
|
ui.app,
|
|
ui.charset,
|
|
ui.palette,
|
|
self.swatch_width + self.char_swatch_x,
|
|
1,
|
|
)
|
|
self.dim_renderable = UIRenderable(ui.app, self.dim_art)
|
|
self.dim_renderable.alpha = 0.75
|
|
# separate dimmed out box for xform, easier this way
|
|
xform_width = XformToggleButton.width + XformCycleButton.width
|
|
self.dim_xform_art = UIArt(
|
|
art_name, ui.app, ui.charset, ui.palette, xform_width, 1
|
|
)
|
|
self.dim_xform_renderable = UIRenderable(ui.app, self.dim_xform_art)
|
|
self.dim_xform_renderable.alpha = 0.75
|
|
# create clickable buttons
|
|
self.buttons = []
|
|
self.button_map = {}
|
|
for button_class, button_name in self.button_names.items():
|
|
button = button_class(self)
|
|
setattr(self, button_name + "_button", button)
|
|
cb_name = "%s_button_pressed" % button_name
|
|
button.callback = getattr(self, cb_name)
|
|
self.buttons.append(button)
|
|
# keep a mapping of button names to buttons, for eg tooltip updates
|
|
self.button_map[button_name] = button
|
|
# some button captions, widths, locations will be set in reset_art
|
|
# determine total width of left-justified items
|
|
self.left_items_width = (
|
|
self.tool_cycle_button.x + self.tool_cycle_button.width + 15
|
|
)
|
|
# set some properties in bulk
|
|
self.renderables = []
|
|
for r in [
|
|
self.char_renderable,
|
|
self.fg_renderable,
|
|
self.bg_renderable,
|
|
self.dim_renderable,
|
|
self.dim_xform_renderable,
|
|
]:
|
|
r.ui = ui
|
|
r.grain_strength = 0
|
|
# add to list of renderables to manage eg destroyed on quit
|
|
self.renderables.append(r)
|
|
# red X for transparent colors
|
|
self.x_renderable = UIRenderableX(ui.app, self.char_art)
|
|
# give it a special reference to this element
|
|
self.x_renderable.status_bar = self
|
|
self.renderables.append(self.x_renderable)
|
|
UIElement.__init__(self, ui)
|
|
|
|
# button callbacks
|
|
|
|
def char_toggle_button_pressed(self):
|
|
if self.ui.active_dialog:
|
|
return
|
|
self.ui.selected_tool.toggle_affects_char()
|
|
|
|
def char_cycle_button_pressed(self, mouse_button):
|
|
if self.ui.active_dialog:
|
|
return
|
|
if mouse_button == 1:
|
|
self.ui.select_char(self.ui.selected_char + 1)
|
|
elif mouse_button == 3:
|
|
self.ui.select_char(self.ui.selected_char - 1)
|
|
|
|
def fg_toggle_button_pressed(self):
|
|
if self.ui.active_dialog:
|
|
return
|
|
self.ui.selected_tool.toggle_affects_fg()
|
|
|
|
def fg_cycle_button_pressed(self, mouse_button):
|
|
if self.ui.active_dialog:
|
|
return
|
|
if mouse_button == 1:
|
|
self.ui.select_fg(self.ui.selected_fg_color + 1)
|
|
elif mouse_button == 3:
|
|
self.ui.select_fg(self.ui.selected_fg_color - 1)
|
|
|
|
def bg_toggle_button_pressed(self):
|
|
if self.ui.active_dialog:
|
|
return
|
|
self.ui.selected_tool.toggle_affects_bg()
|
|
|
|
def bg_cycle_button_pressed(self, mouse_button):
|
|
if self.ui.active_dialog:
|
|
return
|
|
if mouse_button == 1:
|
|
self.ui.select_bg(self.ui.selected_bg_color + 1)
|
|
elif mouse_button == 3:
|
|
self.ui.select_bg(self.ui.selected_bg_color - 1)
|
|
|
|
def xform_toggle_button_pressed(self):
|
|
if self.ui.active_dialog:
|
|
return
|
|
self.ui.selected_tool.toggle_affects_xform()
|
|
|
|
def xform_cycle_button_pressed(self, mouse_button):
|
|
if self.ui.active_dialog:
|
|
return
|
|
if mouse_button == 1:
|
|
self.ui.cycle_selected_xform()
|
|
elif mouse_button == 3:
|
|
self.ui.cycle_selected_xform(True)
|
|
# update caption with new xform
|
|
self.xform_cycle_button.caption = uv_names[self.ui.selected_xform]
|
|
|
|
def tool_cycle_button_pressed(self, mouse_button):
|
|
if self.ui.active_dialog:
|
|
return
|
|
if mouse_button == 1:
|
|
self.ui.cycle_selected_tool()
|
|
elif mouse_button == 3:
|
|
self.ui.cycle_selected_tool(True)
|
|
self.tool_cycle_button.caption = self.ui.selected_tool.get_button_caption()
|
|
|
|
def file_cycle_button_pressed(self, mouse_button):
|
|
if not self.ui.active_art:
|
|
return
|
|
if self.ui.active_dialog:
|
|
return
|
|
if mouse_button == 1:
|
|
self.ui.next_active_art()
|
|
elif mouse_button == 3:
|
|
self.ui.previous_active_art()
|
|
|
|
def layer_cycle_button_pressed(self, mouse_button):
|
|
if not self.ui.active_art:
|
|
return
|
|
if self.ui.active_dialog:
|
|
return
|
|
if mouse_button == 1:
|
|
self.ui.set_active_layer(self.ui.active_art.active_layer + 1)
|
|
elif mouse_button == 3:
|
|
self.ui.set_active_layer(self.ui.active_art.active_layer - 1)
|
|
|
|
def frame_cycle_button_pressed(self, mouse_button):
|
|
if not self.ui.active_art:
|
|
return
|
|
if self.ui.active_dialog:
|
|
return
|
|
if mouse_button == 1:
|
|
self.ui.set_active_frame(self.ui.active_art.active_frame + 1)
|
|
elif mouse_button == 3:
|
|
self.ui.set_active_frame(self.ui.active_art.active_frame - 1)
|
|
|
|
def zoom_set_button_pressed(self, mouse_button):
|
|
if not self.ui.active_art:
|
|
return
|
|
if self.ui.active_dialog:
|
|
return
|
|
if mouse_button == 1:
|
|
self.ui.app.camera.zoom_proportional(1)
|
|
elif mouse_button == 3:
|
|
self.ui.app.camera.zoom_proportional(-1)
|
|
|
|
def reset_art(self):
|
|
UIElement.reset_art(self)
|
|
self.tile_width = ceil(self.ui.width_tiles * self.ui.scale)
|
|
# must resize here, as window width will vary
|
|
self.art.resize(self.tile_width, self.tile_height)
|
|
# write chars/colors to the art
|
|
self.rewrite_art()
|
|
self.x_renderable.scale_x = self.char_art.width
|
|
self.x_renderable.scale_y = -self.char_art.height
|
|
# dim box
|
|
self.dim_art.clear_frame_layer(0, 0, self.ui.colors.white)
|
|
self.dim_art.update()
|
|
self.dim_xform_art.clear_frame_layer(0, 0, self.ui.colors.white)
|
|
self.dim_xform_art.update()
|
|
# rebuild geo, elements may be new dimensions
|
|
self.dim_art.geo_changed = True
|
|
self.dim_xform_art.geo_changed = True
|
|
self.char_art.geo_changed = True
|
|
self.fg_art.geo_changed = True
|
|
self.bg_art.geo_changed = True
|
|
|
|
def rewrite_art(self):
|
|
bg = self.ui.colors.white
|
|
self.art.clear_frame_layer(0, 0, bg)
|
|
# if user is making window reeeeally skinny, bail
|
|
if self.tile_width < self.left_items_width:
|
|
return
|
|
# draw tool label
|
|
self.art.write_string(
|
|
0, 0, self.tool_label_x, 0, self.tool_label, self.ui.palette.darkest_index
|
|
)
|
|
# only draw right side info if the window is wide enough
|
|
if self.art.width > self.left_items_width + self.right_items_width:
|
|
self.file_cycle_button.visible = True
|
|
self.layer_cycle_button.visible = True
|
|
self.frame_cycle_button.visible = True
|
|
self.zoom_set_button.visible = True
|
|
self.write_right_elements()
|
|
else:
|
|
self.file_cycle_button.visible = False
|
|
self.layer_cycle_button.visible = False
|
|
self.frame_cycle_button.visible = False
|
|
self.zoom_set_button.visible = False
|
|
|
|
def set_active_charset(self, new_charset):
|
|
self.char_art.charset = self.fg_art.charset = self.bg_art.charset = new_charset
|
|
self.reset_art()
|
|
|
|
def set_active_palette(self, new_palette):
|
|
self.char_art.palette = self.fg_art.palette = self.bg_art.palette = new_palette
|
|
self.reset_art()
|
|
|
|
def get_tile_y(self):
|
|
"returns tile coordinate Y position of bar"
|
|
return (
|
|
int(
|
|
self.ui.app.window_height
|
|
/ (self.ui.charset.char_height * self.ui.scale)
|
|
)
|
|
- 1
|
|
)
|
|
|
|
def update_button_captions(self):
|
|
"set captions for buttons that change from selections"
|
|
art = self.ui.active_art
|
|
self.xform_cycle_button.caption = uv_names[self.ui.selected_xform]
|
|
self.tool_cycle_button.caption = self.ui.selected_tool.get_button_caption()
|
|
self.tool_cycle_button.width = len(self.tool_cycle_button.caption) + 2
|
|
# right edge elements
|
|
self.file_cycle_button.caption = (
|
|
os.path.basename(art.filename) if art else FileCycleButton.caption
|
|
)
|
|
self.file_cycle_button.width = len(self.file_cycle_button.caption) + 2
|
|
# NOTE: button X offsets will be set in write_right_elements
|
|
null = "---"
|
|
layers = art.layers if art else 0
|
|
layer = "%s/%s" % (art.active_layer + 1, layers) if art else null
|
|
self.layer_cycle_button.caption = layer
|
|
self.layer_cycle_button.width = len(self.layer_cycle_button.caption)
|
|
frames = art.frames if art else 0
|
|
frame = "%s/%s" % (art.active_frame + 1, frames) if art else null
|
|
self.frame_cycle_button.caption = frame
|
|
self.frame_cycle_button.width = len(self.frame_cycle_button.caption)
|
|
# zoom %
|
|
zoom = "%.1f" % self.ui.app.camera.get_current_zoom_pct() if art else null
|
|
self.zoom_set_button.caption = zoom[:5] # maintain size
|
|
|
|
def update(self):
|
|
if not self.ui.active_art:
|
|
return
|
|
# update buttons
|
|
UIElement.update(self)
|
|
# set color swatches
|
|
for i in range(self.swatch_width):
|
|
self.char_art.set_color_at(0, 0, i, 0, self.ui.selected_bg_color, False)
|
|
self.fg_art.set_color_at(0, 0, i, 0, self.ui.selected_fg_color, False)
|
|
self.bg_art.set_color_at(0, 0, i, 0, self.ui.selected_bg_color, False)
|
|
# set char w/ correct FG color and xform
|
|
self.char_art.set_char_index_at(0, 0, 1, 0, self.ui.selected_char)
|
|
self.char_art.set_color_at(0, 0, 1, 0, self.ui.selected_fg_color, True)
|
|
self.char_art.set_char_transform_at(0, 0, 1, 0, self.ui.selected_xform)
|
|
# position elements
|
|
self.position_swatch(self.char_renderable, self.char_swatch_x)
|
|
self.position_swatch(self.fg_renderable, self.fg_swatch_x)
|
|
self.position_swatch(self.bg_renderable, self.bg_swatch_x)
|
|
# update buttons before redrawing art (ie non-interactive bits)
|
|
self.update_button_captions()
|
|
for art in [self.char_art, self.fg_art, self.bg_art]:
|
|
art.update()
|
|
self.rewrite_art()
|
|
self.draw_buttons()
|
|
|
|
def position_swatch(self, renderable, x_offset):
|
|
renderable.x = (self.char_art.quad_width * x_offset) - 1
|
|
renderable.y = self.char_art.quad_height - 1
|
|
|
|
def reset_loc(self):
|
|
UIElement.reset_loc(self)
|
|
|
|
def write_right_elements(self):
|
|
"""
|
|
fills in right-justified parts of status bar, eg current
|
|
frame/layer/tile labels (buttons positioned but drawn separately)
|
|
"""
|
|
dark = self.ui.colors.black
|
|
light = self.ui.colors.white
|
|
art = self.ui.active_art
|
|
padding = 2
|
|
# position file button
|
|
x = self.tile_width - (self.file_cycle_button.width + 1)
|
|
self.file_cycle_button.x = x
|
|
x -= padding
|
|
# zoom
|
|
self.art.write_string(0, 0, x, 0, self.zoom_label, dark, light, True)
|
|
x -= len(self.zoom_label) + self.zoom_set_button.width
|
|
self.zoom_set_button.x = x
|
|
x -= padding
|
|
# tile
|
|
tile = "X/Y"
|
|
color = light
|
|
if self.ui.app.cursor and art:
|
|
tile_x, tile_y = self.ui.app.cursor.get_tile()
|
|
tile_y = int(tile_y)
|
|
# user-facing coordinates are always base 1
|
|
tile_x += 1
|
|
tile_y += 1
|
|
if tile_x <= 0 or tile_x > art.width:
|
|
color = self.dim_color
|
|
if tile_y <= 0 or tile_y > art.height:
|
|
color = self.dim_color
|
|
tile_x = str(tile_x).rjust(3)
|
|
tile_y = str(tile_y).rjust(3)
|
|
tile = "%s,%s" % (tile_x, tile_y)
|
|
self.art.write_string(0, 0, x, 0, tile, color, dark, True)
|
|
# tile label
|
|
x -= len(tile)
|
|
self.art.write_string(0, 0, x, 0, self.tile_label, dark, light, True)
|
|
# position layer button
|
|
x -= padding + len(self.tile_label) + self.layer_cycle_button.width
|
|
self.layer_cycle_button.x = x
|
|
# layer label
|
|
self.art.write_string(0, 0, x, 0, self.layer_label, dark, light, True)
|
|
# position frame button
|
|
x -= padding + len(self.layer_label) + self.frame_cycle_button.width
|
|
self.frame_cycle_button.x = x
|
|
# frame label
|
|
self.art.write_string(0, 0, x, 0, self.frame_label, dark, light, True)
|
|
|
|
def render(self):
|
|
if not self.ui.active_art:
|
|
return
|
|
UIElement.render(self)
|
|
# draw wireframe red X /behind/ char if BG transparent
|
|
if self.ui.selected_bg_color == 0:
|
|
self.x_renderable.x = self.char_renderable.x
|
|
self.x_renderable.y = self.char_renderable.y
|
|
self.x_renderable.render()
|
|
self.char_renderable.render()
|
|
self.fg_renderable.render()
|
|
self.bg_renderable.render()
|
|
# draw red X for transparent FG or BG
|
|
if self.ui.selected_fg_color == 0:
|
|
self.x_renderable.x = self.fg_renderable.x
|
|
self.x_renderable.y = self.fg_renderable.y
|
|
self.x_renderable.render()
|
|
if self.ui.selected_bg_color == 0:
|
|
self.x_renderable.x = self.bg_renderable.x
|
|
self.x_renderable.y = self.bg_renderable.y
|
|
self.x_renderable.render()
|
|
# dim out items if brush is set to not affect them
|
|
self.dim_renderable.y = self.char_renderable.y
|
|
swatch_width = self.art.quad_width * StatusBarCycleButton.width
|
|
if not self.ui.selected_tool.affects_char:
|
|
self.dim_renderable.x = self.char_renderable.x - swatch_width
|
|
self.dim_renderable.render()
|
|
if not self.ui.selected_tool.affects_fg_color:
|
|
self.dim_renderable.x = self.fg_renderable.x - swatch_width
|
|
self.dim_renderable.render()
|
|
if not self.ui.selected_tool.affects_bg_color:
|
|
self.dim_renderable.x = self.bg_renderable.x - swatch_width
|
|
self.dim_renderable.render()
|
|
if not self.ui.selected_tool.affects_xform:
|
|
# separate dimmer renderable for xform's wider size
|
|
self.dim_xform_renderable.y = self.char_renderable.y
|
|
self.dim_xform_renderable.x = XformToggleButton.x * self.art.quad_width - 1
|
|
self.dim_xform_renderable.render()
|