Compare commits

...

5 Commits

Author SHA1 Message Date
36f5226107 optional arguments in lattice spec 2022-09-23 14:45:20 -04:00
5d40050580 Set lattice on open 2022-09-23 14:22:44 -04:00
267c5e5c5c cli passing 2022-09-23 13:41:26 -04:00
42e9f60c4e Fix grid drawing 2022-09-23 13:17:35 -04:00
9cb25730eb lattice 2022-09-23 12:58:48 -04:00
4 changed files with 195 additions and 29 deletions

80
src/jam
View File

@@ -1,16 +1,69 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys
import os.path,os
import filecheck
from lattice import Lattice
## read cli before loading kivy, in case there are errors
# read cli
openfile=""
lattice=""
def read_cli():
global openfile
global lattice
# init flag
flag=""
# loop over arguments
for arg in sys.argv[1:]:
# option flag
if arg[0]=='-':
# loop over options
for c in arg[1:]:
# lattice
if c=='L':
flag="lattice"
else:
# read lattice argument
if flag=="lattice":
# test the specification
(obj,message)=Lattice.new(arg)
if obj==None:
print(message,file=sys.stderr)
exit(-1)
lattice=arg
# reset flag
flag=""
# no flags
else:
openfile=arg
(ret,message)=filecheck.check_edit(openfile)
if ret<0:
print(message,file=sys.stderr)
exit(-1)
# read cli
read_cli()
## import kivy
# disable kivy argument parser
os.environ["KIVY_NO_ARGS"] = "1"
from kivy.app import App from kivy.app import App
from kivy.uix.widget import Widget from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout from kivy.uix.boxlayout import BoxLayout
from kivy.config import Config from kivy.config import Config
import sys
import os.path
from painter import Painter from painter import 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
# App class # App class
class Jam_app(App): class Jam_app(App):
@@ -21,6 +74,9 @@ class Jam_app(App):
# the file open for editing # the file open for editing
self.openfile=kwargs.get("openfile","") self.openfile=kwargs.get("openfile","")
# the lattice open for editing
self.lattice=kwargs.get("lattice","")
# readonly mode # readonly mode
self.readonly=False self.readonly=False
@@ -48,24 +104,20 @@ class Jam_app(App):
# set readonly mode # set readonly mode
self.readonly=not os.access(self.openfile,os.W_OK) self.readonly=not os.access(self.openfile,os.W_OK)
# load lattice
if self.lattice!="":
(obj,message)=Lattice.new(self.lattice)
self.painter.set_lattice(obj)
return layout return layout
# disable red circles on right click # disable red circles on right click
Config.set('input', 'mouse', 'mouse,disable_multitouch') Config.set('input', 'mouse', 'mouse,disable_multitouch')
# do not exit on escape # do not exit on escape
Config.set('kivy', 'exit_on_escape', 0) Config.set('kivy', 'exit_on_escape', 0)
# read cli
openfile=""
if len(sys.argv)==2:
openfile=sys.argv[1]
# check file
(ret,message)=filecheck.check_edit(openfile)
if ret<0:
print(message,file=sys.stderr)
exit(-1)
# run # run
if __name__ == '__main__': if __name__ == '__main__':
Jam_app(openfile=openfile).run() Jam_app(openfile=openfile,lattice=lattice).run()

60
src/lattice.py Normal file
View File

