News

( My personal website has gone to dust, any links thereto will fail. This site will be home for a while. )

Tuesday, 9 August 2011

If Inkscape could... wouldn't it be great?

I use Inkscape. A lot. In the Linux world there really is nothing to touch it.

I often wish.. I shake my head and regret that I can't contribute code because it's too complex for me. If I could only ...

A few blue-sky fantasies of what I would do with Inkscape. 

Take this as constructive and positive. There is no implied criticism at all. Without this amazing app I could not do what I do. Viva Inkscape!

Extending capability
In the blue-skies to come I mention many ideas that would require Inkscape to be a non-pure-svg beast of some kind. I realize that this is not part of the SVG standard that is being implemented.

I would add an 'enhanced' Inkscape format — one that contains the needed metadata and structures to manage it. Perhaps even a second file for every svg saved. Not too onerous given what it's doing. (Or maybe some database like Firefox uses.)

DRY
Don't Repeat Yourself.

Draw a logo once and use it in many other files. Alter the original and have it update everywhere.

I'd include a 'library' that would let you drill-into other svg files - a little like Blender does for linking-appending blend files.

Here's an example:
I am working on signs for a zoo. The motif is butterflies.
I have an svg with lots of drawings. I'd like to open a new svg and pull a few objects out, for the design of a poster.
I locate them in the library - it shows them visually - and drop them on my canvas.
While I'm at it, I fetch my standard insignia which I use to frame my work. Perhaps a frame with a little bit of text and a url at the bottom.

I notice some fixes I should make in a certain clone. I enter the 'clone-source edit mode' and alter the thing. It changes the original file!
Any other files using that clone when re-opened (imagine if it was all live! - that's another whole blue sky), will reflect the changes.

More than this. I imagine situations where I'd like to alter a clone, but not its source. I want bits of it to be in different colours, sizes and locations. I mark it as a fork and edit it as I please. This clone then draws its basics from the source and after that it applies my updates. Much like css, overrides come afterwards and it adapts.

I'd also like to take a certain group, turn it into a clone and place it into my 'cool shapes' library. I open that lib and drag the group into it.

The ability to fork or copy any library would be important. I may want to take something in a different direction, but not influence other users of those clones/symbols.
I could copy a library - making it unique (some kind of id hell I am sure) and then mess with it.
Forking would be similar, only I could later commit/merge and all instances would then update.

And why stop at local file paths? I'd bake-in the ability to follow any URL (be it local, network, ssh or web) to get to other files.
This would be over-arched by a kind of 'relative to X' setting: all links to other files are relative-to a path, a domain, a network share name, and any stack of them. Or just bung-on some scripted controller to re-write stuff on the fly.

At any point one can export to a normal SVG file. It would break all links and collapse whatever is needed into that ouput - providing a single svg that can move away from my hard drive.


Smart Ojects

Got a group (or a clone, or ...) that is destined to be an image on a website? Want it to output as a jpg 70% quality image? I would have more than the current 'properties' panel allows.

I would let you specify output scripts (and edit them) per object. I'd implement a cascade too, so that objects in a tree take their parent's output scripts over the children, with some flexibility - but that needs more thought.

Output scripts could cover things like path effects as well as the extensions we have now.
I could tell a path to jitter and then stretch a pattern along itself. I could change which pattern it uses dynamically!
I could tell it to duplicate itself at an offset and use another pattern along the duplicate.
I could say, hey, every 10 units place a duplicate of that clone.

And why stop at output scripts? How about input scripts, or pipes?

Say I want a clone that has text within it and I want that text to change depending on some outside influence - say a script that counts or shows the date. The entire contents of an object may be subject to a script (or a stack of them) that determines how it draws next.

Here's an example:
I have samples of logos for a client. I want a little label on each on. Logo A, logo B, logo C, etc.
I have set each logo to have an id. Why not drop a smart-label-clone into each logo and have it look-up the id and change the text! If I change the id, the label will change. Sorted.

How about objects that know where they are in relation to other objects? If one is touching another, above or below another, a percentage distance away from another - run certain scripts to change whatever is required. Perhaps a clone of a leaf can then change its tone the further back in a drawing of a tree it is placed. That kind of thing.

Entire drawings can have output and input scripts too.

Example:
Say I want this page to go out to a PDF, but I want it to collapse all the clones and convert all the filters to alpha png files etc.
I want the background colour to be different to the current one. I want it to output only what is on the page and not a whole bunch of stuff off to the side. This kind of master-control would be possible.
Say I wanted to send that PDF out to Scribus and open that app, or I want it to output a PNG of the page as well as a PDF. Etc.

Another example:
I want each object to follow its own scripts and make image files as set. I then want to herd all those files into different directories for a website I am building. I don't want to set each object's path, so I have a master script that moves all the little outputs.

Input example:
When I open this file, I want it to hit a website and fetch something. I want it to open a database and fetch something else.
I want to set variables that objects on the canvas will react to. I want to open a text file and get that into a text-area. I want to run some bash script and get its output into another script that turns into a list of colours that a bunch of other objects will use.

Another:
I have a path - let's say it's a swooping curve. What I really want is a 3D shape done in Blender. Can I not, from a script, open blender, send the path to it, run a script in Blender to extrude/colour/render and then get the result back in Inkscape? Blue sky remember. :)

