def __init__(self, icon_file_func, *args, **kwargs): super(_PreviewWidget, self).__init__(*args, **kwargs) self.board_drawer = \ BoardDrawer(get_size_func=self._get_drawer_size, invalidate_rect_func=self._invalidate_drawer_rect) self.removal_drawer = \ RemovalDrawer(get_size_func=self._get_drawer_size, invalidate_rect_func=self._invalidate_drawer_rect) self.win_drawer = \ WinDrawer(get_size_func=self._get_drawer_size, invalidate_rect_func=self._invalidate_drawer_rect) self._icon_file_func = icon_file_func self._preview_rect = Gdk.Rectangle() self._preview_rect.x = self._preview_rect.y = \ self._preview_rect.width = self._preview_rect.height = 0 self._toolbar_rect = Gdk.Rectangle() self._toolbar_rect.x = self._toolbar_rect.y = \ self._toolbar_rect.width = self._toolbar_rect.height = 0 self._drawer_rect = Gdk.Rectangle() self._drawer_rect.x = self._drawer_rect.y = \ self._drawer_rect.width = self._drawer_rect.height = 0 self._drawer = self.board_drawer # Mouse position as a floating point value over the 4x3 unit preview # area. self._cursor_pos = (0.0, 0.0) # Cursor size in pixels. self._cursor_size = (0, 0) self._click_visible = False self._cursor_visible = False self.connect('draw', self._draw_event_cb) self.connect('configure-event', self._configure_event_cb)
class _PreviewWidget(Gtk.DrawingArea): def __init__(self, icon_file_func, *args, **kwargs): super(_PreviewWidget, self).__init__(*args, **kwargs) self.board_drawer = \ BoardDrawer(get_size_func=self._get_drawer_size, invalidate_rect_func=self._invalidate_drawer_rect) self.removal_drawer = \ RemovalDrawer(get_size_func=self._get_drawer_size, invalidate_rect_func=self._invalidate_drawer_rect) self.win_drawer = \ WinDrawer(get_size_func=self._get_drawer_size, invalidate_rect_func=self._invalidate_drawer_rect) self._icon_file_func = icon_file_func self._preview_rect = Gdk.Rectangle() self._preview_rect.x = self._preview_rect.y = \ self._preview_rect.width = self._preview_rect.height = 0 self._toolbar_rect = Gdk.Rectangle() self._toolbar_rect.x = self._toolbar_rect.y = \ self._toolbar_rect.width = self._toolbar_rect.height = 0 self._drawer_rect = Gdk.Rectangle() self._drawer_rect.x = self._drawer_rect.y = \ self._drawer_rect.width = self._drawer_rect.height = 0 self._drawer = self.board_drawer # Mouse position as a floating point value over the 4x3 unit preview # area. self._cursor_pos = (0.0, 0.0) # Cursor size in pixels. self._cursor_size = (0, 0) self._click_visible = False self._cursor_visible = False self.connect('draw', self._draw_event_cb) self.connect('configure-event', self._configure_event_cb) # self.connect('size-allocate', self._configure_event_cb) def _get_drawer_size(self): return (self._drawer_rect.width, self._drawer_rect.height) def _invalidate_drawer_rect(self, rect): if self.get_window(): (x, y) = (self._drawer_rect.x, self._drawer_rect.y) offset_rect = Gdk.Rectangle() offset_rect.x, offset_rect.y, = (rect.x + x, rect.y + y) offset_rect.width, offset_rect.height = (rect.width, rect.height) self.get_window().invalidate_rect(offset_rect, True) def set_drawer(self, drawer): self._drawer = drawer r = self._preview_rect self._invalidate_client_rect(0, 0, r.width, r.height) def center_cursor(self): self.set_cursor_pos(2.0, 1.5) def set_cursor_pos(self, x, y): self._invalidate_cursor() self._cursor_pos = (x, y) self._invalidate_cursor() self._update_mouse_position() def get_cursor_pos(self): return self._cursor_pos def set_click_visible(self, click_visible): self._click_visible = click_visible self._invalidate_click() def set_cursor_visible(self, cursor_visible): self._cursor_visible = cursor_visible self._invalidate_cursor() def get_block_coord(self, x, y): # Returns the coordinate of the given board block in terms of # 4x3 units. if (self._preview_rect.width == 0 or self._preview_rect.height == 0): return (0, 0) (drawer_x, drawer_y) = self.board_drawer.get_block_coord(x, y) preview_x = drawer_x preview_y = drawer_y + self._toolbar_rect.height out_x = preview_x * 4.0 / self._preview_rect.width out_y = preview_y * 3.0 / self._preview_rect.height return (out_x, out_y) def get_icon_coord(self, index): # Returns the coordinate of the given icon in terms of 4x3 units. icon_height = self._toolbar_rect.height preview_x = icon_height * (index + 0.5) preview_y = icon_height * 0.5 out_x = preview_x * 4.0 / self._preview_rect.width out_y = preview_y * 3.0 / self._preview_rect.height return (out_x, out_y) def _get_cursor_pixel_coords(self): (x, y) = self._cursor_pos pixel_x = x * self._preview_rect.width / 4 pixel_y = y * self._preview_rect.height / 3 return (pixel_x, pixel_y) def _invalidate_cursor(self): (pixel_x, pixel_y) = self._get_cursor_pixel_coords() self._invalidate_client_rect(pixel_x, pixel_y, *self._cursor_size) if self._click_visible: self._invalidate_click() def _invalidate_click(self): (pixel_x, pixel_y) = self._get_cursor_pixel_coords() r = self._cursor_size[0] * _CLICK_OUTER_RADIUS r2 = r * 2 self._invalidate_client_rect(pixel_x - r, pixel_y - r, r2, r2) def _invalidate_client_rect(self, x, y, width, height): if self.get_window(): rect = Gdk.Rectangle() rect.x, rect.y, rect.width, rect.height = ( int(math.floor(x)) + self._preview_rect.x, int(math.floor(y)) + self._preview_rect.y, int(math.ceil(width)) + 1, int(math.ceil(height)) + 1) self.get_window().invalidate_rect(rect, True) def _update_mouse_position(self): (pixel_x, pixel_y) = self._get_cursor_pixel_coords() (x, y) = (pixel_x, pixel_y - self._toolbar_rect.height) self.board_drawer.set_mouse_selection(x, y) def _size_allocate_cb(self, widget, rect): self.width = rect.width self.height = rect.height def _draw_event_cb(self, widget, cr): alloc = self.get_allocation() cr.rectangle(0, 0, alloc.width, alloc.height) self._draw(cr, alloc.width, alloc.height) def _draw(self, cr, width, height): cr.set_source_rgb(*_BG_COLOR) cr.rectangle(0, 0, width, height) cr.fill() cr.save() cr.rectangle(self._preview_rect.x, self._preview_rect.y, self._preview_rect.width, self._preview_rect.height) cr.clip() self._draw_toolbar(cr) self._draw_grid(cr) if self._click_visible: self._draw_click(cr) if self._cursor_visible: self._draw_cursor(cr) cr.restore() def _draw_toolbar(self, cr): cr.set_source_rgb(*_TOOLBAR_COLOR) cr.rectangle(self._toolbar_rect.x, self._toolbar_rect.y, self._toolbar_rect.width, self._toolbar_rect.height) cr.fill() icon_height = self._toolbar_rect.height scale = icon_height / 55.0 for (i, icon_name) in enumerate([ 'new-game', 'replay-game', 'edit-undo', 'edit-redo', 'easy-level', 'medium-level', 'hard-level', ]): file_path = self._icon_file_func(icon_name) handle = _get_icon_handle(file_path) cr.save() cr.translate(self._toolbar_rect.x + i * icon_height, self._toolbar_rect.y) cr.scale(scale, scale) handle.render_cairo(cr) cr.restore() def _draw_grid(self, cr): cr.save() cr.translate(self._drawer_rect.x, self._drawer_rect.y) self._drawer.draw(cr, self._drawer_rect.width, self._drawer_rect.height) cr.restore() def _draw_click(self, cr): width = self._cursor_size[0] weight = width * _CLICK_WEIGHT_SCALE outline_weight = width * _CLICK_OUTLINE_WEIGHT_SCALE r1 = width * _CLICK_INNER_RADIUS + outline_weight r2 = width * _CLICK_OUTER_RADIUS - outline_weight (pixel_x, pixel_y) = self._get_cursor_pixel_coords() x = pixel_x + self._preview_rect.x y = pixel_y + self._preview_rect.y cr.save() cr.translate(x, y) cr.set_line_cap(cairo.LINE_CAP_ROUND) angle_inc = math.pi * 2.0 / 6 cr.rotate(angle_inc * 0.75) for i in range(6): cr.set_line_width(outline_weight) cr.set_source_rgb(*_CURSOR_OUTLINE_COLOR) cr.move_to(r1, 0) cr.line_to(r2, 0) cr.stroke() cr.set_line_width(weight) cr.set_source_rgb(*_CURSOR_COLOR) cr.move_to(r1, 0) cr.line_to(r2, 0) cr.stroke() cr.rotate(angle_inc) cr.restore() def _draw_cursor(self, cr): (pixel_x, pixel_y) = self._get_cursor_pixel_coords() x = pixel_x + self._preview_rect.x y = pixel_y + self._preview_rect.y (width, height) = self._cursor_size weight = width * _CURSOR_WEIGHT_SCALE outline_weight = width * _CURSOR_OUTLINE_WEIGHT_SCALE hw = outline_weight / 2.0 def draw_arrow(): cr.move_to(x + width * 0.9 - hw, y + hw) cr.line_to(x + hw, y + hw) cr.line_to(x + hw, y + height * 0.9 - hw) cr.move_to(x + hw, y + hw) cr.line_to(x + width - hw, y + height - hw) cr.stroke() cr.save() cr.set_line_cap(cairo.LINE_CAP_ROUND) cr.set_line_join(cairo.LINE_JOIN_ROUND) cr.set_line_width(outline_weight) cr.set_source_rgb(*_CURSOR_OUTLINE_COLOR) draw_arrow() cr.set_line_width(weight) cr.set_source_rgb(*_CURSOR_COLOR) draw_arrow() cr.restore() def _configure_event_cb(self, widget, event): (width, height) = (event.width, event.height) actual_width = width - _PREVIEW_TRIM_WIDTH actual_height = actual_width * 3 // 4 self.set_size_request(actual_width, actual_height) icon_height = int(math.ceil(actual_height * _ICON_HEIGHT)) board_height = actual_height - icon_height x_offset = (width - actual_width) // 2 y_offset = (height - actual_height) // 2 self._preview_rect = Gdk.Rectangle() self._preview_rect.x = x_offset self._preview_rect.y = y_offset self._preview_rect.width = actual_width self._preview_rect.height = actual_height self._toolbar_rect = Gdk.Rectangle() self._toolbar_rect.x = x_offset self._toolbar_rect.y = y_offset self._toolbar_rect.width = actual_width self._toolbar_rect.height = icon_height self._drawer_rect = Gdk.Rectangle() self._drawer_rect.x = x_offset self._drawer_rect.y = y_offset + icon_height self._drawer_rect.width = actual_width self._drawer_rect.height = board_height self.board_drawer.resize(actual_width, board_height) self.removal_drawer.resize(actual_width, board_height) self.win_drawer.resize(actual_width, board_height) cursor_width = actual_height * _CURSOR_SCALE self._cursor_size = (cursor_width, cursor_width) self._update_mouse_position()
class _PreviewWidget(Gtk.DrawingArea): def __init__(self, icon_file_func, *args, **kwargs): super(_PreviewWidget, self).__init__(*args, **kwargs) self.board_drawer = \ BoardDrawer(get_size_func=self._get_drawer_size, invalidate_rect_func=self._invalidate_drawer_rect) self.removal_drawer = \ RemovalDrawer(get_size_func=self._get_drawer_size, invalidate_rect_func=self._invalidate_drawer_rect) self.win_drawer = \ WinDrawer(get_size_func=self._get_drawer_size, invalidate_rect_func=self._invalidate_drawer_rect) self._icon_file_func = icon_file_func self._preview_rect = Gdk.Rectangle() self._preview_rect.x = self._preview_rect.y = \ self._preview_rect.width = self._preview_rect.height = 0 self._toolbar_rect = Gdk.Rectangle() self._toolbar_rect.x = self._toolbar_rect.y = \ self._toolbar_rect.width = self._toolbar_rect.height = 0 self._drawer_rect = Gdk.Rectangle() self._drawer_rect.x = self._drawer_rect.y = \ self._drawer_rect.width = self._drawer_rect.height = 0 self._drawer = self.board_drawer # Mouse position as a floating point value over the 4x3 unit preview # area. self._cursor_pos = (0.0, 0.0) # Cursor size in pixels. self._cursor_size = (0, 0) self._click_visible = False self._cursor_visible = False self.connect('draw', self._draw_event_cb) self.connect('configure-event', self._configure_event_cb) # self.connect('size-allocate', self._configure_event_cb) def _get_drawer_size(self): return (self._drawer_rect.width, self._drawer_rect.height) def _invalidate_drawer_rect(self, rect): if self.get_window(): (x, y) = (self._drawer_rect.x, self._drawer_rect.y) offset_rect = Gdk.Rectangle() offset_rect.x, offset_rect.y, = (rect.x + x, rect.y + y) offset_rect.width, offset_rect.height = (rect.width, rect.height) self.get_window().invalidate_rect(offset_rect, True) def set_drawer(self, drawer): self._drawer = drawer r = self._preview_rect self._invalidate_client_rect(0, 0, r.width, r.height) def center_cursor(self): self.set_cursor_pos(2.0, 1.5) def set_cursor_pos(self, x, y): self._invalidate_cursor() self._cursor_pos = (x, y) self._invalidate_cursor() self._update_mouse_position() def get_cursor_pos(self): return self._cursor_pos def set_click_visible(self, click_visible): self._click_visible = click_visible self._invalidate_click() def set_cursor_visible(self, cursor_visible): self._cursor_visible = cursor_visible self._invalidate_cursor() def get_block_coord(self, x, y): # Returns the coordinate of the given board block in terms of 4x3 units. if (self._preview_rect.width == 0 or self._preview_rect.height == 0): return (0, 0) (drawer_x, drawer_y) = self.board_drawer.get_block_coord(x, y) preview_x = drawer_x preview_y = drawer_y + self._toolbar_rect.height out_x = preview_x * 4.0 / self._preview_rect.width out_y = preview_y * 3.0 / self._preview_rect.height return (out_x, out_y) def get_icon_coord(self, index): # Returns the coordinate of the given icon in terms of 4x3 units. icon_height = self._toolbar_rect.height preview_x = icon_height * (index + 0.5) preview_y = icon_height * 0.5 out_x = preview_x * 4.0 / self._preview_rect.width out_y = preview_y * 3.0 / self._preview_rect.height return (out_x, out_y) def _get_cursor_pixel_coords(self): (x, y) = self._cursor_pos pixel_x = x * self._preview_rect.width / 4 pixel_y = y * self._preview_rect.height / 3 return (pixel_x, pixel_y) def _invalidate_cursor(self): (pixel_x, pixel_y) = self._get_cursor_pixel_coords() self._invalidate_client_rect(pixel_x, pixel_y, *self._cursor_size) if self._click_visible: self._invalidate_click() def _invalidate_click(self): (pixel_x, pixel_y) = self._get_cursor_pixel_coords() r = self._cursor_size[0] * _CLICK_OUTER_RADIUS r2 = r * 2 self._invalidate_client_rect(pixel_x - r, pixel_y - r, r2, r2) def _invalidate_client_rect(self, x, y, width, height): if self.get_window(): rect = Gdk.Rectangle() rect.x, rect.y, rect.width, rect.height = ( int(math.floor(x)) + self._preview_rect.x, int(math.floor(y)) + self._preview_rect.y, int(math.ceil(width)) + 1, int(math.ceil(height)) + 1) self.get_window().invalidate_rect(rect, True) def _update_mouse_position(self): (pixel_x, pixel_y) = self._get_cursor_pixel_coords() (x, y) = (pixel_x, pixel_y - self._toolbar_rect.height) self.board_drawer.set_mouse_selection(x, y) def _size_allocate_cb(self, widget, rect): self.width = rect.width self.height = rect.height def _draw_event_cb(self, widget, cr): alloc = self.get_allocation() cr.rectangle(0, 0, alloc.width, alloc.height) self._draw(cr, alloc.width, alloc.height) def _draw(self, cr, width, height): cr.set_source_rgb(*_BG_COLOR) cr.rectangle(0, 0, width, height) cr.fill() cr.save() cr.rectangle(self._preview_rect.x, self._preview_rect.y, self._preview_rect.width, self._preview_rect.height) cr.clip() self._draw_toolbar(cr) self._draw_grid(cr) if self._click_visible: self._draw_click(cr) if self._cursor_visible: self._draw_cursor(cr) cr.restore() def _draw_toolbar(self, cr): cr.set_source_rgb(*_TOOLBAR_COLOR) cr.rectangle(self._toolbar_rect.x, self._toolbar_rect.y, self._toolbar_rect.width, self._toolbar_rect.height) cr.fill() icon_height = self._toolbar_rect.height scale = icon_height / 55.0 for (i, icon_name) in enumerate(['new-game', 'replay-game', 'edit-undo', 'edit-redo']): file_path = self._icon_file_func(icon_name) handle = _get_icon_handle(file_path) cr.save() cr.translate(self._toolbar_rect.x + i * icon_height, self._toolbar_rect.y) cr.scale(scale, scale) handle.render_cairo(cr) cr.restore() def _draw_grid(self, cr): cr.save() cr.translate(self._drawer_rect.x, self._drawer_rect.y) self._drawer.draw(cr, self._drawer_rect.width, self._drawer_rect.height) cr.restore() def _draw_click(self, cr): width = self._cursor_size[0] weight = width * _CLICK_WEIGHT_SCALE outline_weight = width * _CLICK_OUTLINE_WEIGHT_SCALE r1 = width * _CLICK_INNER_RADIUS + outline_weight r2 = width * _CLICK_OUTER_RADIUS - outline_weight (pixel_x, pixel_y) = self._get_cursor_pixel_coords() x = pixel_x + self._preview_rect.x y = pixel_y + self._preview_rect.y cr.save() cr.translate(x, y) cr.set_line_cap(cairo.LINE_CAP_ROUND) angle_inc = math.pi * 2.0 / 6 cr.rotate(angle_inc * 0.75) for i in range(6): cr.set_line_width(outline_weight) cr.set_source_rgb(*_CURSOR_OUTLINE_COLOR) cr.move_to(r1, 0) cr.line_to(r2, 0) cr.stroke() cr.set_line_width(weight) cr.set_source_rgb(*_CURSOR_COLOR) cr.move_to(r1, 0) cr.line_to(r2, 0) cr.stroke() cr.rotate(angle_inc) cr.restore() def _draw_cursor(self, cr): (pixel_x, pixel_y) = self._get_cursor_pixel_coords() x = pixel_x + self._preview_rect.x y = pixel_y + self._preview_rect.y (width, height) = self._cursor_size weight = width * _CURSOR_WEIGHT_SCALE outline_weight = width * _CURSOR_OUTLINE_WEIGHT_SCALE hw = outline_weight / 2.0 def draw_arrow(): cr.move_to(x + width * 0.9 - hw, y + hw) cr.line_to(x + hw, y + hw) cr.line_to(x + hw, y + height * 0.9 - hw) cr.move_to(x + hw, y + hw) cr.line_to(x + width - hw, y + height - hw) cr.stroke() cr.save() cr.set_line_cap(cairo.LINE_CAP_ROUND) cr.set_line_join(cairo.LINE_JOIN_ROUND) cr.set_line_width(outline_weight) cr.set_source_rgb(*_CURSOR_OUTLINE_COLOR) draw_arrow() cr.set_line_width(weight) cr.set_source_rgb(*_CURSOR_COLOR) draw_arrow() cr.restore() def _configure_event_cb(self, widget, event): (width, height) = (event.width, event.height) actual_width = width - _PREVIEW_TRIM_WIDTH actual_height = actual_width * 3 / 4 self.set_size_request(actual_width, actual_height) icon_height = int(math.ceil(actual_height * _ICON_HEIGHT)) board_height = actual_height - icon_height x_offset = (width - actual_width) / 2 y_offset = (height - actual_height) / 2 self._preview_rect = Gdk.Rectangle() self._preview_rect.x = x_offset self._preview_rect.y = y_offset self._preview_rect.width = actual_width self._preview_rect.height = actual_height self._toolbar_rect = Gdk.Rectangle() self._toolbar_rect.x = x_offset self._toolbar_rect.y = y_offset self._toolbar_rect.width = actual_width self._toolbar_rect.height = icon_height self._drawer_rect = Gdk.Rectangle() self._drawer_rect.x = x_offset self._drawer_rect.y = y_offset + icon_height self._drawer_rect.width = actual_width self._drawer_rect.height = board_height self.board_drawer.resize(actual_width, board_height) self.removal_drawer.resize(actual_width, board_height) self.win_drawer.resize(actual_width, board_height) cursor_width = actual_height * _CURSOR_SCALE self._cursor_size = (cursor_width, cursor_width) self._update_mouse_position()