@@ -0,0 +1,60 @@
from point import Point
# parent class of all lattices
class Lattice():
def __init__(self,**kwargs):
self.type=kwargs.get("type","")
# lattice point nearest to point
# overwrite in subclasses
def nearest(self,point):
return point
# delta to nearest point
def nearest_delta(self,point):
return self.nearest(point)-point
# draw lattice
# overwrite in subclasses
def draw(self,painter):
return
# return the lattice according to a specification
def new(spec):
specs=spec.split(":")
# check type of lattice
if specs[0]=="square":
return Square_lattice.new_square(specs[1:],spec)
else:
return(None,"error: unrecognized lattice type: '"+specs[0]+"'")
# square lattice
class Square_lattice(Lattice):
def __init__(self,**kwargs):
self.spacing=kwargs.get("spacing",1.)
super(Square_lattice,self).__init__(**kwargs,type="square")
# lattice point nearest to point
def nearest(self,point):
return Point(round(point.x/self.spacing)*self.spacing,round(point.y/self.spacing)*self.spacing)
# draw
def draw(self,painter):
painter.draw_grid(Point(self.spacing/2,self.spacing/2),self.spacing)
# return the lattice according to a specification
def new_square(specs,spec):
# no optional args
if len(specs)==0:
return (Square_lattice(),"")
if len(specs)>1:
return (None,"error: '"+spec+"' is not a valid specification for the square lattice: should be 'square[:spacing]'")
try:
spacing=float(specs[0])
return (Square_lattice(spacing=spacing),"")
except:
return (None,"error: '"+spec+"' is not a valid specification for the square lattice: should be 'square[:spacing]'")

View File