Hey, why not drop a blend file into Inkscape - now it knows what to open at that position. Why not drop a text-file, why not an Open Office doc? Let's get those pipes flowing.

I would also be sure to make Inkscape scriptable from outside. At the moment the command-line is broken. Besides a general Python/Ruby/etc. approach, just using the command-line switches from bash in order to employ Inkscape as a pipe would be super powerful.

In short, I see a kind of spreadsheet capacity in Inkscape. By allowing scripts to pipe in and out between any objects the capacity is unlimited.


Abstract the code
The engine
The svg drawing engine, make it a library.

I would use it from Python. Others would use it from other things.

Imagine Open Office or Blender being able to open and render Inkscape SVG files.

I don't mean just a few curves and weirdly deformed clones. I mean the whole works: layers, filters, clones, groups, all of it. Just imagine that!

Release the magic svg renderer. The world will love you. librsvg is just not cutting the mustard.

Abstract the tools
I would take the text, line, circle, polygon, node tool, select tool and all the others and get them into a library.

I would use them in different combinations from my own Python apps. I'd like to write a little app to do animations - with simple drawings on a canvas:

from Inkscape import canvas
from Inkscape.tools import pen, text, circle

You get the idea.



Professional interface
Nodes. Oh those huge nodes. Those nodes that are like herding elephants in a small room. I would add a section to the settings where I could set the size (and colour) of my nodes. I would make them 1 pixel red dots. Really.

I'd switch off tool icons on the cursor. I'd add more options for what the cursor can look like and what angle it takes to the point it operates from.

Switching from a tool to the last one used. I'd nominate space-bar for the job, but add a general keyboard shortcuts editor too. I often want to jump between select and node. Between the gradient tool and the dropper.

Drag to/from the layer dialog, or some kind of send-to context menu. Moving stuff between layers is really tricky at the moment.

Make the gui 'go away'
I don't need bars that cross the entire canvas just to hold file, edit, etc.

I don't need all the tools when I am drawing something.

I don't want that huge fill dialog as a window, I don't like it as a panel either. I want a little on-canvas colour widget (mypaint style) that comes at a keystroke and goes in a heartbeat.

In fact, nothing but the canvas.

Nothing. But. The. Canvas.

Imagine the tools as little icons that float in a cloud around the cursor when you call them up. Recently used tools are closer to the cursor.

Imagine the Path menu as a cloud of buttons that come and go - on the canvas, at the cursor.

Colour palette, same. Path effects editor, gradient editor. All citizens of the canvas.

General notes
Random thoughts as they happened:

