class GraphicButton(BaseButton): stylesheet = dictUnion(BaseButton.stylesheet, {DEFAULT_GRAPHIC: None, #default to none DEFAULT_PRESSED_GRAPHIC: None}) def __init__(self, *args, central_graphic = None, **kwargs): super().__init__(*args, **kwargs) self.pressed_graphic = None #overwrite these self.unpressed_graphic = None #overwrite these self.setCentralWidget(ScreenElement(parent_stylesheet=self.stylesheet)) if central_graphic: self.setButtonGraphic(central_graphic) self.setWidth(self.getOrderedStylesheetData(DEFAULT_BUTTON_WIDTH, DEFAULT_WIDTH, default=self.getCentralWidget().getSpriteWidth())) self.setHeight(self.getOrderedStylesheetData(DEFAULT_BUTTON_HEIGHT, DEFAULT_HEIGHT, default=self.getCentralWidget().getSpriteHeight())) def setButtonGraphic(self, val): cw = self.getCentralWidget() if cw: cw.setSprite(val) #overwritten stuff def setSprite(self, *args, **kwargs): pass #disallow this
def __init__(self, sprite_path = None, children = (), stylesheet = None, parent_stylesheet = None, **kwargs): super().__init__(**kwargs) self._children = {} self._funcs = {} self._focus_listeners = set() self._focused_child_id = None self._parent = None self._layer = 0 self._mouse_over = False self._element_position = (0,0) self._lock_input = set() self._hide_lock = set() for string in list(self.keybind_map.keys()) + list(self.other_event_keys): try: self._funcs[string] = getattr(self, string) except AttributeError: pass try: newfunc = kwargs[string] try: oldfunc = self._funcs[string] def combinedfunc(*args, **kwargs): #create a pseudo super rv1 = oldfunc() rv2 = newfunc() return rv1 or rv2 self._funcs[string] = combinedfunc except KeyError: self._funcs[string] = newfunc except KeyError: pass section_bounds_query_key = QUERY_MOUSE_MOVE_SECTION_BOUNDS + self.id if self._funcs.keys() & (MOUSEENTER_KEY, MOUSELEAVE_KEY, MOUSEOVER_KEY): #if we have any of these self.addSectionalMouseMoveListener(self._mouseMove, priority = self.getEventListenerPriority, query_key = section_bounds_query_key) self.addQueryListener(section_bounds_query_key, self.getExtents) if parent_stylesheet: self.stylesheet = dictUnion(parent_stylesheet, self.stylesheet) if stylesheet: self.updateStyleSheet(stylesheet) width = self.stylesheet.get(DEFAULT_WIDTH, None) if width is not None: self.setWidth(width) height = self.stylesheet.get(DEFAULT_HEIGHT, None) if height is not None: self.setHeight(height) self.setSprite(sprite_path or self.stylesheet.get(DEFAULT_GRAPHIC, None)) for child in children: #add starting children self.addChild(child)
def addChildApplyStylesheet(self, newchildtype, *args, parent_stylesheet = {},update_layers = True, **kwargs): return self.addChild(newchildtype(*args, parent_stylesheet=dictUnion(self.stylesheet, parent_stylesheet), **kwargs), update_layers)
def updateStyleSheet(self, sheet): self.stylesheet = dictUnion(self.stylesheet, sheet) #merge them. sheet's keys will be used preferentially
class BaseButton(Container): stylesheet = dictUnion(Container.stylesheet, {}) def __init__(self, sprite_path = None, width = None, height = None, *args, **kwargs): super().__init__(sprite_path, **kwargs) #defaults self._central_widget_id = None self._highlight_id = self.addChildApplyStylesheet(ScreenElement, self.stylesheet.get(DEFAULT_HIGHLIGHT_GRAPHIC, tuple(FULL_MISC_PATH + ["menugraphics", "hover.png"]))) highlight = self.getHighlight() highlight.setSpriteShow(False) highlight.setSpriteCenter(True,True) highlight.setSpriteFollowCamera(True) sprite_path = sprite_path or self.getOrderedStylesheetData(DEFAULT_BUTTON_GRAPHIC, DEFAULT_GRAPHIC, default=tuple(FULL_MISC_PATH + ["menugraphics", "brown_button.bordered"])) if sprite_path is not None: self.setSprite(sprite_path) if width is None: width = self.getOrderedStylesheetData(DEFAULT_BUTTON_WIDTH, DEFAULT_WIDTH, default=80) if width is not None: self.setWidth(width) if height is None: height = height or self.getOrderedStylesheetData(DEFAULT_BUTTON_HEIGHT, DEFAULT_HEIGHT, default=24) if height is not None: self.setHeight(height) self.setBorder(*self.getOrderedStylesheetData(DEFAULT_BUTTON_BORDER, DEFAULT_BORDER, default=(4,4))) self.unpressed_graphic = sprite_path self.pressed_graphic = self.stylesheet.get(DEFAULT_PRESSED_GRAPHIC, tuple(FULL_MISC_PATH + ["menugraphics", "brown_button_pressed.bordered"])) self.setSpriteFollowCamera(True) def getHighlight(self): try: return self.getChild(self._highlight_id) except AttributeError: #not fully initialized yet return None def addHighlight(self): highlight = self.getHighlight() if highlight: highlight.setSpriteShow(True) def removeHighlight(self): highlight = self.getHighlight() if highlight: highlight.setSpriteShow(False) def setPressedDown(self): if self.pressed_graphic: self.setSprite(self.pressed_graphic) def setUnpressed(self): if self.pressed_graphic and self.unpressed_graphic: self.setSprite(self.unpressed_graphic) def _updateHighlight(self): highlight = self.getHighlight() if highlight: highlight.setDimensions(*self.getDimensions()) def setCentralWidget(self, widget): self._central_widget_id = self.addChild(widget) cw = self.getCentralWidget() cw.setSpriteCenter(True, True) cw.setSpriteFollowCamera(True) return self._central_widget_id def getCentralWidget(self): try: return self.getChild(self._central_widget_id) except KeyError: return None def showElement(self, key = None): super().showElement(key=key) if not self.focus: self.removeHighlight() #event stuff def mouseenter(self): self.setSelfFocused() def mouseover(self, *args, **kwargs): if not self.isFocused(): self.setSelfFocused() def mouseleave(self): self.setUnpressed() def confirmhold(self): self.setPressedDown() def confirm(self): self.setUnpressed() def gainfocus(self): self.addHighlight() def losefocus(self): self.setUnpressed() self.removeHighlight() #overwritten stuff def _updateContainerPositions(self): super()._updateContainerPositions() central_widget = self.getCentralWidget() if central_widget: self.setDimensions(*map(max, self.getDimensions(), map(operator.add, central_widget.getDimensions(), map(operator.mul, self.getBorder(), (2,2))))) self._updateHighlight() def getAnchorPoint(self): extents = self.getExtents() #set to middle of button return (extents[0] + extents[1])/2, (extents[2] + extents[3])/2 def setSpriteCenter(self, *args, **kwargs): super().setSpriteCenter(*args, **kwargs) self._applyChildrenPositions() self.tellParentToUpdate() def _applyChildrenPositions(self): super()._applyChildrenPositions() self._updateHighlight() self.tellParentToUpdate() def setWidth(self, *args, **kwargs): super().setWidth(*args, **kwargs) self._updateHighlight() self.tellParentToUpdate() def setHeight(self, *args, **kwargs): super().setHeight(*args, **kwargs) self._updateHighlight() self.tellParentToUpdate() def updateChildrenLayers(self, lastlayer = None): base_layer = lastlayer or self.getLayer() for child in self.getAllChildren(): child.setLayer(base_layer) highlight = self.getHighlight() if highlight: self.getHighlight().setLayer(base_layer + 1) return base_layer + 1
class Container(ScreenElement): stylesheet = dictUnion(ScreenElement.stylesheet, {DEFAULT_BORDER: (0,0)}) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.border = self.stylesheet.get(DEFAULT_BORDER, (0,0)) self.spacing = None self.strict_spacing = False self._update_positions = False self._currently_updating = False def setBorder(self, x = None, y = None): if x is None: x = self.border[0] if y is None: y = self.border[1] self.border = (x,y) self.updateContainerPositions() def getBorder(self): return self.border def getSpacing(self): return self.spacing def setStrictSpacing(self, val): if val != self.strict_spacing: self.strict_spacing = val self.delayUpdatePositions() def getStrictSpacing(self): return self.strict_spacing def delayUpdatePositions(self): if not self._update_positions and not self._currently_updating: try: self._update_positions = self.delay(self.updateContainerPositions, self.getLayer) except DelayedEventException: pass def updateContainerPositions(self): if not self._currently_updating: #prevent infinite loops self.undelay(self._update_positions) self._update_positions = False self._currently_updating = True self._updateContainerPositions() self._currently_updating = False def _updateContainerPositions(self): pass def containerPositionsUpdated(self): return not bool(self._update_positions) #template functions def setSpacing(self, *args, **kwargs): pass def getChildKey(self, *args, **kwargs): return None def getChildByKey(self, *args, **kwargs): return None #overwritten stuff def getWidth(self): if not self.containerPositionsUpdated(): self.updateContainerPositions() return super().getWidth() def getHeight(self): if not self.containerPositionsUpdated(): self.updateContainerPositions() return super().getHeight() def getExtents(self): if not self.containerPositionsUpdated(): self.updateContainerPositions() return super().getExtents() def getAnchorPoint(self): #return bottom left corner extents = self.getExtents() return extents[0], extents[2] def removeAllChildren(self): super().removeAllChildren() self.delayUpdatePositions() def setDimensions(self, *args, **kwargs): super().setDimensions(*args, **kwargs) self._applyChildrenPositions() def updateElement(self): super().updateElement() self.delayUpdatePositions()
class MenuTemplate(ScreenElement): #not to be used without a container #update event list other_event_keys = ScreenElement.other_event_keys + (INTERLINK_EVENT_LEFT, INTERLINK_EVENT_RIGHT, INTERLINK_EVENT_UP, INTERLINK_EVENT_DOWN) button_type = LabelButton focus_first_button = True stylesheet = dictUnion(ScreenElement.stylesheet, {}) def __init__(self, sprite_path = None, button_type = None, **kwargs): super().__init__(**kwargs) sprite_path = sprite_path or self.getOrderedStylesheetData(DEFAULT_MENU_GRAPHIC, DEFAULT_GRAPHIC, default=tuple(FULL_MISC_PATH + ["menugraphics", "menu0.bordered"])) if sprite_path: self.setSprite(sprite_path) if button_type is not None: self.button_type = button_type self._first_button = True self._buttons = set() self._menu_interlinks = {} #overwrites self.border = self.stylesheet.get(DEFAULT_BORDER, None) or self.stylesheet.get(DEFAULT_MENU_BORDER, None) or (8,8) #defaults self.setSpriteCenter(True, True) self.setSpriteFollowCamera(True) self.setPos(*map(operator.truediv, getWindowDimensionsScaled(), (2,2))) self.addQueryListener(self.getEventID(MENU_LOOKUP_KEY), lambda: self) def setButtonType(self, newtype): #will NOT regenerate old buttons! self.button_type = newtype def addButton(self, *args, **kwargs): ID = self.addChildApplyStylesheet(self.button_type, *args, **kwargs) self._updateFirstButton(ID) self._buttons.add(ID) return ID def getButton(self, *args, **kwargs): #alias for getChildByKey return self.getChildByKey(*args, **kwargs) def getAllButtons(self): return [self.getChild(ID) for ID in self._buttons] def _updateFirstButton(self, ID): if self._first_button: if self.focus_first_button: self.setFocus(ID) self._first_button = False def interlinkMenu(self, direction, othermenuid): othermenu = self.queryMenuByID(othermenuid) if othermenu: self._interlinkMenu(direction, othermenu.id) othermenu._interlinkMenu(DIRECTION_FLIP_LOOKUP[direction], self.id) def _interlinkMenu(self, direction, othermenuid): try: self._menu_interlinks[direction].add(othermenuid) except KeyError: self._menu_interlinks[direction] = set() self._menu_interlinks[direction].add(othermenuid) def clearInterlink(self, direction, othermenuid): othermenu = self.queryMenuByID(othermenuid) if othermenu: self._clearInterlink(direction, othermenu.id) othermenu._clearInterlink(DIRECTION_FLIP_LOOKUP[direction], self.id) def _clearInterlink(self, direction, othermenuid): try: self._menu_interlinks[direction].discard(othermenuid) except KeyError: pass def clearAllInterlinks(self): for direction, menuref in self.getAllInterlinkedMenus(): menuref._clearInterlink(DIRECTION_FLIP_LOOKUP[direction], self.id) self._menu_interlinks.clear() def _interlinkActivate(self, direction): if direction in self._menu_interlinks: pos = self.getFocusedChild().getCenterPosition() mindistance = math.inf mindistance_menu_ref = None mindistance_button_id = None for menu_id in self._menu_interlinks[direction]: menu_ref = self.queryMenuByID(menu_id) if menu_ref: for button in menu_ref.getAllButtons(): other_pos = button.getCenterPosition() distance = math.hypot(pos[0] - other_pos[0], pos[1] - other_pos[1]) if distance < mindistance: mindistance = distance mindistance_menu_ref = menu_ref mindistance_button_id = button.id if mindistance_menu_ref: #TODO: make this suck less self.clearFocus() self.getParent().clearFocus() mindistance_menu_ref.setFocus(mindistance_button_id) mindistance_menu_ref.setSelfFullyFocused() return True return False def getAllInterlinkedMenus(self): menulist = [] for direction, menuset in self._menu_interlinks.items(): for menuid in menuset: menuref = self.queryMenuByID(menuid) if menuref: menulist.append((direction, menuref)) return menulist def queryMenuByID(self, menu_id): try: menu_id.id except AttributeError: return callQuery(self._getEventID(menu_id, MENU_LOOKUP_KEY)) else: return menu_id #is a reference #input stuff def mouseover(self, *args, **kwargs): if not self.isInputLocked(): self.setSelfFullyFocused() #overwritten stuff def setFocus(self, *args, **kwargs): super().setFocus(*args, **kwargs) if self.hasFocusedChild(): for direction, menuref in self.getAllInterlinkedMenus(): menuref.clearFocus() def removeChild(self, *args, **kwargs): ID = super().removeChild(*args, **kwargs) self._buttons.discard(ID) return ID def removeAllChildren(self, *args, **kwargs): super().removeAllChildren(*args, **kwargs) self._buttons.clear() def setSpriteCenter(self, *args, **kwargs): super().setSpriteCenter(*args, **kwargs) self._applyChildrenPositions() def _updateContainerPositions(self, *args, **kwargs): maxwidth = 0 maxheight = 0 for button in self.getAllButtons(): try: button.updateContainerPositions except: pass else: button.updateContainerPositions() maxwidth = max(maxwidth, button.getWidth()) maxheight = max(maxheight, button.getHeight()) for button in self.getAllButtons(): button.setDimensions(maxwidth, maxheight) super()._updateContainerPositions(*args, **kwargs)