diff options
Diffstat (limited to 'TurtleArt/tapaletteview.py')
-rw-r--r-- | TurtleArt/tapaletteview.py | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/TurtleArt/tapaletteview.py b/TurtleArt/tapaletteview.py new file mode 100644 index 0000000..213ee17 --- /dev/null +++ b/TurtleArt/tapaletteview.py @@ -0,0 +1,423 @@ +# -*- coding: utf-8 -*- +#Copyright (c) 2014, Walter Bender + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in +#all copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +#THE SOFTWARE. + +_SKIN_IMAGE = 1 +_MARGIN = 5 +_BUTTON_SIZE = 32 + +from tautils import find_group, debug_output +from tablock import Block +from tapalette import (palette_names, palette_blocks, hidden_proto_blocks, + block_styles) +from taconstants import (PALETTE_SCALE, ICON_SIZE, PYTHON_SKIN, XO1, + HORIZONTAL_PALETTE, PALETTE_WIDTH, PALETTE_HEIGHT, + CATEGORY_LAYER, TOP_LAYER, PROTO_LAYER) +from tasprite_factory import SVG, svg_str_to_pixbuf +from sprites import Sprite + + +def _width_and_height(blk): + ''' What are the width and height of a stack? ''' + minx = 10000 + miny = 10000 + maxx = -10000 + maxy = -10000 + for gblk in find_group(blk): + (x, y) = gblk.spr.get_xy() + w, h = gblk.spr.get_dimensions() + if x < minx: + minx = x + if y < miny: + miny = y + if x + w > maxx: + maxx = x + w + if y + h > maxy: + maxy = y + h + return(maxx - minx, maxy - miny) + + +class PaletteView(): + ''' Palette View class abstraction ''' + + def __init__(self, turtle_window, n): + ''' + This class handles the display of block palettes + ''' + self.blocks = [] + self.backgrounds = [None, None] + + self._turtle_window = turtle_window + self._palette_index = n + self._visible = False + self._populated = False + + if not n < len(palette_names): + # Shouldn't happen, but hey... + debug_output('palette index %d is out of range' % n, + self._turtle_window.running_sugar) + self._name = 'undefined' + else: + self._name = palette_names[n] + + def create(self, regenerate=True, show=False): + if not self._name == 'undefined': + # Create proto blocks for each palette entry + self._create_proto_blocks() + + save_selected = self._turtle_window.selected_palette + self.layout_palette(regenerate=regenerate, + show=(show or + save_selected == self._palette_index)) + + def show(self): + ''' Show palette background and proto blocks. If needed, display + shift button. ''' + orientation = self._turtle_window.orientation + if self.backgrounds[orientation] is not None: + self.backgrounds[orientation].set_layer(CATEGORY_LAYER) + + for blk in self.blocks: + if blk.get_visibility(): + blk.spr.set_layer(PROTO_LAYER) + else: + blk.spr.hide() + + self.display_palette_shift_buttons() + + def hide(self): + ''' Hide the palette. ''' + for background in self.backgrounds: + if background is not None: + background.hide() + + for blk in self.blocks: + blk.spr.hide() + + self._hide_palette_shift_buttons() + + if self._trash_palette(): + for blk in self._turtle_window.trash_stack: + for gblk in find_group(blk): + gblk.spr.hide() + + def move(self, x, y): + ''' Move the palette. ''' + buttons = self._turtle_window.palette_button + + for blk in self.blocks: + blk.spr.move((x + blk.spr.save_xy[0], y + blk.spr.save_xy[1])) + + for button in buttons: + button.move((x + button.save_xy[0], y + button.save_xy[1])) + + for spr in self.backgrounds: + if spr is not None: + spr.move((x + spr.save_xy[0], y + spr.save_xy[1])) + + if self._trash_palette(): + for blk in self._turtle_window.trash_stack: + for gblk in find_group(blk): + gblk.spr.move((x + gblk.spr.save_xy[0], + y + gblk.spr.save_xy[1])) + + def shift(self): + ''' Shift blocks on the palette. ''' + buttons = self._turtle_window.palette_button + orientation = self._turtle_window.orientation + + x, y = self.backgrounds[orientation].get_xy() + w, h = self.backgrounds[orientation].get_dimensions() + bx, by = self.blocks[0].spr.get_xy() + if orientation == 0: + width = self._turtle_window.width + + if bx != _BUTTON_SIZE: + dx = w - width + else: + dx = width - w + dy = 0 + else: + height = self._turtle_window.height + offset = self._turtle_window.toolbar_offset + + dx = 0 + if by != offset + _BUTTON_SIZE + _MARGIN: + dy = h - height + ICON_SIZE + else: + dy = height - h - ICON_SIZE + + for blk in self.blocks: + if blk.get_visibility(): + blk.spr.move_relative((dx, dy)) + + buttons[orientation].set_layer(TOP_LAYER) + if dx < 0 or dy < 0: + buttons[orientation + 5].set_layer(TOP_LAYER) + buttons[orientation + 3].hide() + else: + buttons[orientation + 5].hide() + buttons[orientation + 3].set_layer(TOP_LAYER) + + def _create_proto_blocks(self): + ''' + Create the proto blocks that will populate this palette. + Reload the palette, but reuse the existing blocks. + If a block doesn't exist, add it. + ''' + for blk in self.blocks: + blk.spr.hide() + + preexisting_blocks = self.blocks[:] + self.blocks = [] + for name in palette_blocks[self._palette_index]: + # Did we already create this block? + preexisting_block = False + for blk in preexisting_blocks: + if blk.name == name: + self.blocks.append(blk) + preexisting_block = True + break + + # If not, create it now. + if not preexisting_block: + self.blocks.append(Block(self._turtle_window.block_list, + self._turtle_window.sprite_list, + name, 0, 0, 'proto', [], + PALETTE_SCALE)) + if name in hidden_proto_blocks: + self.blocks[-1].set_visibility(False) + else: + self.blocks[-1].spr.set_layer(PROTO_LAYER) + self.blocks[-1].unhighlight() + self.blocks[-1].resize() + + # Some proto blocks get a skin. + if name in block_styles['box-style-media']: + self._proto_skin(name + 'small', self.blocks[-1].spr) + elif name in PYTHON_SKIN: + self._proto_skin('pythonsmall', self.blocks[-1].spr) + elif len(self.blocks[-1].spr.labels) > 0: + self.blocks[-1].refresh() + + def _proto_skin(self, name, spr): + ''' Utility for creating proto block skins ''' + x, y = self._turtle_window._calc_image_offset(name, spr) + spr.set_image(self._turtle_window.media_shapes[name], _SKIN_IMAGE, + x, y) + + def _float_palette(self, spr): + ''' We sometimes let the palette move with the canvas. ''' + if self._turtle_window.running_sugar and \ + not self._turtle_window.hw in [XO1]: + spr.move_relative( + (self._turtle_window.activity.hadj_value, + self._turtle_window.activity.vadj_value)) + + def _trash_palette(self): + return 'trash' in palette_names and \ + self._palette_index == palette_names.index('trash') + + def layout_palette(self, regenerate=False, show=True): + ''' Layout prototypes in a palette. ''' + + offset = self._turtle_window.toolbar_offset + buttons = self._turtle_window.palette_button + orientation = self._turtle_window.orientation + w = PALETTE_WIDTH + h = PALETTE_HEIGHT + + if orientation == HORIZONTAL_PALETTE: + x, y, max_w = self._horizontal_layout( + _BUTTON_SIZE, offset + _MARGIN, self.blocks) + if self._trash_palette(): + blocks = [] # self.blocks[:] + for blk in self._turtle_window.trash_stack: + blocks.append(blk) + x, y, max_w = self._horizontal_layout(x + max_w, y, blocks) + w = x + max_w + _BUTTON_SIZE + _MARGIN + if show: + buttons[2].move((w - _BUTTON_SIZE, offset)) + buttons[4].move((_BUTTON_SIZE, offset)) + buttons[6].move((_BUTTON_SIZE, offset)) + else: + x, y, max_h = self._vertical_layout( + _MARGIN, offset + _BUTTON_SIZE + _MARGIN, self.blocks) + if self._trash_palette(): + blocks = [] # self.blocks[:] + for blk in self._turtle_window.trash_stack: + blocks.append(blk) + x, y, max_h = self._vertical_layout(x, y + max_h, blocks) + h = y + max_h + _BUTTON_SIZE + _MARGIN - offset + if show: + buttons[2].move((PALETTE_WIDTH - _BUTTON_SIZE, offset)) + buttons[3].move((0, offset + _BUTTON_SIZE)) + buttons[5].move((0, offset + _BUTTON_SIZE)) + + self._make_background(0, offset, w, h, regenerate) + + if show: + for blk in self.blocks: + if blk.get_visibility(): + blk.spr.set_layer(PROTO_LAYER) + else: + blk.spr.hide() + + buttons[2].save_xy = buttons[2].get_xy() + self._float_palette(buttons[2]) + self.backgrounds[orientation].set_layer(CATEGORY_LAYER) + self.display_palette_shift_buttons() + + if self._trash_palette(): + for blk in self._turtle_window.trash_stack: + for gblk in find_group(blk): + gblk.spr.set_layer(PROTO_LAYER) + + svg = SVG() + self.backgrounds[orientation].set_shape( + svg_str_to_pixbuf(svg.palette(w, h))) + + def _make_background(self, x, y, w, h, regenerate=False): + ''' Make the background sprite for the palette. ''' + orientation = self._turtle_window.orientation + + if regenerate and not self.backgrounds[orientation] is None: + self.backgrounds[orientation].hide() + self.backgrounds[orientation] = None + + if self.backgrounds[orientation] is None: + svg = SVG() + self.backgrounds[orientation] = \ + Sprite(self._turtle_window.sprite_list, x, y, + svg_str_to_pixbuf(svg.palette(w, h))) + self.backgrounds[orientation].save_xy = (x, y) + + self._float_palette(self.backgrounds[orientation]) + + if orientation == 0 and w > self._turtle_window.width: + self.backgrounds[orientation].type = \ + 'category-shift-horizontal' + elif orientation == 1 and \ + h > self._turtle_window.height - ICON_SIZE: + self.backgrounds[orientation].type = \ + 'category-shift-vertical' + else: + self.backgrounds[orientation].type = 'category' + + ''' + if self._trash_palette(): + svg = SVG() + self.backgrounds[orientation].set_shape( + svg_str_to_pixbuf(svg.palette(w, h))) + ''' + + def _horizontal_layout(self, x, y, blocks): + ''' Position prototypes in a horizontal palette. ''' + offset = self._turtle_window.toolbar_offset + max_w = 0 + + for blk in blocks: + if not blk.get_visibility(): + continue + + w, h = _width_and_height(blk) + if y + h > PALETTE_HEIGHT + offset: + x += int(max_w + 3) + y = offset + 3 + max_w = 0 + + (bx, by) = blk.spr.get_xy() + dx = x - bx + dy = y - by + for g in find_group(blk): + g.spr.move_relative((int(dx), int(dy))) + g.spr.save_xy = g.spr.get_xy() + self._float_palette(g.spr) + y += int(h + 3) + if w > max_w: + max_w = w + + return x, y, max_w + + def _vertical_layout(self, x, y, blocks): + ''' Position prototypes in a vertical palette. ''' + row = [] + row_w = 0 + max_h = 0 + + for blk in blocks: + if not blk.get_visibility(): + continue + + w, h = _width_and_height(blk) + if x + w > PALETTE_WIDTH: + # Recenter row. + dx = int((PALETTE_WIDTH - row_w) / 2) + for r in row: + for g in find_group(r): + g.spr.move_relative((dx, 0)) + g.spr.save_xy = (g.spr.save_xy[0] + dx, + g.spr.save_xy[1]) + row = [] + row_w = 0 + x = 4 + y += int(max_h + 3) + max_h = 0 + + row.append(blk) + row_w += (4 + w) + + (bx, by) = blk.spr.get_xy() + dx = int(x - bx) + dy = int(y - by) + for g in find_group(blk): + g.spr.move_relative((dx, dy)) + g.spr.save_xy = g.spr.get_xy() + self._float_palette(g.spr) + + x += int(w + 4) + if h > max_h: + max_h = h + + # Recenter last row. + dx = int((PALETTE_WIDTH - row_w) / 2) + for r in row: + for g in find_group(r): + g.spr.move_relative((dx, 0)) + g.spr.save_xy = (g.spr.save_xy[0] + dx, g.spr.save_xy[1]) + + return x, y, max_h + + def _hide_palette_shift_buttons(self): + buttons = self._turtle_window.palette_button + for i in range(4): + buttons[i + 3].hide() + + def display_palette_shift_buttons(self): + ''' Palettes too wide (or tall) for the screen get a shift button. ''' + self._hide_palette_shift_buttons() + + buttons = self._turtle_window.palette_button + orientation = self._turtle_window.orientation + + if self.backgrounds[orientation].type == 'category-shift-horizontal': + buttons[3].set_layer(CATEGORY_LAYER) + elif self.backgrounds[orientation].type == 'category-shift-vertical': + buttons[4].set_layer(CATEGORY_LAYER) |