@@ -7,6 +7,7 @@ from kivy.graphics import Color,Line
from point import Point from point import Point
from polyomino import Cross from polyomino import Cross
from polyomino import Square_element from polyomino import Square_element
from lattice import Square_lattice
from tools import remove_fromlist from tools import remove_fromlist
@@ -20,6 +21,9 @@ class Painter(Widget):
# list of particles # list of particles
self.particles=[] self.particles=[]
# underlying lattice
self.lattice=None
# particle under mouse # particle under mouse
self.undermouse=None self.undermouse=None
@@ -47,12 +51,29 @@ class Painter(Widget):
self.keyboard = Window.request_keyboard(None,self,"text") 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,on_textinput=self.on_textinput) self.keyboard.bind(on_key_down=self.on_key_down,on_key_up=self.on_key_up,on_textinput=self.on_textinput)
# redraw on resize
self.bind(size=lambda obj,value: self.draw())
def reset(self): def reset(self):
self.particles=[] self.particles=[]
self.undermouse=None self.undermouse=None
self.draw() self.draw()
# set lattice
def set_lattice(self,lattice):
self.lattice=lattice
# draw
self.draw()
# snap all existing particles to grid
for particle in self.particles:
delta=self.lattice.nearest_delta(particle.squares[0].pos)
if not self.check_interaction_any(particle,delta):
particle.move(delta)
# convert logical coordinates (normalized and centered) to the ones that are plotted # convert logical coordinates (normalized and centered) to the ones that are plotted
def pos_tocoord_x(self,x): def pos_tocoord_x(self,x):
return self.width/2+x*Square_element.size return self.width/2+x*Square_element.size
@@ -74,6 +95,10 @@ class Painter(Widget):
for particle in self.particles: for particle in self.particles:
particle.draw(self) particle.draw(self)
# draw lattice
if self.lattice!=None:
self.lattice.draw(self)
# draw grids # draw grids
for particle in self.particles: for particle in self.particles:
if particle.grid>0: if particle.grid>0:
@@ -87,17 +112,30 @@ class Painter(Widget):
# height offset due to status bar and command prompt # height offset due to status bar and command prompt
height_offset=self.app.status_bar.height+self.app.command_prompt.height height_offset=self.app.status_bar.height+self.app.command_prompt.height
# vertical lines # vertical lines
# offest wrt 0 # lines right of pos
offset=(pos.x-0.5)%mesh xx=pos.x+mesh/2
for i in range(math.floor((self.width/Square_element.size-offset)/mesh)+1): while self.pos_tocoord_x(xx)<self.width:
Color(1,1,1) Color(1,1,1)
Line(points=((i*mesh+offset)*Square_element.size,height_offset,(i*mesh+offset)*Square_element.size,self.height+height_offset)) Line(points=(self.pos_tocoord_x(xx),height_offset,self.pos_tocoord_x(xx),self.height+height_offset))
# horizontal lines xx+=mesh
# offset wrt 0 # lines left of pos
offset=(pos.y-0.5)%1-height_offset/Square_element.size xx=pos.x-mesh/2
for i in range(math.floor((self.height/Square_element.size-offset)/mesh)+1): while self.pos_tocoord_x(xx)>0:
Color(1,1,1) Color(1,1,1)
Line(points=(0,(i*mesh+offset)*Square_element.size+height_offset,self.width,(i*mesh+offset)*Square_element.size+height_offset)) Line(points=(self.pos_tocoord_x(xx),height_offset,self.pos_tocoord_x(xx),self.height+height_offset))
xx-=mesh
# lines above pos
yy=pos.y+mesh/2
while self.pos_tocoord_y(yy)<self.height:
Color(1,1,1)
Line(points=(0,self.pos_tocoord_y(yy),self.width,self.pos_tocoord_y(yy)))
yy+=mesh
# lines below pos
yy=pos.y-mesh/2
while self.pos_tocoord_y(yy)>0:
Color(1,1,1)
Line(points=(0,self.pos_tocoord_y(yy),self.width,self.pos_tocoord_y(yy)))
yy-=mesh
# respond to keyboard # respond to keyboard
@@ -175,6 +213,10 @@ class Painter(Widget):
# create new cross # create new cross
if touch.button=="right": if touch.button=="right":
new=Cross(touchx,touchy) new=Cross(touchx,touchy)
# snap to lattice
if self.lattice!=None:
new.move(self.lattice.nearest_delta(new.squares[0].pos))
if not self.check_interaction_any(new,Point(0,0)): if not self.check_interaction_any(new,Point(0,0)):
# add to list # add to list
self.particles.append(new) self.particles.append(new)
@@ -237,16 +279,28 @@ class Painter(Widget):
# respond to drag # respond to drag
def on_touch_move(self,touch): def on_touch_move(self,touch):
# only respond to touch in drawing area
if self.collide_point(*touch.pos):
# convert to logical # convert to logical
touchx=self.coord_topos_x(touch.x) touchx=self.coord_topos_x(touch.x)
touchy=self.coord_topos_y(touch.y) touchy=self.coord_topos_y(touch.y)
# only respond to touch in drawing area
if self.collide_point(*touch.pos):
# only move on left click # only move on left click
if touch.button=="left" and self.modifiers==[] and self.undermouse!=None: if touch.button=="left" and self.modifiers==[] and self.undermouse!=None:
# attempted move determined by the relative position to the relative position of click within self.undermouse # attempted move determined by the relative position to the relative position of click within self.undermouse
delta=self.adjust_move(Point(touchx,touchy)-(self.offset+self.undermouse.squares[0].pos),0) delta=self.adjust_move(Point(touchx,touchy)-(self.offset+self.undermouse.squares[0].pos),0)
# snap to lattice
if self.lattice!=None:
delta=self.lattice.nearest(delta)
# check that the move is possible (which is not guaranteed after snapping to lattice)
if not self.check_interaction_unselected_list(self.selected,delta):
for particle in self.selected:
particle.move(delta)
# no lattice, move is guaranteed to be acceptable
else:
for particle in self.selected: for particle in self.selected:
particle.move(delta) particle.move(delta)

View File

@@ -8,7 +8,7 @@ from tools import isint_nonzero,sgn,in_interval
# parent class of all polyominos # parent class of all polyominos
class Polyomino(): class Polyomino():
def __init__(self,**kwargs): def __init__(self,**kwargs):
# square elements that maje up the polyomino # square elements that make up the polyomino
self.squares=kwargs.get("squares",[]) self.squares=kwargs.get("squares",[])
self.color=kwargs.get("color",(0,0,1)) self.color=kwargs.get("color",(0,0,1))