move painter to its own file

This commit is contained in:
Ian Jauslin 2021-11-03 22:36:28 -04:00
parent a702a4a991
commit 152f70d402
3 changed files with 279 additions and 242 deletions

241
cross.py
View File

@ -1,8 +1,6 @@
import math import math
import sys import sys
from kivy.graphics import Color,Line,Rectangle from kivy.graphics import Color,Line,Rectangle
from kivy.uix.widget import Widget
from kivy.core.window import Window
from point import Point from point import Point
from tools import isint_nonzero,sgn from tools import isint_nonzero,sgn
@ -213,242 +211,3 @@ def cross_polar(t):
# cross painter
class Cross_painter(Widget):
def __init__(self,app,**kwargs):
# list of crosses
self.crosses=[]
# cross under mouse
self.undermouse=None
# list of selected crosses
self.selected=[]
# app is used to share information between widgets
self.app=app
# modifiers
self.modifiers=[]
# init Widget
super(Cross_painter,self).__init__(**kwargs)
# init keyboard
self.keyboard = Window.request_keyboard(None,self,"text")
self.keyboard.bind(on_key_down=self.on_key_down,on_key_up=self.on_key_up)
def reset(self):
self.crosses=[]
self.undermouse=None
self.draw()
# draw all crosses
def draw(self):
self.canvas.clear()
with self.canvas:
for cross in self.crosses:
cross.draw()
# respond to keyboard
def on_key_down(self, keyboard, keycode, text, modifiers):
# check that command_prompt is not focused
if not self.app.command_prompt.insert:
if keycode[1]=="shift":
if not 's' in self.modifiers:
self.modifiers.append('s')
self.modifiers.sort()
def on_key_up(self, keyboard, keycode):
if keycode[1]=="shift":
if 's' in self.modifiers:
# remove
self.modifiers[self.modifiers.index('s')]=self.modifiers[len(self.modifiers)-1]
self.modifiers=self.modifiers[:len(self.modifiers)-1]
self.modifiers.sort()
# respond to mouse down
def on_touch_down(self,touch):
# only respond to touch in area
if self.collide_point(*touch.pos):
# create new cross
if touch.button=="right":
if self.check_interaction_any(Point(touch.x/Cross.size,touch.y/Cross.size),None):
new=Cross(touch.x/Cross.size,touch.y/Cross.size)
# add to list
self.crosses.append(new)
# unselect all crosses
for sel in self.selected:
sel.selected=False
self.selected=[]
self.draw()
# select cross
if touch.button=="left":
# find cross under touch
self.undermouse=self.find_cross(Point(touch.x/Cross.size,touch.y/Cross.size))
# no modifiers
if self.modifiers==[]:
if self.undermouse==None or not self.undermouse in self.selected:
# unselect all crosses
for sel in self.selected:
sel.selected=False
self.selected=[]
# shift-click
elif self.modifiers==['s']:
if self.undermouse!=None:
if self.undermouse not in self.selected:
self.selected.append(self.undermouse)
self.undermouse.selected=True
else:
# remove
self.selected[self.selected.index(self.undermouse)]=self.selected[len(self.selected)-1]
self.selected=self.selected[:len(self.selected)-1]
self.undermouse.selected=False
self.draw()
# respond to drag
def on_touch_move(self,touch):
if self.collide_point(*touch.pos):
# only move on left click
if touch.button=="left" and self.undermouse!=None:
self.undermouse.pos=self.check_move(Point(touch.x/Cross.size,touch.y/Cross.size),self.undermouse)
# redraw
self.draw()
# find the cross at position pos
def find_cross(self,pos):
for cross in self.crosses:
if cross_distx(pos,cross.pos)<=0.5 or cross_disty(pos,cross.pos)<=0.5:
return cross
# none found
return None
# check whether a position intersects with any of the crosses
def check_interaction_any(self,pos,exception):
for other in self.crosses:
if other!=exception:
if other.check_interaction(pos)==False:
return False
return True
# check that a cross can move to new position
def check_move(self,newpos,cross):
# whether newpos is acceptable
accept_newpos=True
for other in self.crosses:
# do not compare a cross to itself
if other!=cross:
# move would make cross overlap with other
if other.check_interaction(newpos)==False:
accept_newpos=False
# check if cross touches other
if other.check_touch(cross.pos):
# move along other while remaining stuck
candidate=other.move_along(newpos,cross.pos)
else:
candidate=other.move_on_line_to_stick(cross.pos,newpos-cross.pos)
if self.check_interaction_any(candidate,cross):
return candidate
if accept_newpos:
return newpos
else:
# cannot move cross at all, try again
return self.check_move(candidate,cross)
# write configuration to file
def write(self,file):
ff=open(file,"w")
for cross in self.crosses:
ff.write("{:05.2f},{:05.2f};{:3.1f},{:3.1f},{:3.1f}\n".format(cross.pos.x,cross.pos.y,cross.color[0],cross.color[1],cross.color[2]))
ff.close()
# read configuration from file
def read(self,file):
self.reset()
try:
ff=open(file,"r")
except:
self.app.command_prompt.message="error: could not read file '"+file+"' (this should not happen and is probably a bug)"
return
# counter
i=0
try:
lines=ff.readlines()
except:
self.app.command_prompt.message="error: could not read the contents of file '"+file+"'"
return
for line in lines:
i+=1
# remove newline
line=line[:len(line)-1]
# ignore comments
if '#' in line:
line=line[:line.find('#')]
# ignore empty lines
if len(line)==0:
continue
entries=line.split(";")
# skip line if improperly formatted
if len(entries)>2:
print("warning: ignoring line "+str(i)+" in file '"+file+"': more than two ';' spearated entries in '"+line+"'",file=sys.stderr)
continue
# position
pos_str=entries[0].split(",")
# skip line if improperly formatted
if len(pos_str)!=2:
print("warning: ignoring line "+str(i)+" in file '"+file+"': position '"+entries[0]+"' does not have two components",file=sys.stderr)
continue
try:
pos=Point(float(pos_str[0]),float(pos_str[1]))
except:
print("warning: ignoring line "+str(i)+" in file '"+file+"': position '"+entries[0]+"' cannot be read",file=sys.stderr)
continue
# color
color=(0,0,1)
if len(entries)==2:
color_str=entries[1].split(",")
# skip line if improperly formatted
if len(color_str)!=3:
print("warning: ignoring line "+str(i)+" in file '"+file+"': color '"+entries[1]+"' does not have three components",file=sys.stderr)
continue
try:
color=(float(color_str[0]),float(color_str[1]),float(color_str[2]))
except:
print("warning: ignoring line "+str(i)+" in file '"+file+"': color '"+entries[1]+"' cannot be read",file=sys.stderr)
continue
if self.check_interaction_any(pos,None):
# add to list
self.crosses.append(Cross(pos.x,pos.y,color=color))
else:
print("warning: ignoring line "+str(i)+" in file '"+file+"': particle overlaps with existing particles",file=sys.stderr)
ff.close()
self.draw()