Gimp files
Any reason why .xcf files can't be used directly in Inkscape? If there are layers, then bring them in as a group.

Small things
Dropping images onto the canvas — auto link (or embed as per settings). Don't ask me every time.

Importing PDF — don't ask me stuff every time. Let me open all the pages in a PDF. Let me control it from settings.

Multiple pages
I would *so* have them. No quibbles.
If I had managed to do that blue-sky DRY thing I spoke of earlier, then each page could be a little universe of its own svg tree.

Output a multiple page PDF document? For sure.

Clips
It seems to happen quite often. I find I have clipped some drawing which has other clips within it. I was aiming at a design, not an efficiency.

Now I sit with this monster that can barely be dragged around because it takes so long to draw.

I want to 'simplify' it. I want to 'collapse' it.

I want to tell Inkscape: "Look, you know this monster? Please start at the bottom and give me a bunch of paths that have no clips anymore. Anything that was invisible should simply not be there. Cut paths, do what you have to, yeah?"

I'd also employ some way to make this 'live' — your original object with all the clips (the monster) would remain so. It would always be editable. Any users downstream that want the 'simple' version are going through the algorithm on file open to produce the collapsed version for actual work.

Gradients
Along a path.
Along a 'web' - I imagine drawing a radiating set of paths and the gradient would be like a spider weaving a web along those paths - a kind of radial fill, but one that can better fit any shape I want to fill.

Blurs
In similar fashion to gradients. I'd like to control the blur all around the edge of a shape. Some edges I want sharp, others less and the far side I want very blurred.

I see a kind of 'blur control path' that starts with my original path (or lets me draw one) and then I am pushing/pulling the nodes to control the blur.

Display modes
I'd give each object the ability to display in a different way. Some stuff can be outlines. Some stuff a simple fill. Other stuff full quality effects.

I would also add some kind of mipmap system so that complex objects can be quickly represented in colour but without the lag of vectors - temporarily of course.

Rotation
Occasionally I want the bounding-box to rotate. Sometimes not.
Sometimes I want to skew the rotated group and then it's too late — the skewing is not acting local to the group.

I get confused by these things — global, local et al. I just know I want an effect and that the bounding box is not on my side.

Canvas
If you are like me, your hand and arm are kind of wired to draw stuff from a certain angle. I often want to rotate the canvas to other angles so I can draw naturally.

Bookmarks
At any point in my flow, I'd like to make a bookmark. This would capture the interface, the display modes, the angle of the canvas — everything — so that I can then switch into different work-modes.

Here I am working on the top-left, miles away from the page. My clone-zone. I am zoomed-in just so. I have the mode set to outline. I want to flip back and forth to the page, but in another zoom and another mode. Bookmarks.

Multiple views
Like Blender, I would give Inkscape the multiple, non-overlapping windows treatment. Open a split and work on a clone and its, uh, clone-target at once.

3D
The current 3d box tool is a good start. The fact that I simply cannot use it is a little sad, but it shows that faux 3D is certainly on the cards.

I'd allow extruding and box-modelling on the canvas. This would be controlled by a kind of stack (input/output scripts again) so that one is always editing the path (or simplest element) and the 3D is constructed afterwards.

I'd like to implement a kind of face-drawing system. You have a plane (a face) and you can draw into that. Then you can rotate that entire face/plane/group/clone in 3 dimensions.

Entire groups could exist in 3d. Things like perspective and rotation would take on a whole new meaning.

3D text goes without saying.

It's not that the SVG would have to be 3D, but that the output of Inkscape would generate SVG such that it looks like a normal 2D svg. I'd make Inkscape more general.

Colour/Style
I'd like to change all the red to dark-red in this file. Sure, I can text-edit (sed, ftw!) the svg, but what would be much better is some kind of variable colour and style override.

I want to 'edit' a 'colour' and have all users of that colour now reflect the new colour. So, 'colours' are merely slots which can change. Same for styles — outline, thickness etc.

