def __init__(self, text = '', pos=None, size=None, font_size=40, start_pos=None): self.size = MovingValue(Point((20,20)), get_default(size, Point((20,20)))) pos = get_default(pos, Point((0,0))) start_pos = get_default(start_pos, Point((0,0))) self.pos = MovingValue(start_pos, pos, step=0.3) self.widgets = [] # sub-widgets self.focused_widgets = [] self.hovered_widget = None self.trigger_lists = {'pre' : TriggerList(), 'post' : TriggerList(),} self.text = text self._font_size = font_size self.reset() self._init_params() self._init_event_triggers() self.max_undo = 25 # TODO kick this outta here #from Ellipse import Ellipse from Shapes.Rectangle import Rectangle self.shape = Rectangle(pygame.Rect(self.get_current_rect()))
def __init__(self, node, *args, **kw): self.node = node self.style = style._make_style() self.text_widget = TextEdit(self.style, self._get_text, self._set_text, [all_printable], {'\r':'\n'}) self.node.obs.add_observer(self, '_node_') Widget.__init__(self, *args, **kw) self._size = MovingValue(Point) self._pos = MovingValue(Point) from Shapes.Ellipse import Ellipse self.shape = Ellipse(pygame.Rect(0,0,1,1)) self.obs_loc = Observable() self._register_keys() self.reset_max_text_size() self.mouse_area = MouseArea(self.in_bounds)
def __init__(self, size, *args, **kw): Widget.__init__(self, *args, **kw) self._size = MovingValue(Point, final=Point(size)) self.nodes = set() self.edges = set() self.node_widgets = Dict() self.edge_widgets = Dict() self.sorted_widgets = SortedItems(self.node_widgets) self.layout = Layout() self.selected_widget_index = None self._update_index() self._node_link_start_pos = None self._node_link_source_node = None self._node_link_color = None self._register_keys() self._save_next_display_update = False
class GraphWidget(Widget): bg_color=(0,0,0) activated_bg_color=(10,10,20) key_create_node = Key(pygame.KMOD_CTRL, pygame.K_a) key_delete_node = Key(pygame.KMOD_CTRL, pygame.K_d) key_next_node = Key(0, pygame.K_TAB) key_prev_node = Key(pygame.KMOD_SHIFT, pygame.K_TAB) key_select_node_right = Key(0, pygame.K_RIGHT) key_select_node_left = Key(0, pygame.K_LEFT) key_select_node_up = Key(0, pygame.K_UP) key_select_node_down = Key(0, pygame.K_DOWN) key_connect = Key(pygame.KMOD_CTRL, pygame.K_RETURN) key_disconnect = Key(pygame.KMOD_CTRL, pygame.K_BACKSPACE) key_cycle_layout = Key(pygame.KMOD_CTRL, pygame.K_k) #key_export_dot = Key(pygame.KMOD_CTRL, pygame.K_) key_export_snapshot = Key(pygame.KMOD_CTRL, pygame.K_x) key_save = Key(pygame.KMOD_CTRL, pygame.K_s) key_load = Key(pygame.KMOD_CTRL, pygame.K_l) def __init__(self, size, *args, **kw): Widget.__init__(self, *args, **kw) self._size = MovingValue(Point, final=Point(size)) self.nodes = set() self.edges = set() self.node_widgets = Dict() self.edge_widgets = Dict() self.sorted_widgets = SortedItems(self.node_widgets) self.layout = Layout() self.selected_widget_index = None self._update_index() self._node_link_start_pos = None self._node_link_source_node = None self._node_link_color = None self._register_keys() self._save_next_display_update = False def __getstate__(self): d = Widget.__getstate__(self) del d['parenting_keymap'] return d def __setstate__(self, d): Widget.__setstate__(self, d) self._register_keys() def _register_keys(self): self.parenting_keymap = Keymap() r = self.keymap.register_key r(self.key_create_node, keydown_noarg(self._create_new_node)) r(self.key_cycle_layout, keydown_noarg(self._cycle_layout_engine)) r(self.key_save, keydown_noarg(self._save)) r(self.key_load, keydown_noarg(self._load)) r(self.key_export_snapshot, keydown_noarg(self._export_snapshot)) self._set_next_keymap() def get_size(self): return tuple(self._size.current) def set_size(self, p): self._size.final = p size = property(get_size, set_size) def _node_connect(self, e): for node in e.source, e.target: if node not in self.nodes: self.add_node(node) if e not in self.edges: self.add_edge(e) def _node_disconnect(self, e): self.remove_edge(e) def update_edges_lines(self, widget, node): center = Point(widget.final_rect().center) for edge in node.connections['in']: edge_w = self.edge_widgets[edge] edge_w.line.final[-1] = center.copy() edge_w.line.reset() for edge in node.connections['out']: edge_w = self.edge_widgets[edge] edge_w.line.final[0] = center.copy() edge_w.line.reset() def _node_widget_loc_pos_set(self, widget, node, new_pos): self.update_edges_lines(widget, node) def _node_widget_loc_size_set(self, widget, node, new_size): self.update_edges_lines(widget, node) def add_edge(self, edge): edge.obs.add_observer(self, '_edge_') self.edges.add(edge) if edge.source not in self.node_widgets: self.add_node(edge.source) source = self.node_widgets[edge.source].final_rect().center if edge.target not in self.node_widgets: self.add_node(edge.target) target = self.node_widgets[edge.target].final_rect().center w = EdgeWidget(edge, partial(self.node_widgets.get), MovingLine(list, [Point(source), Point(target)])) self.edge_widgets[edge] = w self.update_layout() def remove_edge(self, edge): edge.obs.remove_observer(self) del self.edge_widgets[edge] self.edges.remove(edge) self.update_layout() def add_node(self, node): if node in self.nodes: return self.nodes.add(node) w = NodeWidget(node) self.node_widgets[node] = w node.obs.add_observer(self, '_node_') w.obs_loc.add_observer(self, '_node_widget_loc_', w, node) loop.loop.mouse_map.push_area(w.mouse_area, partial(self._widget_mouse_event, node, w)) # They might be adding a node that is connected to some other, # yet-to-be added nodes for edge in node.iter_all_connections(): self.add_edge(edge) self.update_layout() return w def remove_node(self, node): node.disconnect_all() w = self.node_widgets[node] loop.loop.mouse_map.remove_area(w.mouse_area) w.obs_loc.remove_observer(self) node.obs.remove_observer(self) del self.node_widgets[node] self.nodes.remove(node) self._update_index() if self._node_link_source_node: self._node_link_source_node = None self.update_layout() return w def generate_groups(self): groups = {'0':[]} for node in self.nodes: groups['0'].append(node) return groups def update_layout(self): groups = self.generate_groups() self.layout.update(groups, self.size, self.node_widgets, self.edge_widgets) def update(self): if self.node_widgets: import math # todo rewrite this shitty code sizes = {} slots = {} slot_places = {} average = 0 num = len(self.node_widgets) for w in self.node_widgets.values(): size = w.reset_max_text_size() sizes[w] = size for w, size in sizes.iteritems(): dist = int(round(math.log(size + 1)/2.)) slot = dist slots.setdefault(slot, size) slots[slot] = min(slots[slot],size) slot_places[w] = slot for w,slot in slot_places.iteritems(): size = sizes[w] new_max_size = slots[slot] if new_max_size < size: w.reset_max_text_size(new_max_size) w.update() ## for w in self.node_widgets.values(): ## w.reset_max_text_size() ## w.update() for w in self.edge_widgets.itervalues(): w.update() self._size.update() def _draw(self, surface, pos): for w in self.edge_widgets.values(): p = Point(pos) w._draw(surface, pos) for w in self.node_widgets.values(): # for our children, pos is the parent's pos offset # because of how NodeWidget works. w._draw(surface, pos) if self._node_link_start_pos is not None: n,w = self.selected() if w is not None: start_pos = self._node_link_start_pos() end_pos = self._node_link_end_pos() draw.line(surface, self._node_link_color, start_pos, end_pos) if self._save_next_display_update: self._save_next_display_update = None draw.save(surface.subsurface(pygame.Rect(pos, self.size)), self._save_next_display_update) def selected(self): if self.selected_widget_index is None: return None, None return self.sorted_widgets[self.selected_widget_index] def _set_next_keymap(self): self.keymap.set_next_keymap(self.parenting_keymap) if self.selected_widget_index is not None: self.parenting_keymap.set_next_keymap(self.selected()[1].keymap) r = self.parenting_keymap.register_key r(self.key_delete_node, keydown_noarg(self._delete_selected_node)) r(self.key_next_node, keydown_noarg(self._next_node)) r(self.key_prev_node, keydown_noarg(self._prev_node)) r(self.key_select_node_right, keydown_noarg(self._select_node_right)) r(self.key_select_node_left, keydown_noarg(self._select_node_left)) r(self.key_select_node_up, keydown_noarg(self._select_node_up)) r(self.key_select_node_down, keydown_noarg(self._select_node_down)) if self.key_connect not in self.parenting_keymap: r(self.key_connect, keydown_noarg(self._start_connect)) if self.key_disconnect not in self.parenting_keymap: r(self.key_disconnect, keydown_noarg(self._start_disconnect)) else: ur = self.parenting_keymap.unregister_key ur(self.key_next_node) ur(self.key_prev_node) ur(self.key_select_node_right) ur(self.key_select_node_left) ur(self.key_select_node_up) ur(self.key_select_node_down) ur(self.key_delete_node) ur(self.key_connect) ur(self.key_disconnect) self.parenting_keymap.set_next_keymap(self.focus_keymap) def _set_index(self, index): if self.selected_widget_index != index: self.selected_widget_index = index self._update_index() def _update_index(self): if not self.node_widgets: self.selected_widget_index = None elif self.selected_widget_index is not None: self.selected_widget_index %= len(self.sorted_widgets) self._set_next_keymap() def _find_widget_index(self, w): for i, (node, widget) in enumerate(self.sorted_widgets): if w == widget: return i def _add_index(self, amount): if self.selected_widget_index is None: index = 0 else: index = self.selected_widget_index l = len(self.sorted_widgets) index += amount index %= l self._set_index(index) def _next_node(self): '''Next node''' self._add_index(1) def _prev_node(self): '''Previous node''' self._add_index(-1) def _select_node_right(self): '''Select the next node to the right''' def dist(pos1, pos2): if pos1[0] < pos2[0]: return None return pos1[0] - pos2[0] self._select_node_dir(dist) def _select_node_left(self): '''Select the next node to the left''' def dist(pos1, pos2): if pos1[0] > pos2[0]: return None return pos2[0] - pos1[0] self._select_node_dir(dist) def _select_node_up(self): '''Select the next node above''' def dist(pos1, pos2): if pos1[1] > pos2[1]: return None return pos2[1] - pos1[1] self._select_node_dir(dist) def _select_node_down(self): '''Select the next node below''' def dist(pos1, pos2): if pos1[1] < pos2[1]: return None return pos1[1] - pos2[1] self._select_node_dir(dist) def _select_node_dir(self, distance_between): closest_right = None min_dist = None n, w = self.selected() if not w: return for widget in self.node_widgets.itervalues(): if widget == w: continue dist = distance_between(widget.pos, w.pos) if dist is None: continue if closest_right is None or dist < min_dist: closest_right = widget min_dist = dist if closest_right is not None: i = self._find_widget_index(closest_right) self._set_index(i) def _create_new_node(self): '''Create new node''' n = Graph.Node() w = self.add_node(n) self._set_index(self._find_widget_index(w)) def _delete_selected_node(self): '''Delete selected node''' n, w = self.sorted_widgets[self.selected_widget_index] self._add_index(1) self.remove_node(n) def _node_linking_started(self, key, start_func, connect_func, color, doc): r = self.parenting_keymap.register_key ur = self.parenting_keymap.unregister_key start_node, start_node_widget = self.selected() assert start_node is not None def _end_connect(): ur(key) r(key, keydown_noarg(start_func)) end_node, end_node_widget = self.sorted_widgets[self.selected_widget_index] self._node_link_func(start_node, end_node) self._node_link_start_pos = None self._node_link_end_pos = None _end_connect.__doc__ = doc def _start_pos(): return start_node_widget.rect().center def _end_pos(): n, w = self.selected() if w is None: return None return w.rect().center ur(key) r(key, keydown_noarg(_end_connect)) self._node_link_start_pos = _start_pos self._node_link_end_pos = _end_pos self._node_link_color = color self._node_link_func = connect_func def _connect_func(self, node1, node2): node1.connect_node(node2) def _disconnect_func(self, node1, node2): node1.disconnect_node(node2) def _start_connect(self): '''Sets the source node to connect''' self._node_linking_started(self.key_connect, self._start_connect, self._connect_func, (0,255,0), "Sets the target node to connect") def _start_disconnect(self): '''Sets the source node to disconnect''' self._node_linking_started(self.key_disconnect, self._start_disconnect, self._disconnect_func, (255,0,0), "Sets the target node to disconnect") def _widget_mouse_event(self, node, widget, event): #print event connect_mod = pygame.key.get_mods() & pygame.KMOD_SHIFT #disconnect_mod = pygame.key.get_modes() & pygame.KMOD_SHIFT if event.type == pygame.MOUSEBUTTONDOWN: i = self._find_widget_index(widget) self._set_index(i) if connect_mod and self._node_link_start_pos is None: def _start_pos(): return widget.rect().center self._node_link_source_node = node self._node_link_start_pos = _start_pos self._node_link_end_pos = pygame.mouse.get_pos self._node_link_color = (0,255,0) self._node_link_func = self._connect_func elif event.type == pygame.MOUSEBUTTONUP: if self._node_link_source_node is not None: self._node_link_start_pos = None self._node_link_end_pos = None self._node_link_func(self._node_link_source_node, node) self._node_link_source_node = None self._node_link_color = None def _cycle_layout_engine(self): '''Change to the next layout engine''' self.layout.cycle_layout_engines() self.update_layout() def _save(self): '''Save''' import pickle f=open('save.pkl', 'wb') pickle.dump(self.nodes,f,2) def _load(self): '''Load''' import pickle f=open('save.pkl', 'rb') nodes = pickle.load(f) for node in tuple(self.nodes): self.remove_node(node) self._set_next_keymap() for node in nodes: # TODO if we are loading nodes in addition to existing nodes, # make sure ID's are re-allocated to prevent collisions. node.id = Graph.persistent_id(node) for edge in node.iter_all_connections(): edge.id = Graph.persistent_id(edge) for node in nodes: self.add_node(node) def _export_dot(self): '''Export the graph to a .dot file''' d = Graph.generate_dot(self.generate_groups()) print d open('save.dot', 'wb').write(d) def _export_snapshot(self): '''Export the graph to a snapshot image''' self._save_next_display_update = 'snapshot.bmp'
class NodeWidget(Widget): bg_color=(10,10,100) activated_bg_color=(40,40,160) fg_color=(70,70,150) activated_fg_color=(100,100,250) key_stop_edit=Key(0, pygame.K_ESCAPE) def __init__(self, node, *args, **kw): self.node = node self.style = style._make_style() self.text_widget = TextEdit(self.style, self._get_text, self._set_text, [all_printable], {'\r':'\n'}) self.node.obs.add_observer(self, '_node_') Widget.__init__(self, *args, **kw) self._size = MovingValue(Point) self._pos = MovingValue(Point) from Shapes.Ellipse import Ellipse self.shape = Ellipse(pygame.Rect(0,0,1,1)) self.obs_loc = Observable() self._register_keys() self.reset_max_text_size() self.mouse_area = MouseArea(self.in_bounds) def __getstate__(self): d= self.__dict__.copy() del d['focus_keymap'] del d['keymap'] return d def __setstate__(self, d): for k,v in d.iteritems(): self.__dict__[k] = v self.focus_keymap = Keymap() self.keymap = Keymap() self._register_keys() def _register_keys(self): r = self.focus_keymap.register_key r(Key(pygame.KMOD_CTRL, pygame.K_RIGHT), keydown_noarg(self._move_right)) r(Key(pygame.KMOD_CTRL, pygame.K_LEFT), keydown_noarg(self._move_left)) r(Key(pygame.KMOD_CTRL, pygame.K_UP), keydown_noarg(self._move_up)) r(Key(pygame.KMOD_CTRL, pygame.K_DOWN), keydown_noarg(self._move_down)) r(Key(0, pygame.K_RETURN), keydown_noarg(self._edit_value)) def in_bounds(self, p): return self.rect().collidepoint(tuple(p)) def _get_text(self): if self.node.value is None: return '' return str(self.node.value) def _set_text(self, text): self.node.value = text self._update_text_size() def reset_max_text_size(self, value=None): # returns the calculated actual size if value is None: value = FONT_SIZE_ABSOLUTE_MAX self.max_font_size = value return self.calc_text_size() def calc_text_size(self): min_font_size = 2 ratio = 0.7 yratio = 1 size = self.style.font_size self.text_widget.update() while ((self.text_widget.size[0] < self._size.final[0]*ratio or self.text_widget.size[1] < self._size.final[1]*yratio) and self.style.font_size < min(self.max_font_size, FONT_SIZE_ABSOLUTE_MAX)): self.style.font_size = min(self.style.font_size + 1, FONT_SIZE_ABSOLUTE_MAX) self._update_text_size() while ((self.text_widget.size[0] > self._size.final[0]*ratio or self.text_widget.size[1] > self._size.final[1]*yratio) and self.style.font_size > min_font_size or self.style.font_size > self.max_font_size): self.style.font_size -= 1 self._update_text_size() return self.style.font_size def _update_text_size(self, new_size=None): if new_size is not None: self.style.font_size = new_size self.text_widget.set_style(self.style) self.text_widget.update(force=True) def get_size(self): return tuple(self._size.current) def set_size(self, p): p = Point(p) self._size.final = p self._update_text_size() self.obs_loc.notify.size_set(p) size = property(get_size, set_size) def get_pos(self): return tuple(self._pos.current) def set_pos(self, p): p = Point(p) self._pos.final = p self.obs_loc.notify.pos_set(p) pos = property(get_pos, set_pos) def rect(self): s = self.size return pygame.Rect(self.pos[0],self.pos[1],s[0],s[1]) def final_rect(self): s = self._size.final p = self._pos.final return pygame.Rect(p[0],p[1],s[0],s[1]) def _node_connect(self, e): pass def _node_disconnect(self, e): pass def update(self): self.text_widget.update() self._update_text_size() self._size.update() self._pos.update() self.shape.rect = self.rect() def _draw(self, surface, pos_offset): self.shape.paint(pos_offset, surface, self.fg_color, self.bg_color) self.text_widget.draw(surface, tuple(Point(pos_offset) + Point(self.rect().center) - Point(self.text_widget.size)/2)) def _move_right(self): '''move right''' self.pos = Point(self.pos) + Point((5,0)) def _move_left(self): '''move left''' self.pos = Point(self.pos) + Point((-5,0)) def _move_up(self): '''move up''' self.pos = Point(self.pos) + Point((0,-5)) def _move_down(self): '''move down''' self.pos = Point(self.pos) + Point((0,5)) def _edit_value(self): '''Edit value''' self.keymap.set_next_keymap(self.text_widget.focus_keymap) self.text_widget._start_editing() self.keymap.register_key(self.key_stop_edit, keydown_noarg(self._stop_edit_value)) def _stop_edit_value(self): '''Stop editing value''' self.text_widget._stop_editing() self.keymap.set_next_keymap(self.focus_keymap) self.keymap.unregister_key(self.key_stop_edit)
class Widget(object): painting_z_order = 0 font_size = 40 def __init__(self, text = '', pos=None, size=None, font_size=40, start_pos=None): self.size = MovingValue(Point((20,20)), get_default(size, Point((20,20)))) pos = get_default(pos, Point((0,0))) start_pos = get_default(start_pos, Point((0,0))) self.pos = MovingValue(start_pos, pos, step=0.3) self.widgets = [] # sub-widgets self.focused_widgets = [] self.hovered_widget = None self.trigger_lists = {'pre' : TriggerList(), 'post' : TriggerList(),} self.text = text self._font_size = font_size self.reset() self._init_params() self._init_event_triggers() self.max_undo = 25 # TODO kick this outta here #from Ellipse import Ellipse from Shapes.Rectangle import Rectangle self.shape = Rectangle(pygame.Rect(self.get_current_rect())) def get_font_size(self): return self._font_size def set_font_size(self, value): self._font_size = value self.update_default_font() font_size = property(fget=get_font_size,fset=set_font_size) def reset(self): self.record = False self.font=None self.default_font=None self.rendered_params = None self.rendered_text = None self.update_default_font() self.save_next_paint = None self.drag_start_pos = None self.dragged_widget = None self.history = [] self.history_redo = [] self.undoing = False self.shape_image = None self.focused_shape_image = None self.hovered_shape_image = None def __getstate__(self): d = self.__dict__.copy() del d['font'] del d['default_font'] del d['rendered_text'] del d['rendered_params'] return d def __setstate__(self, d): self.__dict__ = d self.reset() def update_default_font(self): self.default_font = get_font(self.font_size) def _init_params(self): self.params = ParamHolder(["visible", "enabled", "fore_color", "back_color", "text_color", "in_focus", "in_hover", "focus_back_color", "focus_fore_color", "focus_text_color", "hover_back_color", "hover_text_color", "autosize", "in_drag_mode", "user"], "WidgetParams") self.params.enabled = True self.params.visible = True # In drag mode, you can drag the widget with the mouse to move # it self.params.in_drag_mode = False self.params.fore_color = (100,100,200) self.params.back_color = (10, 10,15) self.params.text_color = (150,150,150) # If the widget is NOT really in focus, it will not receive # events. But this parameter specifies how to DISPLAY the # widget self.params.in_focus = False self.params.in_hover = False self.params.focus_back_color = (50,50,100) self.params.focus_fore_color = (150,150,250) self.params.focus_text_color = (230,230,255) self.params.hover_back_color = (20,20,60) self.params.hover_text_color = (200,200,215) self.params.user = None self.params.autosize = "by size" def update_moving(self): self.render_text() self.size.update() self.pos.update() def in_bounds(self, pos): # Pos is relative to PARENTs origin p = self.pos.current s = self.size.current if ((pos.x > p.x) and (pos.y > p.y) and (pos.x < p.x + s.x) and (pos.y < p.y + s.y)): return True return False def center_pos(self, current=True): if current: return self.pos.current+self.size.current*0.5 return self.pos.final+self.size.final*0.5 ###################################################################### def add_widget(self, widget, z = None): if widget in self.widgets: raise ValueError("Widget already exists! %r" % (widget,)) if z is None: self.widgets.append(widget) else: self.widgets.insert(z, widget) def remove_widget(self, widget): self.widgets.remove(widget) def _z_ordered_widgets(self): return sorted((w.painting_z_order, w) for w in self.widgets) ###################################################################### def pos_relative_to_widget(self, pos, widget): return pos - widget.pos.current def handle_event(self, event): if self.trigger_lists['pre'].handle_event(event): return True if 'mouse' in event.type: orig_pos = event.pos.copy() handled = False if event.to_all: for widget in self.widgets: if 'mouse' in event.type: event.pos = self.pos_relative_to_widget(orig_pos, widget) handled = widget.handle_event(event) elif event.to_focused and self.focused_widgets: for widget in self.focused_widgets: if 'mouse' in event.type: event.pos = self.pos_relative_to_widget(orig_pos, widget) handled = widget.handle_event(event) if 'mouse' in event.type: event.pos = orig_pos if handled: self.trigger_lists['post'].handle_event(event, only_forced=True) return True return self.trigger_lists['post'].handle_event(event) def _init_event_triggers(self): # force_handling = True means that the handler will be called # even if the event was handled by someone lower in the # hierarchy self.trigger_lists['pre'].register_event_type('paint', self.paint) for event_type, handler, force_handling in (('mouse motion', self.mouse_motion, False), ('mouse down', self.mouse_down, False), ('mouse up', self.mouse_up, False), ('key up', self.key_up, False), ('key down', self.key_down, False), ('enter down', self.enter_down, False), ): for when in ('pre', 'post'): # This saves for the common event handlers the need to # implement two methods self.trigger_lists[when].register_event_type(event_type, partial(handler, when), forced=force_handling) def mouse_motion(self, when, event): if when == 'post': return # This is expected to be relative to our origin, not global origin. p = event.pos self.params.in_hover = True self.hovered_widget = None if self.dragged_widget: new_event = event.copy() new_event.pos -= self.dragged_widget.pos.current self.dragged_widget.mouse_motion(when, new_event) else: for z, widget in reversed(self._z_ordered_widgets()): if self.hovered_widget or not widget.in_bounds(p): # todo call some widget.?? method? widget.params.in_hover = False continue new_event = event.copy() # make it relative new_event.pos = self.pos_relative_to_widget(event.pos, widget) if widget.mouse_motion(when, new_event): self.hovered_widget = widget if self.params.in_drag_mode and self.drag_start_pos: # if we use event.pos, the widget will not be dragged beyond its own borders self.pos.final = self.drag_start_pos + mouse_pos() return True def _modkey_used(self, key, mods=None): if not mods: mods = pygame.key.get_mods() return mods & key multiselect_modifier = pygame.KMOD_CTRL def _multiselect_modifier_used(self, mods=None): return self._modkey_used(self.multiselect_modifier) def mouse_down(self, when, event): if when == 'post': return p = event.pos self.params.in_focus = True if not self._multiselect_modifier_used(): self.unset_focus() self.dragged_widget = None for z, widget in reversed(self._z_ordered_widgets()): if not widget.in_bounds(p): continue self.set_focus(widget) new_event = event.copy() # make it relative new_event.pos = self.pos_relative_to_widget(event.pos, widget) if widget.params.in_drag_mode: self.dragged_widget = widget if widget.mouse_down(when, new_event): return True if self.params.in_drag_mode: self.drag_start_pos = self.pos.current - mouse_pos() self.pos.final = self.pos.current return True def mouse_up(self, when, event): self.dragged_widget = None if self.params.in_drag_mode and self.drag_start_pos: self.drag_start_pos = None return True return False def set_focus(self, widget): if widget not in self.widgets: for subwidget in self.widgets: if subwidget.set_focus(widget): self.set_focus(subwidget) return True return False elif widget not in self.focused_widgets: self.focused_widgets.append(widget) widget.params.in_focus = True return True def unset_focus(self): for w in self.focused_widgets: w.params.in_focus = False self.focused_widgets = [] def key_up(self, when, event): pass def key_down(self, when, event): if when=='pre': return False if not self.params.enabled: return False self.entered_text(event) return True def entered_text(self, event): import string event = event.pygame_event if event.key == pygame.K_BACKSPACE: self.text = self.text[:-1] elif event.unicode in (string.letters + string.digits + string.hexdigits + ' \r' + string.punctuation): ch = event.unicode.replace('\r', '\n') self.text += ch def enter_down(self, when, event): pass ######################################################## def get_current_rect(self): return self.pos.current.x, self.pos.current.y, self.size.current.x, self.size.current.y def paint(self, event): if not self.params.visible: return surface = event.surface self.update_moving() if self.params.in_focus: back_color = self.params.focus_back_color fore_color = self.params.focus_fore_color elif self.params.in_hover: back_color = self.params.hover_back_color fore_color = self.params.fore_color else: back_color = self.params.back_color fore_color = self.params.fore_color self.paint_shape(event.parent_offset, surface, fore_color, back_color) self.paint_text(event.parent_offset, surface) self.paint_widgets(event) if self.save_next_paint: # TODO rewrite this to use the Trigger for 'paint' args, kw = self.save_next_paint self.save_next_paint = None # reset before calling to prevent hypothetical recursions self.save_snapshot_image(event, *args, **kw) if self.record: pygame.draw.circle(surface, (255,100,100), (self.size.current.x-7, 7), 5, 0) self.save_snapshot_image(event, self.record_dir + '/img%4.4d.BMP' % (self._frame_counter)) self._frame_counter+=1 return True # since we are painting them explicitly, the lower widgets don't need to def paint_widgets(self, event): new_event = event.copy() new_event.parent_offset = new_event.parent_offset + self.pos.current for z, widget in self._z_ordered_widgets(): # We want to control the specific order of painting, so # don't let the standard event passing through the # hierarchy. just do it here widget.handle_event(new_event) ############################################################################# def undo(self): if not self.history: return doer, undoer, args, kw = self.history.pop() self.undoing = True undoer() self.undoing = False def redo(self): if not self.history_redo: return doer, undoer, args, kw = self.history_redo.pop() self.undoing = False undoer() def save(self, filename): import pickle f=open(filename, 'wb') pickle.dump(self.widgets,f,2) def load(self, filename): import pickle f=open(filename, 'rb') self.widgets = pickle.load(f) def save_snapshot_on_next_paint(self, filename, width=None, height=None, callback_when_done=None): self.save_next_paint = (filename,), dict(width=width, height=height, callback_when_done=callback_when_done) def save_snapshot_image(self, event, filename, width=None, height=None, callback_when_done=None, **kw): if width and not height: height = self.size.current.y/(self.size.current.x/float(width)) elif height and not width: width = self.size.current.x/(self.size.current.y/float(height)) width = get_default(width, self.size.current.x) height = get_default(height, self.size.current.y) rect = pygame.Rect(event.parent_offset.x, event.parent_offset.y, self.size.current.x, self.size.current.y) rect = rect.clip(event.surface.get_rect()) subsurface = event.surface.subsurface(rect) pygame.image.save(pygame.transform.scale(subsurface, (width,height)), filename) if callback_when_done: callback_when_done(filename, width, height) ############################################################################# # TODO move this to some subclass # : def get_current_text_color(self): if self.params.in_focus: text_color = self.params.focus_text_color elif self.params.in_hover: text_color = self.params.hover_text_color else: text_color = self.params.text_color return text_color def render_text(self): text_color = self.get_current_text_color() params = (self.params.autosize, tuple(self.size.final), self.text, text_color) if self.params.autosize == "by text": params += (self.default_font,) if self.rendered_params == params: return lines = self.text.split('\n') if self.params.autosize == "by size": does_fit, self.font = find_font(lines, tuple(self.size.final*(3./4))) else: self.font = self.default_font if self.params.autosize == "by text": width, height = lines_size(self.font, lines) self.size.final.x = self.size.current.x = width self.size.final.y = self.size.current.y = height rendered_lines = [self.font.render(line, True, text_color) for line in lines] size = (max(t.get_width() for t in rendered_lines), sum(t.get_height() for t in rendered_lines)) self.rendered_text = pygame.Surface(size, pygame.SWSURFACE|pygame.SRCALPHA|pygame.SRCCOLORKEY, 32) self.rendered_params = params # TODO: Support centering and stuff? y = 0 for rline in rendered_lines: self.rendered_text.blit(rline, (0, y)) y += rline.get_height() def get_shape(self): # TODO make the shape a mutable attribute of self? if not self.shape: return None self.shape.rect = pygame.Rect(self.get_current_rect()) return self.shape def paint_shape(self, parent_offset, surface, fore_color, back_color): # TODO use subsurfaces instead of parent_offset (problematic beacuse of EdgeWidget right now) self.get_shape() if not self.shape: return #rect = tuple(self.pos.current + parent_offset) + tuple(self.size.current) if self.params.in_focus: image = self.focused_shape_image elif self.params.in_hover: image = self.hovered_shape_image else: image = self.shape_image self.shape.paint(parent_offset, surface, fore_color, back_color, image) def paint_text(self, parent_offset, surface): lines = self.text.split('\n') text_size = Point(lines_size(self.font, lines)) surface.blit(self.rendered_text, tuple(parent_offset + self.center_pos()-text_size*0.5)) def change_font_size(self, add = 0, mul = 1): self._font_size *= mul self._font_size += add self.update_default_font() def start_record(self): self._frame_counter = 0 import os, time self.record_dir = '/tmp/record_%s' % (time.time()) os.makedirs(self.record_dir) self.record = True def stop_record(self): self.record = False