def paint_text(self, parent_offset, surface): mid = len(self.line.current)/2 midvalues = self.line.current[mid:mid+2] if len(midvalues) < 2: # not enough control points to figure out where to put text return midsrc, middst = midvalues angle = ((middst - midsrc).angle() % (2*math.pi)) if 1*(2*math.pi)/4 < angle < 3*(2*math.pi)/4: angle += math.pi angle %= 2*math.pi t = self.rendered_text text_centering_vector = Point((-t.get_width()/2, -t.get_height())) text_centering_length = text_centering_vector.norm() text_centering_angle = text_centering_vector.angle() rt, coors = rotate_surface(t, angle) # coors[0] is where the original topleft is in the # rotated surface: topleft = coors[0] desired_topleft = midsrc + from_polar(text_centering_angle+angle, text_centering_length) pos = tuple(desired_topleft - topleft + parent_offset) surface.blit(rt, map(int, pos))
def SurroundLayout(width, height, source_widget, subwidgets_of, scale=1, subwidgets_center_offset=None): if subwidgets_center_offset is None: subwidgets_center_offset = Point(0,source_widget.size.norm()*scale) span = pi else: span = pi source_widget.scale = scale subwidgets = reversed(sorted((widget.order.sublevel, widget) for widget in list(subwidgets_of(source_widget)))) subwidgets = list(subwidgets) num = len(subwidgets) if num <= 1: return center_angle = subwidgets_center_offset.angle() - (span/2) radius = subwidgets_center_offset.norm() angle_step = span/(num-1) center = source_widget.pos for i,(sublevel, subwidget) in enumerate(subwidgets): angle = center_angle + angle_step*i subwidget.pos = center + Point.from_polar(angle, radius) new_offset = Point.from_polar(angle, radius/3.0) SurroundLayout(width, height, subwidget, subwidgets_of, scale=scale*0.8, subwidgets_center_offset=new_offset)
class Widget(object): MAX_VEL = 20 def __init__(self, size, pos = None, order=None, hover_color=(120,120,250), unhover_color=(20,20,150)): if pos is None: pos = Point(0,0) if order is None: order = Order() self.target_pos = pos.shallow_copy() self._size = size self.order = order self._pos = pos.shallow_copy() self.target_scale = 1 self._scale = 1 self.visible = True self.size_margin = 10 self.speed = Point(0.1,0.1) self.scale_speed = 0.1 self.autogrow_to_fit_text = True self.rendered_text_lines = None self.text_lines = [] self._target_size = self._size.shallow_copy() self.font_size = 12 self._prev_font_scale = 12 self._prev_text_lines = self.text_lines[:] # node for topology self.node = Graph.Node(self) # node for layout hints self.puller_node = Graph.Node(self) self.pusher_node = Graph.Node(self) self.update_font() self.hover_color = hover_color self.unhover_color = unhover_color self.border_color = hover_color self.hover_out() self.accel = Point(0,0) self.vel = Point(0,0) def in_bounds(self, pos): s = self._scaled_size() if ((pos.x > self._pos.x) and (pos.y > self._pos.y) and (pos.x < self._pos.x + s.x) and (pos.y < self._pos.y + s.y)): return True return False def set_pos(self, pos): self.target_pos = pos.shallow_copy() def get_pos(self): return self.target_pos pos = property(get_pos, set_pos) def set_scale(self, scale): self.target_scale = scale self.update_target_text_scale() def get_scale(self): return self.target_scale scale = property(get_scale, set_scale) def set_size(self, size): self._size = size/self.target_scale def get_size(self): if not self.text_lines: return self._size*self.target_scale return self._target_size size = property(get_size, set_size) def update_pos(self): #self._pos += (self.target_pos - self._pos)*self.speed global_scale = 0.5 self.accel = Point(0,0) #(self.target_pos - self._pos)*self.speed for puller in self.puller_node.iter_all_connections(): w = puller.value this_scale = global_scale if w in [n.value for n in self.node.connections['out']]: # Decrease forces! this_scale = global_scale*2.0 vec = (w.pos - self.pos) r = vec.norm() if r==0: continue vec = vec*r*(1/(self.scale*12.0*this_scale)) if vec.norm() > 15: # don't let them get too close! self.accel += vec else: self.accel -= vec for pusher in self.pusher_node.iter_all_connections(): w = pusher.value vec = (w.pos - self.pos) r = vec.norm() if r==0: continue vec = vec*(1/r)*(self.scale*1500*global_scale) if vec.norm() > 15: # don't let them get too close! self.accel -= vec else: self.accel += vec self.vel += self.accel self.vel *= 0.02 # "friction" if (self.vel.norm() > self.MAX_VEL): self.vel = Point.from_polar(self.vel.angle(), self.MAX_VEL) self.pos += self.vel self._pos = self.pos def update_scale(self): self._scale += (self.target_scale - self._scale)*self.scale_speed self.update_font() #print self._scale def update_font(self): if not self.text_lines: return font_scale = int(self.font_size*self._scale*2) if (self.text_lines == self._prev_text_lines) and (font_scale == self._prev_font_scale): return self._prev_text_lines = self.text_lines self._prev_font_scale = font_scale self.font = self.get_font(font_scale) self.rendered_text_lines = [] mw, mh = 0, 0 linesize = self.font.get_linesize() for i, line in enumerate(self.text_lines): rendered_line = self.font.render(line, True, (250,250,250,150)) self.rendered_text_lines.append((rendered_line, linesize*i)) w,h = self.font.size(line) mw = max(mw, w) mh += linesize if self.autogrow_to_fit_text: self._size = Point(mw, mh) def _scaled_size(self): if not self.text_lines: return self._size*self._scale return self._size def paint(self, surface): if not self.visible: return #self.set_text(str(self.order.sublevel)) self.update_scale() self.update_pos() s = self._scaled_size() self.draw_connections(surface) pygame.draw.rect(surface, self.color, (self._pos.x, self._pos.y, s.x, s.y), 0) pygame.draw.rect(surface, self.border_color, (self._pos.x, self._pos.y, s.x, s.y), 2) #pygame.draw.ellipse(surface, self.color, (self._pos.x, self._pos.y, s.x, s.y), 3) if self.rendered_text_lines: for rendered_text_line, y in self.rendered_text_lines: surface.blit(rendered_text_line, (self._pos.x, self._pos.y+y)) def draw_connections(self, surface): s = self._scaled_size() my_pos = self._pos + Point(s.x/2, s.y/2) # Draw "pull" connections for widget_node in self.puller_node.iter_all_connections(): widget = widget_node.value if not widget.visible: continue other_pos = widget._pos + Point(widget._scaled_size().x/2, widget._scaled_size().y/2) pygame.draw.aalines(surface, (200,20,50,100), False, (my_pos.as_tuple(), other_pos.as_tuple()), True) # draw topological connections for widget_node in self.node.connections['out']: widget = widget_node.value other_pos = widget._pos + Point(widget._scaled_size().x/2, widget._scaled_size().y/2) pygame.draw.aalines(surface, (200,220,250,100), False, (my_pos.as_tuple(), other_pos.as_tuple()), True) ## for widget_node in self.pusher_node.iter_all_connections(): ## widget = widget_node.value ## other_pos = widget._pos + Point(widget._scaled_size().x/2, widget._scaled_size().y/2) ## pygame.draw.aalines(surface, (20,220,50,100), False, (my_pos.as_tuple(), other_pos.as_tuple()), True) @staticmethod @Func.cached def get_font(font_size): return pygame.font.SysFont('serif',font_size) def update_target_text_scale(self): if not self.text_lines: return mw,mh = 0,0 for line in self.text_lines: w,h = self.get_font(int(self.font_size*self.target_scale*2)).size(line) mw = max(mw, w) mh += h self._target_size = Point(mw,mh) def set_text(self, text_lines): self.text_lines = text_lines[:] if not self.text_lines: self.rendered_text_lines = [] return self.update_target_text_scale() self.update_font() def set_text_line(self, num, line): if num >= len(self.text_lines): self.text_lines.extend(['']*(num-len(self.text_lines)+1)) self.text_lines[num] = line self.set_text(self.text_lines) def hover_in(self): self.color = self.hover_color def hover_out(self): self.color = self.unhover_color