3
jam
View File

@ -7,7 +7,8 @@ from kivy.config import Config
import sys import sys
import os.path import os.path
from cross import Cross,Cross_painter from cross import Cross
from painter import Cross_painter
from status_bar import Status_bar from status_bar import Status_bar
from command_prompt import Command_prompt from command_prompt import Command_prompt
import filecheck import filecheck

277
painter.py Normal file
View File

@ -0,0 +1,277 @@
import sys
from kivy.uix.widget import Widget
from kivy.core.window import Window
from point import Point
# cross painter
class Cross_painter(Widget):
def __init__(self,app,**kwargs):
# list of crosses
self.crosses=[]
# cross under mouse
self.undermouse=None
# list of selected crosses
self.selected=[]
# app is used to share information between widgets
self.app=app
# modifiers
self.modifiers=[]
# init Widget
super(Cross_painter,self).__init__(**kwargs)
# init keyboard
self.keyboard = Window.request_keyboard(None,self,"text")
self.keyboard.bind(on_key_down=self.on_key_down,on_key_up=self.on_key_up)
def reset(self):
self.crosses=[]
self.undermouse=None
self.draw()
# draw all crosses
def draw(self):
self.canvas.clear()
with self.canvas:
for cross in self.crosses:
cross.draw()
# respond to keyboard
def on_key_down(self, keyboard, keycode, text, modifiers):
# check that command_prompt is not focused
if not self.app.command_prompt.insert:
if keycode[1]=="shift":
if not 's' in self.modifiers:
self.modifiers.append('s')
self.modifiers.sort()
def on_key_up(self, keyboard, keycode):
if keycode[1]=="shift":
if 's' in self.modifiers:
# remove
self.modifiers[self.modifiers.index('s')]=self.modifiers[len(self.modifiers)-1]
self.modifiers=self.modifiers[:len(self.modifiers)-1]
self.modifiers.sort()
# respond to mouse down
def on_touch_down(self,touch):
# only respond to touch in area
if self.collide_point(*touch.pos):
# create new cross
if touch.button=="right":
if self.check_interaction_any(Point(touch.x/Cross.size,touch.y/Cross.size),None):
new=Cross(touch.x/Cross.size,touch.y/Cross.size)
# add to list
self.crosses.append(new)
# unselect all crosses
for sel in self.selected:
sel.selected=False
self.selected=[]
self.draw()
# select cross
if touch.button=="left":
# find cross under touch
self.undermouse=self.find_cross(Point(touch.x/Cross.size,touch.y/Cross.size))
# no modifiers
if self.modifiers==[]:
if self.undermouse==None or not self.undermouse in self.selected:
# unselect all crosses
for sel in self.selected:
sel.selected=False
self.selected=[]
# shift-click
elif self.modifiers==['s']:
if self.undermouse!=None:
if self.undermouse not in self.selected:
self.selected.append(self.undermouse)
self.undermouse.selected=True
else:
# remove
self.selected[self.selected.index(self.undermouse)]=self.selected[len(self.selected)-1]
self.selected=self.selected[:len(self.selected)-1]
self.undermouse.selected=False
self.draw()
# respond to drag
def on_touch_move(self,touch):
if self.collide_point(*touch.pos):
# only move on left click
if touch.button=="left" and self.modifiers==[] and self.undermouse!=None:
# single cross
if not self.undermouse in self.selected:
self.undermouse.pos=self.check_move(Point(touch.x/Cross.size,touch.y/Cross.size),self.undermouse)
# multiple crosses
else:
self.group_move(touch)
# redraw
self.draw()
# move all selected crosses
def group_move(self,touch):
# save position of undermouse (to use it after it has been reset)
relative_position=self.undermouse.pos
# determine order in which to move
# direction of motion
direction=Point(touch.x/Cross.size,touch.y/Cross.size)-self.undermouse.pos
# sort according to scalar product with direction
self.selected.sort(key=(lambda cross: direction.dot(cross.pos-self.undermouse.pos)),reverse=True)
# move
for cross in self.selected:
cross.pos=self.check_move(Point(touch.x/Cross.size-relative_position.x+cross.pos.x,touch.y/Cross.size-relative_position.y+cross.pos.y),cross)
## new position of undermouse
#self.undermouse.pos=self.check_move(Point(touch.x/Cross.size,touch.y/Cross.size),self.undermouse)
## move other crosses by the same amount as undermouse
#for cross in self.selected:
# if cross!=self.undermouse:
# cross.pos=self.check_move(Point(self.undermouse.pos.x-relative_position.x+cross.pos.x,self.undermouse.pos.y-relative_position.y+cross.pos.y),cross)
#
#for cross in self.selected:
# cross.pos=self.check_move(Point(touch.x/Cross.size-relative_position.x+cross.pos.x,touch.y/Cross.size-relative_position.y+cross.pos.y),cross)
# find the cross at position pos
def find_cross(self,pos):
for cross in self.crosses:
if cross_distx(pos,cross.pos)<=0.5 or cross_disty(pos,cross.pos)<=0.5:
return cross
# none found
return None
# check whether a position intersects with any of the crosses
def check_interaction_any(self,pos,exception):
for other in self.crosses:
if other!=exception:
if other.check_interaction(pos)==False:
return False
return True
# check that a cross can move to new position
def check_move(self,newpos,cross):
# whether newpos is acceptable
accept_newpos=True
for other in self.crosses:
# do not compare a cross to itself
if other!=cross:
# move would make cross overlap with other
if other.check_interaction(newpos)==False:
accept_newpos=False
# check if cross touches other
if other.check_touch(cross.pos):
# move along other while remaining stuck
candidate=other.move_along(newpos,cross.pos)
else:
candidate=other.move_on_line_to_stick(cross.pos,newpos-cross.pos)
if self.check_interaction_any(candidate,cross):
return candidate
if accept_newpos:
return newpos
else:
# cannot move cross at all, try again
return self.check_move(candidate,cross)
# write configuration to file
def write(self,file):
ff=open(file,"w")
for cross in self.crosses:
ff.write("{:05.2f},{:05.2f};{:3.1f},{:3.1f},{:3.1f}\n".format(cross.pos.x,cross.pos.y,cross.color[0],cross.color[1],cross.color[2]))
ff.close()
# read configuration from file
def read(self,file):
self.reset()
try:
ff=open(file,"r")
except:
self.app.command_prompt.message="error: could not read file '"+file+"' (this should not happen and is probably a bug)"
return
# counter
i=0
try:
lines=ff.readlines()
except:
self.app.command_prompt.message="error: could not read the contents of file '"+file+"'"
return
for line in lines:
i+=1
# remove newline
line=line[:len(line)-1]
# ignore comments
if '#' in line:
line=line[:line.find('#')]
# ignore empty lines
if len(line)==0:
continue
entries=line.split(";")
# skip line if improperly formatted
if len(entries)>2:
print("warning: ignoring line "+str(i)+" in file '"+file+"': more than two ';' spearated entries in '"+line+"'",file=sys.stderr)
continue
# position
pos_str=entries[0].split(",")
# skip line if improperly formatted
if len(pos_str)!=2:
print("warning: ignoring line "+str(i)+" in file '"+file+"': position '"+entries[0]+"' does not have two components",file=sys.stderr)
continue
try:
pos=Point(float(pos_str[0]),float(pos_str[1]))
except:
print("warning: ignoring line "+str(i)+" in file '"+file+"': position '"+entries[0]+"' cannot be read",file=sys.stderr)
continue
# color
color=(0,0,1)
if len(entries)==2:
color_str=entries[1].split(",")
# skip line if improperly formatted
if len(color_str)!=3:
print("warning: ignoring line "+str(i)+" in file '"+file+"': color '"+entries[1]+"' does not have three components",file=sys.stderr)
continue
try:
color=(float(color_str[0]),float(color_str[1]),float(color_str[2]))
except:
print("warning: ignoring line "+str(i)+" in file '"+file+"': color '"+entries[1]+"' cannot be read",file=sys.stderr)
continue
if self.check_interaction_any(pos,None):
# add to list
self.crosses.append(Cross(pos.x,pos.y,color=color))
else:
print("warning: ignoring line "+str(i)+" in file '"+file+"': particle overlaps with existing particles",file=sys.stderr)
ff.close()
self.draw()