class Application(GObject.GObject): def __init__(self): GObject.GObject.__init__(self) brush_file = open('../brushes/classic/charcoal.myb') brush_info = brush.BrushInfo(brush_file.read()) brush_info.set_color_rgb((0.0, 0.0, 0.0)) self.default_eraser = brush_info.get_base_value("eraser") self.default_radius = brush_info.get_base_value("radius_logarithmic") self.brush = brush.Brush(brush_info) self.button_pressed = False self.last_event = (0.0, 0.0, 0.0) # (x, y, time) self.onionskin_on = True self.onionskin_by_cels = True self.onionskin_length = 3 self.onionskin_falloff = 0.5 self.eraser_on = False self.force_add_cel = True self.surface = None self.surface_node = None self.xsheet = XSheet(24 * 60) self.xsheet.connect('frame-changed', self.xsheet_changed_cb) self.xsheet.connect('layer-changed', self.xsheet_changed_cb) self.metronome = Metronome(self.xsheet) self.update_surface() self.nodes = {} self.create_graph() self.init_ui() def create_graph(self): self.graph = Gegl.Node() main_over = self.graph.create_child("gegl:over") self.nodes['main_over'] = main_over layer_overs = [] for l in range(self.xsheet.layers_length): over = self.graph.create_child("gegl:over") layer_overs.append(over) self.nodes['layer_overs'] = layer_overs layer_overs[0].connect_to("output", main_over, "input") for over, next_over in zip(layer_overs, layer_overs[1:]): next_over.connect_to("output", over, "input") background_node = self.graph.create_child("gegl:rectangle") background_node.set_property('color', Gegl.Color.new("#fff")) background_node.connect_to("output", layer_overs[-1], "input") self.nodes['background'] = background_node layer_nodes = [] for l in range(self.xsheet.layers_length): nodes = {} current_cel_over = self.graph.create_child("gegl:over") current_cel_over.connect_to("output", layer_overs[l], "aux") nodes['current_cel_over'] = current_cel_over onionskin_overs = [] onionskin_opacities = [] for i in range(self.onionskin_length): over = self.graph.create_child("gegl:over") onionskin_overs.append(over) opacity = self.graph.create_child("gegl:opacity") opacity.set_property('value', 1 - self.onionskin_falloff) onionskin_opacities.append(opacity) over.connect_to("output", opacity, "input") for over, next_opacity in zip(onionskin_overs, onionskin_opacities[1:]): next_opacity.connect_to("output", over, "aux") onionskin_opacities[0].connect_to("output", current_cel_over, "aux") nodes['onionskin'] = {} nodes['onionskin']['overs'] = onionskin_overs nodes['onionskin']['opacities'] = onionskin_opacities layer_nodes.append(nodes) self.nodes['layer_nodes'] = layer_nodes self.update_graph() def update_graph(self): get_cel = None if self.onionskin_by_cels: get_cel = self.xsheet.get_cel_relative_by_cels else: get_cel = self.xsheet.get_cel_relative for layer_idx in range(self.xsheet.layers_length): layer_nodes = self.nodes['layer_nodes'][layer_idx] cur_cel = self.xsheet.get_cel(layer_idx=layer_idx) if cur_cel is not None: cur_cel.surface_node.connect_to( "output", layer_nodes['current_cel_over'], "input") else: layer_nodes['current_cel_over'].disconnect("input") if not self.onionskin_on: continue layer_diff = layer_idx - self.xsheet.layer_idx for i in range(self.onionskin_length): prev_cel = get_cel(-(i + 1), layer_diff=layer_diff) over = layer_nodes['onionskin']['overs'][i] opacity = layer_nodes['onionskin']['opacities'][i] if prev_cel is not None: prev_cel.surface_node.connect_to("output", over, "input") else: over.disconnect("input") # debug # print_connections(self.nodes['main_over']) def init_ui(self): window = Gtk.Window() window.props.title = "XSheet" window.connect("destroy", self.destroy_cb) window.connect("size-allocate", self.size_allocate_cb) window.connect("key-press-event", self.key_press_cb) window.connect("key-release-event", self.key_release_cb) window.show() top_box = Gtk.Grid() window.add(top_box) top_box.show() toolbar = Gtk.Toolbar() top_box.attach(toolbar, 0, 0, 2, 1) toolbar.show() factory = Gtk.IconFactory() icon_names = [ 'xsheet-onionskin', 'xsheet-play', 'xsheet-eraser', 'xsheet-metronome', 'xsheet-settings' ] for name in icon_names: filename = os.path.join('data', 'icons', name + '.svg') pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) iconset = Gtk.IconSet.new_from_pixbuf(pixbuf) factory.add(name, iconset) factory.add_default() play_button = Gtk.ToggleToolButton() play_button.set_stock_id("xsheet-play") play_button.connect("toggled", self.toggle_play_cb) toolbar.insert(play_button, -1) play_button.show() onionskin_button = Gtk.ToggleToolButton() onionskin_button.set_stock_id("xsheet-onionskin") onionskin_button.set_active(True) onionskin_button.connect("toggled", self.toggle_onionskin_cb) toolbar.insert(onionskin_button, -1) onionskin_button.show() eraser_button = Gtk.ToggleToolButton() eraser_button.set_stock_id("xsheet-eraser") eraser_button.connect("toggled", self.toggle_eraser_cb) toolbar.insert(eraser_button, -1) eraser_button.show() metronome_button = Gtk.ToggleToolButton() metronome_button.set_stock_id("xsheet-metronome") metronome_button.connect("toggled", self.toggle_metronome_cb) toolbar.insert(metronome_button, -1) metronome_button.show() settings_button = Gtk.ToolButton() settings_button.set_stock_id("xsheet-settings") settings_button.connect("clicked", self.settings_click_cb) toolbar.insert(settings_button, -1) settings_button.show() event_box = Gtk.EventBox() event_box.connect("motion-notify-event", self.motion_to_cb) event_box.connect("button-press-event", self.button_press_cb) event_box.connect("button-release-event", self.button_release_cb) top_box.attach(event_box, 0, 1, 1, 1) event_box.props.expand = True event_box.show() view_widget = GeglGtk.View() view_widget.set_node(self.nodes['main_over']) view_widget.set_autoscale_policy(GeglGtk.ViewAutoscale.DISABLED) view_widget.set_size_request(800, 400) event_box.add(view_widget) view_widget.show() xsheet_widget = XSheetWidget(self.xsheet) top_box.attach(xsheet_widget, 1, 1, 1, 1) xsheet_widget.show() def run(self): return Gtk.main() def destroy_cb(self, *ignored): Gtk.main_quit() def size_allocate_cb(self, widget, allocation): background_node = self.nodes['background'] self.nodes['background'].set_property("width", allocation.width) self.nodes['background'].set_property("height", allocation.height) def motion_to_cb(self, widget, event): # FIXME, better disconnect if self.surface is None: return (x, y, time) = event.x, event.y, event.time pressure = event.get_axis(Gdk.AxisUse.PRESSURE) if pressure is None: pressure = 0.5 xtilt = event.get_axis(Gdk.AxisUse.XTILT) ytilt = event.get_axis(Gdk.AxisUse.YTILT) if xtilt is None or ytilt is None: xtilt = 0 ytilt = 0 dtime = (time - self.last_event[2]) / 1000.0 if self.button_pressed: self.surface.begin_atomic() self.brush.stroke_to(self.surface.backend, x, y, pressure, xtilt, ytilt, dtime) self.surface.end_atomic() self.last_event = (x, y, time) def button_press_cb(self, widget, event): if self.force_add_cel: self.xsheet.add_cel() self.button_pressed = True def button_release_cb(self, widget, event): self.button_pressed = False self.brush.reset() def xsheet_changed_cb(self, xsheet): self.update_surface() self.update_graph() def update_surface(self): cel = self.xsheet.get_cel() if cel is not None: self.surface = cel.surface self.surface_node = cel.surface_node else: self.surface = None self.surface_node = None def toggle_play_stop(self): if self.xsheet.is_playing: self.xsheet.stop() else: self.xsheet.play() def toggle_play_cb(self, widget): self.toggle_play_stop() def toggle_onionskin(self): self.onionskin_on = not self.onionskin_on for layer_idx in range(self.xsheet.layers_length): layer_nodes = self.nodes['layer_nodes'][layer_idx] onionskin_opacities = layer_nodes['onionskin']['opacities'] current_cel_over = layer_nodes['current_cel_over'] if self.onionskin_on: onionskin_opacities[0].connect_to("output", current_cel_over, "aux") else: current_cel_over.disconnect("aux") self.update_graph() def toggle_onionskin_cb(self, widget): self.toggle_onionskin() def toggle_eraser(self): self.eraser_on = not self.eraser_on if self.eraser_on: self.brush.brushinfo.set_base_value("eraser", 1.0) self.brush.brushinfo.set_base_value("radius_logarithmic", self.default_radius * 3) else: self.brush.brushinfo.set_base_value("eraser", self.default_eraser) self.brush.brushinfo.set_base_value("radius_logarithmic", self.default_radius) def toggle_eraser_cb(self, widget): self.toggle_eraser() def toggle_metronome(self): if self.metronome.is_on(): self.metronome.activate() else: self.metronome.deactivate() def toggle_metronome_cb(self, widget): self.toggle_metronome() def settings_click_cb(self, widget): dialog = SettingsDialog(widget.get_toplevel()) dialog.show() def key_press_cb(self, widget, event): if event.keyval == Gdk.KEY_Up: self.xsheet.previous_frame() elif event.keyval == Gdk.KEY_Down: self.xsheet.next_frame() def key_release_cb(self, widget, event): if event.keyval == Gdk.KEY_c: self.xsheet.add_cel() elif event.keyval == Gdk.KEY_p: self.toggle_play_stop() elif event.keyval == Gdk.KEY_o: self.toggle_onionskin() elif event.keyval == Gdk.KEY_e: self.toggle_eraser() elif event.keyval == Gdk.KEY_BackSpace: # FIXME, needs to be done in gegl backend if self.surface is not None: self.surface.clear() elif event.keyval == Gdk.KEY_Left: self.xsheet.previous_layer() elif event.keyval == Gdk.KEY_Right: self.xsheet.next_layer()
class Application(Gtk.Application): _INSTANCE = None def __init__(self): assert Application._INSTANCE is None Gtk.Application.__init__(self) Application._INSTANCE = self self.connect("activate", self._activate_cb) def setup(self): self._set_default_settings() self._xsheet = XSheet() self._xsheet.connect("cursor-changed", self._cursor_changed_cb) self._canvas_graph = CanvasGraph(self._xsheet) self._metronome = Metronome(self._xsheet) self._setup_icons() self._init_ui() if os.path.exists('test.zip'): self._xsheet.load('test.zip') def _activate_cb(self, app): self.setup() self._main_window.present() def get_metronome(self): return self._metronome def _about_cb(self, action, state): print("About") def _quit_cb(self, action, state): self._quit() def _new_cb(self, action, state): self._xsheet.new() def _cut_cb(self, action, state): self._xsheet.cut() def _copy_cb(self, action, state): self._xsheet.copy() def _paste_cb(self, action, state): self._xsheet.paste() def _remove_clear_cb(self, action, state): self._xsheet.remove_clear() def _next_frame_cb(self, action, state): self._xsheet.next_frame() def _previous_frame_cb(self, action, state): self._xsheet.previous_frame() def _next_layer_cb(self, action, state): self._xsheet.next_layer() def _previous_layer_cb(self, action, state): self._xsheet.previous_layer() def _pan_view_up_cb(self, action, state): self._main_window.get_canvas_widget().pan_view("up") def _pan_view_down_cb(self, action, state): self._main_window.get_canvas_widget().pan_view("down") def _pan_view_left_cb(self, action, state): self._main_window.get_canvas_widget().pan_view("left") def _pan_view_right_cb(self, action, state): self._main_window.get_canvas_widget().pan_view("right") def _zoom_view_in_cb(self, action, state): self._main_window.get_canvas_widget().zoom_view(1) def _zoom_view_out_cb(self, action, state): self._main_window.get_canvas_widget().zoom_view(-1) def _activate_toggle_cb(window, action, data=None): action.change_state(GLib.Variant('b', not action.get_state())) def _change_fullscreen_cb(self, action, state): if state.unpack(): self._main_window.fullscreen() else: self._main_window.unfullscreen() action.set_state(state) def _change_timeline_cb(self, action, state): if state.unpack(): self._main_window.get_xsheet_widget().show() else: self._main_window.get_xsheet_widget().hide() action.set_state(state) def _change_play_cb(self, action, state): if state.unpack(): self._xsheet.play(_settings['play']['loop']) else: self._xsheet.stop() action.set_state(state) def _change_play_loop_cb(self, action, state): if state.unpack(): _settings['play']['loop'] = True else: _settings['play']['loop'] = False action.set_state(state) def _change_onionskin_cb(self, action, state): if state.unpack(): self._canvas_graph.set_onionskin_enabled(True) else: self._canvas_graph.set_onionskin_enabled(False) action.set_state(state) def _change_eraser_cb(self, action, state): if state.unpack(): self._set_eraser_enabled(True) else: self._set_eraser_enabled(False) action.set_state(state) def _change_metronome_cb(self, action, state): if state.unpack(): self._metronome.activate() else: self._metronome.deactivate() action.set_state(state) def _quit(self): self._xsheet.save('test.zip') Gtk.Application.quit(self) def _set_default_settings(self): brush = MyPaint.Brush() brush_def = open('../mypaint/brushes/classic/charcoal.myb').read() brush.from_string(brush_def) set_base_color(brush, (0.0, 0.0, 0.0)) self._default_eraser = get_base_value(brush, "eraser") self._default_radius = get_base_value(brush, "radius_logarithmic") _settings['brush'] = brush _settings['onionskin'] = {} _settings['onionskin']['on'] = True _settings['onionskin']['by_cels'] = True _settings['onionskin']['length'] = 3 _settings['onionskin']['falloff'] = 0.5 _settings['eraser'] = {} _settings['eraser']['on'] = False _settings['play'] = {} _settings['play']['loop'] = False def _setup_icons(self): factory = Gtk.IconFactory() icon_names = ['xsheet-onionskin', 'xsheet-play', 'xsheet-eraser', 'xsheet-clear', 'xsheet-metronome', 'xsheet-settings', 'xsheet-prev-layer', 'xsheet-next-layer'] for name in icon_names: filename = os.path.join('data', 'icons', name + '.svg') pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) iconset = Gtk.IconSet.new_from_pixbuf(pixbuf) factory.add(name, iconset) factory.add_default() def _init_ui(self): self._main_window = ApplicationWindow(self, self._xsheet, self._canvas_graph) def add_simple_actions(obj, actions): for action_name, action_cb in actions: action = Gio.SimpleAction(name=action_name) action.connect("activate", action_cb) obj.add_action(action) def add_toggle_actions(obj, actions): for action_name, change_cb, enabled in actions: action = Gio.SimpleAction.new_stateful(action_name, None, GLib.Variant('b', enabled)) action.connect("activate", self._activate_toggle_cb) action.connect("change-state", change_cb) obj.add_action(action) app_actions = ( ("about", self._about_cb), ("quit", self._quit_cb), ) add_simple_actions(self, app_actions) win_actions = ( ("new", self._new_cb), ("cut", self._cut_cb), ("copy", self._copy_cb), ("paste", self._paste_cb), ("remove_clear", self._remove_clear_cb), ("next_frame", self._next_frame_cb), ("previous_frame", self._previous_frame_cb), ("next_layer", self._next_layer_cb), ("previous_layer", self._previous_layer_cb), ("pan_view_up", self._pan_view_up_cb), ("pan_view_down", self._pan_view_down_cb), ("pan_view_left", self._pan_view_left_cb), ("pan_view_right", self._pan_view_right_cb), ("zoom_view_in", self._zoom_view_in_cb), ("zoom_view_out", self._zoom_view_out_cb), ) add_simple_actions(self._main_window, win_actions) toggle_actions = ( ("fullscreen", self._change_fullscreen_cb, False), ("timeline", self._change_timeline_cb, True), ("play", self._change_play_cb, False), ("play_loop", self._change_play_loop_cb, False), ("onionskin", self._change_onionskin_cb, True), ("eraser", self._change_eraser_cb, False), ("metronome", self._change_metronome_cb, False), ) add_toggle_actions(self._main_window, toggle_actions) non_menu_accels = ( ("o", "win.onionskin", None), ("e", "win.eraser", None), ("BackSpace", "win.remove_clear", None), ("<Control>Up", "win.previous_frame", None), ("<Control>Down", "win.next_frame", None), ("<Control>Left", "win.previous_layer", None), ("<Control>Right", "win.next_layer", None), ("<Control><Shift>Up", "win.pan_view_up", None), ("<Control><Shift>Down", "win.pan_view_down", None), ("<Control><Shift>Left", "win.pan_view_left", None), ("<Control><Shift>Right", "win.pan_view_right", None), ("comma", "win.zoom_view_out", None), ("period", "win.zoom_view_in", None), ) for accel, action_name, parameter in non_menu_accels: self.add_accelerator(accel, action_name, parameter) builder = Gtk.Builder() builder.add_from_file("menu.ui") self.set_app_menu(builder.get_object("app-menu")) self.set_menubar(builder.get_object("menubar")) self._main_window.connect("destroy", self._destroy_cb) self._main_window.create_widgets() def _destroy_cb(self, *ignored): self._quit() def _set_eraser_enabled(self, enabled): _settings['eraser']['on'] = enabled brush = _settings['brush'] if _settings['eraser']['on']: set_base_value(brush, "eraser", 1.0) set_base_value(brush, "radius_logarithmic", self._default_radius * 3) else: set_base_value(brush, "eraser", self._default_eraser) set_base_value(brush, "radius_logarithmic", self._default_radius) def _cursor_changed_cb(self, xsheet): cut_action = self._main_window.lookup_action("cut") copy_action = self._main_window.lookup_action("copy") if cut_action.props.enabled and self._xsheet.get_cel() is None: cut_action.props.enabled = False copy_action.props.enabled = False elif not cut_action.props.enabled and self._xsheet.get_cel() is not None: cut_action.props.enabled = True copy_action.props.enabled = True