The master slot tables would be libraries higher-up in the chain. If I wanted to change the colour of a logo in dozens of other files, I would change the slot in the library.

Font manager
I know that currently GTK and other voodoo requires a restart of apps before new fonts are recognized. That aside — it would rule if I could have something like Fonty Python (a font manager) built-into Inkscape.

I would like to associate 'projects' and individual svg files with a certain set of fonts that then get installed as-per.


Fin
You know, I really envisage an entire Operating System interface that is basically Ink-like in nature.

Why have the GIMP as a different app? Why MyPaint? Why can't I simply edit bitmaps (and all their fancy layers) right here on my canvas?

Why not just have all the apps living on a huge canvas with visual pipes between them all hooked-into in/out scripting stacks?

Why not have different 'worlds' where other canvases exist (like virtual desktops kinda) where you can pipe between all the worlds?

Why can't my server (out on distant Linode) not be a canvas of its own here on my machine? I could specify connections from my local canvas to it and when I output a bunch of images here -- it rsyncs in the background to the linode?

Why can't
I see my brother-in-law's FTP server as a plane in my drawing, so that I am updating objects there and pulling them down?

Why can't I route the ouptut of, say, Calibre or Lyx into a stack of scripts to automate all kinds of document processing - between my local and remote canvases?

I dunno. I guess I see something a lot more dynamic and simpler than a zoo of little boxes that never speak to one-another unless you happen to copy and paste — and that's hit and miss too — or hit the dirty bash scripting zone and lose hair.

I see a world of pipes negotiated by scripts such that the fractured experience we currently have is merged into a seamless flow — so that apps become tools that are more powerful than their parts.

Thanks for reading. Resist the urge to stick pitchforks into me, I am delicate :)

August 2011
\d

Wednesday, 17 February 2010

Clutter Mouse Events

Well, it has been a fairly good week in-between the segfaults and other scary things. I have a basic system to tame mouse events so that I can respond to 'click' and 'double-click' and stuff like 'dragging' and 'drag-over'.
This is all written in Python. The licence is GPLv3.
Save them both (states.py in a directory called helpers), then run two_squares.py -- you should be able to drag them around (with different mouse buttons. They dump info to the console.)
Module 1: helpers.states.py
import clutter
import gobject

class DragEnvelope(object):
 """Helps deal with drags."""
 def __init__(self, obj, event):
  self.event = event
  self.diffx = event.x - obj.get_x()
  self.diffy = event.y - obj.get_y()
 def calcPos(self, event):
  """Where to draw the Actor relative to cursor within it."""
  self.newx = event.x - self.diffx
  self.newy = event.y - self.diffy

