在较小表面内移动的表面不会显示以前隐藏的组件

时间:2019-06-06 22:29:11

标签: python pygame

我正在编码一些自定义的GUI对象,以便在pygame菜单中使用,而在编码一个可滚动框时却遇到错误。

此框通过在较小的表面内移动表面(其中包含滚动时移动的组件)来工作,该表面的作用类似于封闭表面的窗口。大部分表面正确显示:最初可见的内表面内容(适合窗口表面的部分)正确显示,但是当移动内表面以显示以前隐藏的组件时,它们不会显示,则初始可见移动正确并在返回时显示。

我认为问题在于外表面的修剪区域,认为仅应显示已经显示的组件,而其他组件仍处于隐藏状态,但我不知道。

自定义GUI组件始终具有Rect(返回该组件的边界rect)和Draw(将组件抹到屏幕上)功能。 这是滚动区域(及其父类)的代码:

class ScrollArea(BaseComponent):
"Implements a section of screen which is operable by scroll wheel"
def __init__(self,surface,rect,colour,components):
    """surface is what this is drawn on
    rect is location + size
    colour is colour of screen
    components is iterable of components to scroll through (they need Draw and Rect functions), this changes the objects location and surface
    """
    super().__init__(surface)
    self.rect = pygame.Rect(rect)
    self.colour = colour
    self.components = components
    self.Make()

def HandleEvent(self, event):
    "Pass events to this; it enables the area to react to them"
    if event.type == pygame.MOUSEBUTTONDOWN and self.rect.collidepoint(event.pos) and self._scroll_rect.h > self.rect.h:
        if event.button == 4: self.scroll_y = min(self.scroll_y + 15,self._scroll_y_min)
        if event.button == 5: self.scroll_y = max(self.scroll_y - 15,self._scroll_y_max)

def Make(self):
    "Updates the area, activates any changes made"
    _pos = self.rect.topleft
    self._sub_surface = pygame.Surface(self.rect.size,pygame.SRCALPHA)
    self.rect = pygame.Rect(_pos,self._sub_surface.get_rect().size)
    self._sub_surface.unlock()#hopefully fixes issues

    self._scroll_surf = pygame.Surface(self.rect.size)
    self._scroll_rect = self._scroll_surf.get_rect()
    scroll_height = 5
    for component in self.components:
        component.surface = self._scroll_surf
        component.Rect().y = scroll_height
        component.Rect().x = 5
        component.Draw()
        scroll_height += component.Rect().h + 5
    self._scroll_rect.h = max(self.rect.h,scroll_height)
    self.scroll_y = 0
    self._scroll_y_min = 0
    self._scroll_y_max = -(self._scroll_rect.h - self.rect.h)

def Draw(self):
    "Draw the area and its inner components"
    self._sub_surface.fill((255, 255, 255, 0))
    self._sub_surface.blit(self._scroll_surf,(0,self.scroll_y))
    pygame.draw.rect(self._sub_surface,self.colour,((0,0),self.rect.size),2)
    self.surface.blit(self._sub_surface,self.rect.topleft)

def Rect(self):
    "Return the rect of this component"
    return self.rect

class BaseComponent:
    def __init__(self,surface):
        "surface is what this is drawn on"
        self.surface = surface
    def HandleEvent(self,event):
        "Pass events into this for the component to react ot them"
        raise NotImplementedError()
    def Make(self):
        "Redo calculations on how component looks"
        raise NotImplementedError()
    def Draw(self):
        "Draw component"
        raise NotImplementedError()
    def ReDraw(self):
        "Call Make then draw functions of component"
        self.Make()
        self.Draw()
    def Rect(self):
        "Return the rect of this component"
        raise NotImplementedError()

为了测试这一点,我使用了以下代码和一个标签组件:

