diff --git a/bin/curvecalc b/bin/curvecalc --- a/bin/curvecalc +++ b/bin/curvecalc @@ -357,7 +357,8 @@ root.bind("",savekey) root.bind("", lambda evt: dispatcher.send('reload all subs')) create_status_lines(root) -for helpline in ["Bindings: C-s save subterms; B1 drag point; C-B1 curve add point; S-B1 sketch points; Del point under mouse; 1..5 add point at time; Esc see current time; S-Esc see curtime to end; Mousewheel zoom; C-p play/pause music at mouse", +for helpline in ["Bindings: C-s save subterms; Esc see current time; S-Esc see curtime to end; Mousewheel zoom; C-p play/pause music at mouse", + "Curve point bindings: B1 drag point; C-B1 curve add point; S-B1 sketch points; Del selected points; 1..5 add point at time; Alt-Shift-B1 drag select points", "Available in functions: nsin/ncos period=amp=1; within(a,b) bef(x) aft(x) compare to time; smoove(x) cubic smoothstep; curvename(t) eval curve"]: tk.Label(root,text=helpline, font="Helvetica -12 italic", anchor='w').pack(side='top',fill='x') diff --git a/light9/curve.py b/light9/curve.py --- a/light9/curve.py +++ b/light9/curve.py @@ -50,11 +50,11 @@ class Curve: i = bisect(self.points,(new_pt[0],None)) self.points.insert(i,new_pt) - def indices_between(self, x1, x2): - leftidx = max(0, bisect_left(self.points, (x1,None)) - 1) - rightidx = min(len(self.points) - 1, - bisect_left(self.points, (x2,None)) + 1) - return range(leftidx, rightidx+1) + def indices_between(self, x1, x2, beyond=0): + leftidx = max(0, bisect(self.points, (x1,None)) - beyond) + rightidx = min(len(self.points), + bisect(self.points, (x2,None)) + beyond) + return range(leftidx, rightidx) def points_between(self, x1, x2): return [self.points[i] for i in self.indices_between(x1,x2)] @@ -136,6 +136,7 @@ class Curveview(tk.Canvas): dispatcher.connect(self.input_time,"input time") dispatcher.connect(self.update_curve,"zoom changed") dispatcher.connect(self.update_curve,"points changed",sender=self.curve) + dispatcher.connect(self.select_between,"select between") self.bind("",self.update_curve) for x in range(1, 6): def add_kb_marker_point(evt, x=x): @@ -161,14 +162,56 @@ class Curveview(tk.Canvas): dispatcher.send("music seek", t=self.world_from_screen(ev.x,0)[0])) + self.bind("", + self.dotmotion, add=True) + self.bind("", + self.dotrelease, add=True) + + # this binds on c-a-b1, etc - RegionZoom(self, self.world_from_screen, self.screen_from_world) + self.regionzoom = RegionZoom(self, self.world_from_screen, + self.screen_from_world) self.sketch = None # an in-progress sketch self.bind("", self.sketch_press) self.bind("", self.sketch_motion) self.bind("", self.sketch_release) + + # hold alt-shift to select, since i had a hard time detecting + # other combos right + self.selecting = False + self.bind("", self.select_press) + self.bind("", self.select_motion, add=True) + self.bind("", self.select_release) + + self.bind("", self.check_deselect, add=True) + + def check_deselect(self,ev): + try: + self.find_index_near(ev.x, ev.y) + except ValueError: + self.selected_points[:] = [] + self.highlight_selected_dots() + + def select_press(self,ev): + if not self.selecting: + self.selecting = True + self.select_start = self.world_from_screen(ev.x,0)[0] + cursors.push(self,"gumby") + + def select_motion(self,ev): + if not self.selecting: + return + + def select_release(self,ev): + if not self.selecting: + return + cursors.pop(self) + self.selecting = False + s,e = (self.select_start, self.world_from_screen(ev.x,0)[0]) + self.select_between(min(s,e), max(s,e)) + def sketch_press(self,ev): self.sketch = Sketch(self,ev) @@ -209,8 +252,9 @@ class Curveview(tk.Canvas): visible_x = (self.world_from_screen(0,0)[0], self.world_from_screen(self.winfo_width(),0)[0]) - visible_points = self.curve.points_between(*visible_x) - visible_idxs = self.curve.indices_between(*visible_x) + visible_idxs = self.curve.indices_between(visible_x[0], visible_x[1], + beyond=1) + visible_points = [cp[i] for i in visible_idxs] self.delete('curve') @@ -280,7 +324,7 @@ class Curveview(tk.Canvas): # print ev.state # self.bind("",curs) # self.bind("",lambda ev: curs(0)) - self.tag_bind(line,"",self.newpointatmouse) + self.tag_bind(line,"",self.new_point_at_mouse) def _draw_handle_points(self,visible_idxs,visible_points): @@ -299,10 +343,6 @@ class Curveview(tk.Canvas): tags=('curve','point', 'handle%d' % i)) self.tag_bind('handle%d' % i,"", lambda ev,i=i: self.dotpress(ev,i)) - self.bind("", - lambda ev,i=i: self.dotmotion(ev,i)) - self.bind("", - lambda ev,i=i: self.dotrelease(ev,i)) #self.tag_bind('handle%d' % i, "", # lambda ev, i=i: self.remove_point_idx(i)) @@ -312,18 +352,21 @@ class Curveview(tk.Canvas): # had a hard time tag_binding to the points, so i trap at # the widget level (which might be nice anyway when there # are multiple pts selected) - tags = self.gettags(self.find_closest(ev.x, ev.y)) - try: - handletags = [t for t in tags if t.startswith('handle')] - i = int(handletags[0][6:]) - except IndexError: - return - self.remove_point_idx(i) + if self.selected_points: + self.remove_point_idx(*self.selected_points) self.bind("", delpoint) self.highlight_selected_dots() + + def find_index_near(self,x,y): + tags = self.gettags(self.find_closest(x, y)) + try: + handletags = [t for t in tags if t.startswith('handle')] + return int(handletags[0][6:]) + except IndexError: + raise ValueError("no point found") - def newpointatmouse(self, ev): + def new_point_at_mouse(self, ev): p = self.world_from_screen(ev.x,ev.y) x, y = p y = max(0, y) @@ -336,8 +379,29 @@ class Curveview(tk.Canvas): self.curve.insert_pt(p) self.update_curve() - def remove_point_idx(self, i): - self.curve.points.pop(i) + def remove_point_idx(self, *idxs): + idxs = list(idxs) + while idxs: + i = idxs.pop() + + self.curve.points.pop(i) + newsel = [] + newidxs = [] + for si in range(len(self.selected_points)): + sp = self.selected_points[si] + if sp == i: + continue + if sp > i: + sp -= 1 + newsel.append(sp) + for ii in range(len(idxs)): + if ii > i: + ii -= 1 + newidxs.append(idxs[ii]) + + self.selected_points[:] = newsel + idxs[:] = newidxs + self.update_curve() def highlight_selected_dots(self): @@ -348,29 +412,47 @@ class Curveview(tk.Canvas): self.itemconfigure(d,fill='blue') def dotpress(self,ev,dotidx): - self.selected_points=[dotidx] + if dotidx not in self.selected_points: + self.selected_points=[dotidx] + self.highlight_selected_dots() + self.last_mouse_world = self.world_from_screen(ev.x, ev.y) + + def select_between(self,start,end): + self.selected_points = self.curve.indices_between(start,end) self.highlight_selected_dots() - def dotmotion(self,ev,dotidx): + def dotmotion(self,ev): + if not ev.state & 256: + return # not lmb-down cp = self.curve.points moved=0 + + cur = self.world_from_screen(ev.x, ev.y) + delta = (cur[0] - self.last_mouse_world[0], + cur[1] - self.last_mouse_world[1]) + self.last_mouse_world = cur + for idx in self.selected_points: - x,y = self.world_from_screen(ev.x,ev.y) - y = max(0,min(1,y)) - if idx>0 and x<=cp[idx-1][0]: + + newp = [cp[idx][0] + delta[0], cp[idx][1] + delta[1]] + + newp[1] = max(0,min(1,newp[1])) + + if idx>0 and newp[0] <= cp[idx-1][0]: continue - if idx=cp[idx+1][0]: + if idx= cp[idx+1][0]: continue moved=1 - cp[idx] = (x,y) + cp[idx] = tuple(newp) if moved: self.update_curve() + def unselect(self): self.selected_points=[] self.highlight_selected_dots() - def dotrelease(self,ev,dotidx): - self.unselect() + def dotrelease(self,ev): + pass #self.unselect() class Curveset: curves = None # curvename : curve diff --git a/light9/zoomcontrol.py b/light9/zoomcontrol.py --- a/light9/zoomcontrol.py +++ b/light9/zoomcontrol.py @@ -170,7 +170,7 @@ class Zoomcontrol(object,tk.Canvas): class RegionZoom: - """rigs c-a-b1 to drag out an area to zoom to. + """rigs c-a-b1 to drag out an area to zoom to. also catches other types of drag events, like b1 drag for selecting points this is used with Curveview """ @@ -181,16 +181,26 @@ class RegionZoom: for evtype, method in [("ButtonPress-1",self.press), ("Motion",self.motion), ("ButtonRelease-1",self.release)]: - canvas.bind("" % evtype, method) - if evtype != "ButtonPress-1": - canvas.bind("<%s>" % evtype, method) + #canvas.bind("" % evtype, method, add=True) + if 1 or evtype != "ButtonPress-1": + canvas.bind("<%s>" % evtype, method,add=True) canvas.bind("", self.finish) self.start_t = self.old_cursor = None - self.state = None + self.state = self.mods = None def press(self,ev): if self.state is not None: self.finish() + + if ev.state == 12: + self.mods = "c-a" + elif ev.state == 13: + self.mods = "c-s-a" + elif ev.state == 0: + return # no + self.mods = "none" + else: + return self.state = "buttonpress" self.start_t = self.end_t = self.world_from_screen(ev.x,0)[0] @@ -236,19 +246,22 @@ class RegionZoom: if abs(self.start_x - ev.x) < 10: # clicked - factor = 1/1.5 - if ev.state & 1: - factor = 1.5 # c-s-a-b1 zooms out - dispatcher.send("zoom about mouse", - t=self.start_t, - factor=factor) + if self.mods in ("c-a", "c-s-a"): + factor = 1/1.5 + if self.mods == "c-s-a": + factor = 1.5 # c-s-a-b1 zooms out + dispatcher.send("zoom about mouse", + t=self.start_t, + factor=factor) self.finish() return - - dispatcher.send("zoom to range", - start=min(self.start_t, self.end_t), - end=max(self.start_t, self.end_t)) + + start,end = min(self.start_t, self.end_t),max(self.start_t, self.end_t) + if self.mods == "c-a": + dispatcher.send("zoom to range", start=start, end=end) + elif self.mods == "none": + dispatcher.send("select between", start=start, end=end) self.finish() def finish(self, *ev):