class Mouserizer(object):
 ## User vars to form ui mask
 HITABLE = 1<<0
 DRAGABLE = 1<<1
 CLICKABLE = 1<<2
 SCROLLABLE = 1<<3
 PICK_UNDER = 1<<4

 LEFT = 1<<0
 MIDDLE = 1<<1
 RIGHT = 1<<2

 ## Private class vars
 __FLAG_ENTERED = 1<<0 # Not used in this version.
 __FLAG_PRESSING = 1<<1
 __FLAG_RELEASING = 1<<2
 __FLAG_MOVING = 1<<3
 __FLAG_DRAGGING = 1<<4

 __PATTERN_CLICK =  __FLAG_PRESSING | __FLAG_RELEASING
 __PATTERN_DRAG_START = __FLAG_PRESSING| __FLAG_MOVING
 __PATTERN_DRAG = __PATTERN_DRAG_START | __FLAG_DRAGGING
 __PATTERN_DROP =  __FLAG_PRESSING| __FLAG_DRAGGING | __FLAG_RELEASING

 YES_CONTINUE_EMITTING = False
 NO_STOP_EMITTING = True
 
 __clutter_mouse_event_types=[
   clutter.MOTION,
   clutter.ENTER,
   clutter.LEAVE,
   clutter.BUTTON_PRESS,
   clutter.BUTTON_RELEASE,
   clutter.SCROLL
   ]

 def __init__(self, ui=None, buttons=None):
  if ui is None: 
   return # Not going to watch for any mouse events, so bug out.
  self.buttons = buttons
  
  ## If we want HIT kind of events, then just connect the usual suspects.
  if (ui & Mouserizer.HITABLE) !=0: # test bit
   self.connect('enter-event', self.on_enter_event)
   self.connect('leave-event', self.on_leave_event)

  self.__PICK_UNDER = False
  if (ui & Mouserizer.PICK_UNDER) !=0:
   self.__PICK_UNDER = True

  ## Keep a record of what we are going to listen to.
  self.ui = ui

  ## Enable the actor (self) to receive events.
  self.set_reactive(True)

  ## This is the state of our situation -- it will be masked bitwise.
  self.ui_state = 0
  
  ## Route all events (for this Actor) through one function:
  self.connect('captured-event', self.event_central)

 def event_central(self, obj, event):
  ## This routine runs many times. Once for every kind
  ## of event the actor is getting.

  ## filter out only the mouse events.
  if event.type not in Mouserizer.__clutter_mouse_event_types: 
    return Mouserizer.YES_CONTINUE_EMITTING

  ## filter out buttons we are NOT going to deal with
  if hasattr(event, "button"):
   b = 1 << (event.button - 1)
   #print bin(b)," vs ", bin(self.buttons)
   if not(b & self.buttons !=0 ):
    return Mouserizer.NO_STOP_EMITTING # is this wise?

  ## event_central ONLY runs when cursor is
  ## over the actor -- thus ENTER is implied.
  
  ## Make a note of PRESS/RELEASE
  if event.type==clutter.BUTTON_PRESS:
   self.ui_state = self.ui_state | Mouserizer.__FLAG_PRESSING # set bit
  if event.type==clutter.BUTTON_RELEASE:
   self.ui_state = self.ui_state | Mouserizer.__FLAG_RELEASING # set bit

  ## Make a note of MOTION
  ## First, clear it.
  self.ui_state = self.ui_state & ~Mouserizer.__FLAG_MOVING # clear bit
  if event.type==clutter.MOTION:
   self.ui_state = self.ui_state | Mouserizer.__FLAG_MOVING # set bit

  ## Now, what kinds of stuff is this actor interested in?

  ## DO META EVENTS - "More than" events. e.g. 'Click' is press, then release.
  if (self.ui & Mouserizer.CLICKABLE) != 0: # test bit
   if self.ui_state == Mouserizer.__PATTERN_CLICK:
    if event.click_count > 1:
     self.emit('double-click', event)
    else:
     ## A single click is fired just before double-click...!
     self.emit('single-click', event)

  if (self.ui & Mouserizer.DRAGABLE) !=0: # test bit
   if self.ui_state == Mouserizer.__PATTERN_DRAG_START:
    self.ui_state=self.ui_state | Mouserizer.__FLAG_DRAGGING # set bit
    self.draglet = DragEnvelope( self, event )
    ## Phew! I thought I was fcuked! In order to get dragging to 
    ## work when the pointer is NOT ON the Actor, I had to revert
    ## to grab_pointer* -- and that needs connecting. I connected the
    ## two appropriate event to *this* same function! And it works :D
    ##
    ## * grab_pointer causes the entire window (stage?) to focus on the
    ##   Actor passed -- so I get all motion and release events even where
    ##   the Actor aint.
    ##   ! Not sure what kind of recursive issues this may throw at me :(
    clutter.grab_pointer( self )
    self.connect('motion-event', self.event_central)
    self.connect('button-release-event', self.event_central)
    self.emit('drag-start', self.draglet )
   elif self.ui_state == Mouserizer.__PATTERN_DRAG:
    self.draglet.calcPos( event ) # A 'draglet' is a little wrapper containing the event and some tricks.
    ## Who is under me? Only do if PICK_UNDER flag is set.
    if self.__PICK_UNDER:
     self.hide()
     a = self.stage.get_actor_at_pos(clutter.PICK_REACTIVE, int(event.x),int(event.y))
     self.show()    
     ## a is!
     ## Only emit if a has a drag-over signal:
     if gobject.signal_lookup('drag-over', a ):
      print a, " under me"
      a.emit('drag-over', self.draglet)
    self.emit('dragging', self.draglet)
    
   elif self.ui_state == Mouserizer.__PATTERN_DROP:
    self.draglet.calcPos( event )
    self.ui_state= self.ui_state & ~Mouserizer.__FLAG_DRAGGING # clear bit
    clutter.ungrab_pointer()
    self.emit("drop", self.draglet)
    del(self.draglet)
   
  ## META EVENTS are done.

  ## Flip opposites off.
  if event.type==clutter.BUTTON_PRESS:
   self.ui_state = self.ui_state & ~Mouserizer.__FLAG_RELEASING # clear bit
  if event.type==clutter.BUTTON_RELEASE:
   self.ui_state = self.ui_state & ~Mouserizer.__FLAG_PRESSING # clear bit
   self.ui_state = self.ui_state & ~Mouserizer.__FLAG_RELEASING # clear bit
   self.ui_state = self.ui_state & ~Mouserizer.__FLAG_DRAGGING # clear bit

  return Mouserizer.YES_CONTINUE_EMITTING