screen_width = 640
screen_height = 480
font_label = pygame.font.Font("freesansbold.ttf",22)
screen = pygame.display.set_mode((screen_width,screen_height))
grey = (125,125,125)
def LoadLoop():
    #objects
    scroll_components = []
    for i in range(20):
        scroll_components.append(Components.Label(screen,(0,0),str(i),font_label,grey))
    scroll_area = Components.ScrollArea(screen,Components.CenterRect(screen_width/2,3*screen_height/16 + 120,300,200),grey,scroll_components)
clock = pygame.time.Clock()
running = True
while running:
    #events
    for event in pygame.event.get():
        scroll_area.HandleEvent(event)
        if event.type == pygame.QUIT:
            running = False
            pygame.quit()
            exit()
    #graphics        
    screen.fill(black)
    scroll_area.Draw(components)
    #render
    pygame.display.update()
    clock.tick(60)

这是标签组件的代码(它基本上只是将文本打印到屏幕上,并以指定的位置为中心):

class Label(BaseComponent):
    "Class which implements placing text on a screen"
    def __init__(self,surface,center,text,font,text_colour):
        """surface is what this is drawn on
        center is the coordinates of where the text is to be located
        text is the text of the label
        font is the font of the label
        text_colour is the text's colour
        """
        super().__init__(surface)
        self.center = center
        self.text = text
        self.font = font
        self.text_colour = text_colour
        self.Make()
    def HandleEvent(self,event):
        "Labels have no events they react to,\nso this does nothing"

    def Make(self):
        "(Re)creates the label which is drawn,\nthis must be used if any changes to the label are to be carried out"
        self._text_surf = self.font.render(self.text, True, self.text_colour)
        self._text_rect = self._text_surf.get_rect()
        self._text_rect.center = self.center

    def Draw(self):
        "Draw the label , will not react to any changes made to the label"
        self.surface.blit(self._text_surf,self._text_rect)
    def Rect(self):
        "Return the rect of this component"
        return self._text_rect

这是此代码产生的窗口: Before scrolling After scrolling

我也使用不同大小的ScrollArea进行了处理,其中一个Labels穿过底部放置,将其切成两半,滚动时仍保留切口。 请帮忙。

1 个答案:

答案 0 :(得分:0)

公约注释

首先,在conventions上做一个旁注:类名应以大写字母开头,函数和方法名应全部为小写。
它们是约定,因此您可以随意不遵循它们,但是遵循约定将使您的代码对习惯它们的人们更易读。

快速修复

错误出在ScrollArea.Make()方法中。仔细看这两行:

self._sub_surface = pygame.Surface(self.rect.size,pygame.SRCALPHA)
self._scroll_surf = pygame.Surface(self.rect.size)

self._sub_surface是滚动区域窗口的表面。 self._scroll_surf是滚动表面。后者应该更高,但是您将它们设置为相同的大小(相同的宽度很好,而相同的高度不是)。
显然,当您遍历组件列表以使标签变色时,self._sub_surface之外的元素也在self._scroll_surf之外,因此根本不会变色。您应该提高self._scroll_surf。尝试例如:

self._scroll_surf = pygame.Surface((self.rect.width, self.rect.height*10)

更好的办法是估计包含所有标签的适当高度,应该为scroll_height,但是稍后在该方法中对其进行计算,因此您应该弄清楚该部分的正确处理方法。

一般建议

通常,我认为您在这里遇到设计问题:

for i in range(20):
    scroll_components.append(Label(screen,(0,0),str(i),font_label,grey))
scroll_area = ScrollArea(screen, pygame.Rect(screen_width/2,3*screen_height/16 + 120,300,200),grey,scroll_components)

创建每个标签时,将screen方法作为参考面传递给Draw
但是,这些标签应在您的scroll_surf的{​​{1}}上涂白。但是您不能这样做,因为还没有实例化ScrollArea,并且您不能在滚动区域之前实例化,因为您需要将Labels作为参数传递。

实际上,在ScrollArea方法中,您用ScrollArea.Make() Surface覆盖了每个标签surface属性。

我认为将字符串列表传递给_scroll_surf并让ScrollArea方法创建标签会更好。
看起来补丁更少,更连贯。