import os.path, time from math import ceil from ui_element import UIElement, UIArt, UIRenderable from ui_button import UIButton, TEXT_LEFT, TEXT_CENTER, TEXT_RIGHT from ui_colors import UIColors from renderable_line import UIRenderableX from art import uv_names # 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()