(Always with the rectangles eh? :D)
import clutter
from helpers.states import Mouserizer as M
import gobject

meta_signals = {
 'single-click' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,) ),
 'double-click' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,) ),
 'drag-start' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,) ),
 'dragging' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,) ),
 'drop' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,) ),
 'drag-over' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,) ),
}

class DragHandle(clutter.Rectangle, M):
 ## Make this class a GObject type
 __gtype_name__ = 'DragHandle'
 ## Specify which signals it can receieve.
 __gsignals__ =  meta_signals

 def __init__(self, stage, col, ui=None, buttons=None):
  """
  You don't have to connect: enter-event, leave-event, single-click or double-click
  """
  if ui is None:
   ui = M.HITABLE
  if buttons is None:
   buttons = M.LEFT
  clutter.Actor.__init__(self)
  M.__init__(self, ui=ui, buttons=buttons)
  self.stage = stage
  
  self.set_color( clutter.color_from_string(col))
  self.set_size(50,50)

 def do_drag_over(self, draglet):
  print "DRAG OVER ", draglet
  return M.NO_STOP_EMITTING 

 def do_single_click(self, *args):
  print "HANDLER CLICK"
  return M.NO_STOP_EMITTING

 def do_double_click(self, *args):
  print "HANDLER DCLICK"
  clutter.main_quit()

 def do_drag_start(self, draglet):
  print "DRAG STARTS:"
  return True

 def do_dragging(self, draglet):
  self.set_position(draglet.newx, draglet.newy)
  return True

 def do_drop(self, *args):
  print "DROP!"
  return True

 def on_enter_event(self, obj, evt):
  print "ENTERING ",obj
  return True

 def on_leave_event(self,obj,evt):
  print "LEAVING ", obj
  return True
 
if __name__ == '__main__':
 stage = clutter.Stage()
 stage.set_size(640, 480)
 stage.set_color(clutter.color_from_string('Black'))

 stage.connect('destroy', clutter.main_quit)

 test=DragHandle( stage, "Green",  ui=M.HITABLE | M.CLICKABLE | M.DRAGABLE, buttons= M.RIGHT )
 stage.add(test)
 test.set_position(100,100)
 test.show()

 test2=DragHandle(stage, "Red",  ui=M.HITABLE | M.DRAGABLE | M.PICK_UNDER , buttons= M.LEFT )
 stage.add(test2)
 test2.show()
 
 stage.show()
 clutter.main()

Well, I hope that helps someone.
\d