Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TODO29
-rwxr-xr-xcolors.py101
2 files changed, 92 insertions, 38 deletions
diff --git a/TODO b/TODO
index da01c18..a732ad7 100644
--- a/TODO
+++ b/TODO
@@ -1,28 +1,15 @@
Colors!
-- Save colors to letter keys using Ctrl+Letter.
-- Finish zooming and panning.
-- Speed up intro animation.
-- Sort out idle vs paint priority issue.
++ Add MIME type, link to gallery site.
++ Help dropdown - text based is fine.
++ Publish tutorials on DailyMotion
++ New toolbars.
++ Reimplement reference image as a way to set the initial canvas.
+ Toolbar repaint issue.
+ Get tutorial paintings from Scott Nash.
+ Improve opacity and size scrollbar behavior: click to set. Maybe a custom widget?
-+ Implement pickup color tool.
-+ Add FPS counter.
-+ Optimize framerate.
-+ Improve videopaint
- + Use Nirav's pygame extension.
- + Calibration button.
-+ Test Journal save & resume.
-+ Fix cursor drawing.
-+ Implement Copy & Paste.
++ Add icons above and below opacity and size scrollbars.
++ Make the color picker come up instantly.
+ Implement joypad key bindings.
-+ Implement stylus support using evtest.
-+ Speed up painting performance of large brushes.
-+ Single stroke quick undo.
-+ Button to adjust playback speed.
-- Implement Wacom support.
-- Fullscreen button.
++ Combo to adjust playback speed.
+ Confirm image clear.
-- Ignore painting while playing back. Make them pause first!
-+ Copy should create a new 1x gdk.Image.
diff --git a/colors.py b/colors.py
index eb39a69..043b8a6 100755
--- a/colors.py
+++ b/colors.py
@@ -472,10 +472,10 @@ class Colors(activity.Activity, ExportedGObject):
self.zoomoutbtn = toolbutton.ToolButton('zoom-out')
self.zoomoutbtn.set_tooltip(_("Zoom Out"))
self.zoomoutbtn.connect('clicked', self.on_zoom_out)
- self.zoomoutbtn.props.accelerator = '<Ctrl>X'
+ self.zoomoutbtn.props.accelerator = '<Ctrl>Down'
self.zoominbtn = toolbutton.ToolButton('zoom-in')
self.zoominbtn.set_tooltip(_("Zoom In"))
- self.zoominbtn.props.accelerator = '<Ctrl>Z'
+ self.zoominbtn.props.accelerator = '<Ctrl>Up'
self.zoominbtn.connect('clicked', self.on_zoom_in)
self.centerbtn = toolbutton.ToolButton('zoom-original')
self.centerbtn.set_tooltip(_("Center Image"))
@@ -491,6 +491,11 @@ class Colors(activity.Activity, ExportedGObject):
self.editsep.set_expand(True)
self.editsep.set_draw(False)
+ self.undobtn = toolbutton.ToolButton('edit-undo')
+ self.undobtn.set_tooltip(_("Undo"))
+ self.undobtn.connect('clicked', self.on_undo)
+ self.undobtn.props.accelerator = '<Ctrl>Z'
+
self.copybtn = toolbutton.ToolButton('edit-copy')
self.copybtn.set_tooltip(_("Copy"))
self.copybtn.connect('clicked', self.on_copy)
@@ -545,6 +550,7 @@ class Colors(activity.Activity, ExportedGObject):
paintbox.insert(self.centerbtn, -1)
paintbox.insert(self.fullscreenbtn, -1)
paintbox.insert(self.editsep, -1)
+ paintbox.insert(self.undobtn, -1)
paintbox.insert(self.copybtn, -1)
#paintbox.insert(self.refsep, -1)
#paintbox.insert(self.takerefbtn, -1)
@@ -564,15 +570,15 @@ class Colors(activity.Activity, ExportedGObject):
self.pausebtn.set_tooltip(_("Pause Playback"))
self.pausebtn.connect('clicked', self.on_pause)
- self.backonebtn = toolbutton.ToolButton('media-seek-backward')
- self.backonebtn.set_tooltip(_("Back One Stroke"))
- self.backonebtn.connect('clicked', self.on_back_one)
- self.backonebtn.props.accelerator = '<Ctrl>Left'
+ #self.backonebtn = toolbutton.ToolButton('media-seek-backward')
+ #self.backonebtn.set_tooltip(_("Back One Stroke"))
+ #self.backonebtn.connect('clicked', self.on_back_one)
+ #self.backonebtn.props.accelerator = '<Ctrl>Left'
- self.forwardonebtn = toolbutton.ToolButton('media-seek-forward')
- self.forwardonebtn.set_tooltip(_("Forward One Stroke"))
- self.forwardonebtn.connect('clicked', self.on_forward_one)
- self.forwardonebtn.props.accelerator = '<Ctrl>Right'
+ #self.forwardonebtn = toolbutton.ToolButton('media-seek-forward')
+ #self.forwardonebtn.set_tooltip(_("Forward One Stroke"))
+ #self.forwardonebtn.connect('clicked', self.on_forward_one)
+ #self.forwardonebtn.props.accelerator = '<Ctrl>Right'
# Position bar
self.playbackpossep = gtk.SeparatorToolItem()
@@ -598,12 +604,10 @@ class Colors(activity.Activity, ExportedGObject):
playbox = gtk.Toolbar()
playbox.insert(self.startbtn, -1)
playbox.insert(self.pausebtn, -1)
- playbox.insert(self.backonebtn, -1)
- playbox.insert(self.forwardonebtn, -1)
- playbox.insert(self.playbackpossep, -1)
playbox.insert(self.beginbtn, -1)
- playbox.insert(self.playbackpositem, -1)
playbox.insert(self.endbtn, -1)
+ playbox.insert(self.playbackpossep, -1)
+ playbox.insert(self.playbackpositem, -1)
# Sample files to learn from. Reads the list from an INDEX file in the data folder.
samplebox = gtk.Toolbar()
@@ -856,6 +860,7 @@ class Colors(activity.Activity, ExportedGObject):
if self.connected:
# Broadcast drawing commands that were generated by this user since the last call to this function.
if self.draw_command_sent < self.easel.get_num_commands():
+ # TODO: Always prepend the current brush here.
buf = self.easel.send_drw_commands(self.draw_command_sent, self.easel.get_num_commands()-self.draw_command_sent)
self.BroadcastDrawCommands(buf.get_bytes(), buf.ncommands)
@@ -884,7 +889,8 @@ class Colors(activity.Activity, ExportedGObject):
self.beginbtn.set_sensitive(False)
self.endbtn.set_sensitive(False)
self.playbackposbar.set_sensitive(False)
-
+ self.undobtn.set_sensitive(False)
+
# Cannot activate sample drawings.
for s in self.samplebtns:
s.set_sensitive(False)
@@ -1253,7 +1259,7 @@ class Colors(activity.Activity, ExportedGObject):
Called to skip the playback position, for example when fast forwarding or rewinding or dragging the scrollbar."""
self.playbackposbar.ignore_change += 1
- #log.debug("play_to %d easel_pos=%d", int(self.playbackpos.get_value()/100.0*self.easel.playback_length()), self.easel.playback_pos())
+ log.debug("play_to %d easel_pos=%d", to, self.easel.playback_pos())
total_left = 0
@@ -1298,13 +1304,16 @@ class Colors(activity.Activity, ExportedGObject):
# This is currently disabled as it leads to the playback position passing the requested position, which
# can cause infinite "Working..." loops when the mouse is held down.
# Leaving it out can lead to inaccurate playback, when the play-to position is mid-stroke.
- #self.easel.playback_finish_stroke()
+ self.easel.playback_finish_stroke()
# Update the progress bar.
if total_left > 0:
f = 1.0-float(to - self.easel.playback_pos())/total_left
self.progress.progress.set_fraction(f)
+ if self.easel.playback_pos() >= to:
+ break
+
# Comment this in to watch the painting and slow things down.
#self.flush_entire_canvas()
@@ -1380,6 +1389,7 @@ class Colors(activity.Activity, ExportedGObject):
if self.mode == Colors.MODE_INTRO:
# Load and play intro movie. It was created on a DS at 60hz, so we need to speed it up drastically to
# make it watchable.
+ self.clear_undo()
self.easel.clear()
self.easel.load(str(activity.get_bundle_path() + "/data/intro.drw"))
self.easel.set_playback_speed(8)
@@ -1429,6 +1439,7 @@ class Colors(activity.Activity, ExportedGObject):
def leave_mode (self):
if self.mode == Colors.MODE_INTRO:
self.easel.stop_playback()
+ self.clear_undo()
self.easel.clear()
self.easel.reset_brush()
self.flush_entire_canvas()
@@ -1437,6 +1448,7 @@ class Colors(activity.Activity, ExportedGObject):
self.easel.stop_playback()
if self.mode == Colors.MODE_CANVAS:
+ # Finish any in-progress stroke.
self.end_draw()
if self.mode == Colors.MODE_PICK:
@@ -1475,6 +1487,7 @@ class Colors(activity.Activity, ExportedGObject):
self.easel.update_playback()
if not self.easel.playback_done():
self.flush_dirty_canvas()
+
# Update the progress bar.
progress_percent = int(100*float(self.easel.playback)/(self.easel.playback_length()+1))
if self.easel.playing and progress_percent != self.playbackpos.get_value():
@@ -1485,6 +1498,7 @@ class Colors(activity.Activity, ExportedGObject):
# Painting during playback mode allows you start where the painter left off.
# But only when playback is not active.
if not self.easel.playing and (self.cur_buttons & Colors.BUTTON_TOUCH):
+ self.clear_undo()
self.easel.truncate_at_playback()
self.set_mode(Colors.MODE_CANVAS)
return
@@ -1496,6 +1510,11 @@ class Colors(activity.Activity, ExportedGObject):
# Update drawing.
if self.cur_buttons & Colors.BUTTON_TOUCH:
+ # At the beginning of the stroke, the shared buffer is used to
+ # save the Undo position (when in single user mode).
+ if not self.easel.stroke:
+ self.save_undo()
+
if self.mx != self.lastmx or self.my != self.lastmy or self.videopaint_enabled:
self.draw(Pos(self.mx, self.my))
self.flush_dirty_canvas()
@@ -1760,6 +1779,7 @@ class Colors(activity.Activity, ExportedGObject):
if response_id is gtk.RESPONSE_OK:
if self.mode != Colors.MODE_CANVAS:
self.set_mode(Colors.MODE_CANVAS)
+ self.clear_undo()
self.easel.clear()
self.flush_entire_canvas()
@@ -1802,6 +1822,7 @@ class Colors(activity.Activity, ExportedGObject):
def on_sample (self, button):
self.set_mode(Colors.MODE_PLAYBACK)
+ self.clear_undo()
self.easel.clear()
self.easel.load(str(button.filename))
self.easel.set_playback_speed(8)
@@ -1823,6 +1844,7 @@ class Colors(activity.Activity, ExportedGObject):
def read_file(self, file_path):
log.debug("Loading from journal %s", file_path)
self.set_mode(Colors.MODE_CANVAS)
+ self.clear_undo()
self.easel.clear()
self.easel.load(str(file_path.encode()))
self.easel.start_playback()
@@ -1830,6 +1852,7 @@ class Colors(activity.Activity, ExportedGObject):
self.playbackpos.set_value(100)
self.set_mode(Colors.MODE_CANVAS)
log.debug("Played back %d commands", self.easel.playback_length())
+ self.save_undo()
def write_file(self, file_path):
log.debug("Saving to journal %s", file_path)
@@ -1847,6 +1870,50 @@ class Colors(activity.Activity, ExportedGObject):
pbuf.save(filename, "png")
#-----------------------------------------------------------------------------------------------------------------
+ # Undo
+ # Only available in non-shared mode. Undo saves a single stroke in the shared buffer
+ # for instant undo. Further undos require a replay of the drawing, which may take
+ # awhile.
+ # Redo not implement yet but shouldn't be hard to do.
+
+ def on_undo(self, button):
+ # Cannot undo when progress window is up.
+ if self.playbackposbar.ignore_change > 0:
+ return
+
+ if not self.connected and len(self.undo_buffer) > 0:
+ to = self.undo_buffer.pop()
+ print "undoing from %d to %d" % (self.easel.get_num_commands(), to)
+ if self.undo_image_valid:
+ # TODO: Add an API for truncating at arbitrary points when the C library is next changed.
+ self.easel.playback = to
+ self.easel.restore_shared_image()
+ else:
+ self.play_to(to)
+ self.easel.truncate_at_playback()
+ self.flush_entire_canvas()
+ self.undo_image_valid = False
+ self.update_undo()
+
+ def save_undo(self):
+ if not self.connected:
+ to = max(0, self.easel.get_num_commands()-1)
+ print "saving undo at ", to
+ self.undo_buffer.append(to)
+ self.easel.save_shared_image()
+ self.undo_image_valid = True
+ self.update_undo()
+
+ def clear_undo(self):
+ self.undo_buffer = []
+ self.undo_image_valid = False
+ self.update_undo()
+
+ def update_undo(self):
+ if not self.connected:
+ self.undobtn.set_sensitive(len(self.undo_buffer) > 0)
+
+ #-----------------------------------------------------------------------------------------------------------------
# Clipboard integration (ported from Oficina)
def on_copy(self, button):