line: extend Line() to add new setter: ellipse, circle and rectangle. With this, we can build automatically the points.

Check the examples/canvas/lines_extended.py for a demo.
This commit is contained in:
Mathieu Virbel 2012-09-26 18:43:03 +02:00
parent 70d722ed5a
commit 66b78ec63f
2 changed files with 351 additions and 4 deletions

View File

@ -0,0 +1,123 @@
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.widget import Widget
from kivy.lang import Builder
Builder.load_string('''
<LineEllipse1>:
canvas:
Color:
rgba: 1, .1, .1, .9
Line:
width: 2.
ellipse: (self.x, self.y, self.width, self.height)
Label:
center: root.center
text: 'Ellipse'
<LineEllipse2>:
canvas:
Color:
rgba: 1, .1, .1, .9
Line:
width: 2.
ellipse: (self.x, self.y, self.width, self.height, 90, 180)
Label:
center: root.center
text: 'Ellipse from 90 to 180'
# fun result with low segments!
<LineEllipse3>:
canvas:
Color:
rgba: 1, .1, .1, .9
Line:
width: 2.
ellipse: (self.x, self.y, self.width, self.height, 90, 720, 10)
Label:
center: root.center
text: 'Ellipse from 90 to 720\\n10 segments'
halign: 'center'
<LineCircle1>:
canvas:
Color:
rgba: .1, 1, .1, .9
Line:
width: 2.
circle: (self.center_x, self.center_y, min(self.width, self.height) / 2)
Label:
center: root.center
text: 'Circle'
<LineCircle2>:
canvas:
Color:
rgba: .1, 1, .1, .9
Line:
width: 2.
circle: (self.center_x, self.center_y, min(self.width, self.height) / 2, 90, 180)
Label:
center: root.center
text: 'Circle from 90 to 180'
<LineCircle3>:
canvas:
Color:
rgba: .1, 1, .1, .9
Line:
width: 2.
circle: (self.center_x, self.center_y, min(self.width, self.height) / 2, 90, 180, 10)
Label:
center: root.center
text: 'Circle from 90 to 180\\n10 segments'
halign: 'center'
<LineRectangle>:
canvas:
Color:
rgba: .1, .1, 1, .9
Line:
width: 2.
rectangle: (self.x, self.y, self.width, self.height)
Label:
center: root.center
text: 'Rectangle'
''')
class LineEllipse1(Widget):
pass
class LineEllipse2(Widget):
pass
class LineEllipse3(Widget):
pass
class LineCircle1(Widget):
pass
class LineCircle2(Widget):
pass
class LineCircle3(Widget):
pass
class LineRectangle(Widget):
pass
class LineExtendedApp(App):
def build(self):
root = GridLayout(cols=2, padding=50, spacing=50)
root.add_widget(LineEllipse1())
root.add_widget(LineEllipse2())
root.add_widget(LineEllipse3())
root.add_widget(LineCircle1())
root.add_widget(LineCircle2())
root.add_widget(LineCircle3())
root.add_widget(LineRectangle())
return root
if __name__ == '__main__':
LineExtendedApp().run()

View File

@ -7,6 +7,11 @@ DEF LINE_JOINT_MITER = 1
DEF LINE_JOINT_BEVEL = 2
DEF LINE_JOINT_ROUND = 3
DEF LINE_MODE_POINTS = 0
DEF LINE_MODE_ELLIPSE = 1
DEF LINE_MODE_CIRCLE = 2
DEF LINE_MODE_RECTANGLE = 3
from kivy.graphics.stencil_instructions cimport StencilUse, StencilUnUse, StencilPush, StencilPop
cdef inline int line_intersection(double x1, double y1, double x2, double y2,
@ -63,13 +68,22 @@ cdef class Line(VertexInstruction):
See :data:`joint_precision` for more information
`close`: bool, default to False
If True, the line will be closed.
`circle`: list
If set, the :data:`points` will be set to build a circle. Check
:data:`circle` for more information.
`ellipse`: list
If set, the :data:`points` will be set to build an ellipse. Check
:data:`ellipse` for more information.
`rectangle`: list
If set, the :data:`points` will be set to build a rectangle. Check
:data:`rectangle` for more information.
.. versionadded:: 1.0.8
`dash_offset` and `dash_length` have been added
.. versionadded:: 1.4.1
`width`, `cap`, `joint`, `cap_precision`, `joint_precision`, `close`
have been added.
`width`, `cap`, `joint`, `cap_precision`, `joint_precision`, `close`,
`ellipse`, `rectangle` have been added.
'''
cdef int _cap
cdef int _cap_precision
@ -80,12 +94,14 @@ cdef class Line(VertexInstruction):
cdef int _dash_offset, _dash_length
cdef int _use_stencil
cdef int _close
cdef int _mode
cdef Instruction _stencil_rect
cdef Instruction _stencil_push
cdef Instruction _stencil_use
cdef Instruction _stencil_unuse
cdef Instruction _stencil_pop
cdef double _bxmin, _bxmax, _bymin, _bymax
cdef tuple _mode_args
def __init__(self, **kwargs):
VertexInstruction.__init__(self, **kwargs)
@ -108,6 +124,12 @@ cdef class Line(VertexInstruction):
self._use_stencil = 0
cdef void build(self):
if self._mode == LINE_MODE_ELLIPSE:
self.prebuild_ellipse()
elif self._mode == LINE_MODE_CIRCLE:
self.prebuild_circle()
elif self._mode == LINE_MODE_RECTANGLE:
self.prebuild_rectangle()
if self._width == 1.0:
self.build_legacy()
else:
@ -143,8 +165,6 @@ cdef class Line(VertexInstruction):
else:
VertexInstruction.apply(self)
cdef void build_legacy(self):
cdef int i, count = len(self.points) / 2
cdef list p = self.points
@ -763,3 +783,207 @@ cdef class Line(VertexInstruction):
def __set__(self, value):
self._close = int(bool(value))
self.flag_update()
property ellipse:
'''Use this property to build an ellipse, without calculate the
:data:`points`. You can only set this property, not get it.
The argument must be a tuple of (x, y, width, height, angle_start,
angle_end, segments):
* x and y represent the bottom left of the ellipse
* width and height represent the size of the ellipse
* (optional) angle_start and angle_end are in degree. The default
value is 0 and 360.
* (optional) segments is the precision of the ellipse. The default
value is calculated from the range between angle.
Note that it's up to you to :data:`close` the ellipse or not.
For example, for building a simple ellipse, in python:
# simple ellipse
Line(ellipse=(0, 0, 150, 150))
# only from 90 to 180 degrees
Line(ellipse=(0, 0, 150, 150, 90, 180))
# only from 90 to 180 degrees, with few segments
Line(ellipse=(0, 0, 150, 150, 90, 180, 20))
.. versionadded:: 1.4.1
'''
def __set__(self, args):
if args == None:
raise GraphicException(
'Invalid ellipse value: {0!r}'.format(args))
if len(args) not in (4, 6, 7):
raise GraphicException('Invalid number of arguments: '
'{0} instead of 4, 6 or 7.'.format(len(args)))
self._mode_args = tuple(args)
self._mode = LINE_MODE_ELLIPSE
self.flag_update()
cdef void prebuild_ellipse(self):
cdef double x, y, w, h, angle_start = 0, angle_end = 360
cdef int angle_dir, segments = 0
cdef double angle_range
cdef tuple args = self._mode_args
if len(args) == 4:
x, y, w, h = args
elif len(args) == 6:
x, y, w, h, angle_start, angle_end = args
elif len(args) == 7:
x, y, w, h, angle_start, angle_end, segments = args
segments += 2
else:
assert(0)
if angle_end > angle_start:
angle_dir = 1
else:
angle_dir = -1
if segments == 0:
segments = int(abs(angle_end - angle_start) / 2) + 3
# rad = deg * (pi / 180), where pi/180 = 0.0174...
angle_start = angle_start * 0.017453292519943295
angle_end = angle_end * 0.017453292519943295
angle_range = abs(angle_end - angle_start) / (segments - 2)
cdef list points = [0, 0] * segments
cdef double angle
cdef double rx = w * 0.5
cdef double ry = h * 0.5
for i in xrange(0, segments * 2, 2):
angle = angle_start + (angle_dir * (i - 1) * angle_range)
points[i] = (x + rx) + (rx * sin(angle))
points[i + 1] = (y + ry) + (ry * cos(angle))
self._points = points
property circle:
'''Use this property to build a circle, without calculate the
:data:`points`. You can only set this property, not get it.
The argument must be a tuple of (center_x, center_y, radius, angle_start,
angle_end, segments):
* center_x and center_y represent the center of the circle
* radius represent the radius of the circle
* (optional) angle_start and angle_end are in degree. The default
value is 0 and 360.
* (optional) segments is the precision of the ellipse. The default
value is calculated from the range between angle.
Note that it's up to you to :data:`close` the circle or not.
For example, for building a simple ellipse, in python:
# simple circle
Line(circle=(150, 150, 50))
# only from 90 to 180 degrees
Line(circle=(150, 150, 50, 90, 180))
# only from 90 to 180 degrees, with few segments
Line(circle=(150, 150, 50, 90, 180, 20))
.. versionadded:: 1.4.1
'''
def __set__(self, args):
if args == None:
raise GraphicException(
'Invalid circle value: {0!r}'.format(args))
if len(args) not in (3, 5, 6):
raise GraphicException('Invalid number of arguments: '
'{0} instead of 3, 5 or 6.'.format(len(args)))
self._mode_args = tuple(args)
self._mode = LINE_MODE_CIRCLE
self.flag_update()
cdef void prebuild_circle(self):
cdef double x, y, r, angle_start = 0, angle_end = 360
cdef int angle_dir, segments = 0
cdef double angle_range
cdef tuple args = self._mode_args
if len(args) == 3:
x, y, r = args
elif len(args) == 5:
x, y, r, angle_start, angle_end = args
elif len(args) == 6:
x, y, r, angle_start, angle_end, segments = args
segments += 2
else:
assert(0)
if angle_end > angle_start:
angle_dir = 1
else:
angle_dir = -1
if segments == 0:
segments = int(abs(angle_end - angle_start) / 2) + 3
# rad = deg * (pi / 180), where pi/180 = 0.0174...
angle_start = angle_start * 0.017453292519943295
angle_end = angle_end * 0.017453292519943295
angle_range = abs(angle_end - angle_start) / (segments - 2)
cdef list points = [0, 0] * segments
cdef double angle
for i in xrange(0, segments * 2, 2):
angle = angle_start + (angle_dir * (i - 1) * angle_range)
points[i] = x + (r * sin(angle))
points[i + 1] = y + (r * cos(angle))
self._points = points
property rectangle:
'''Use this property to build a rectangle, without calculate the
:data:`points`. You can only set this property, not get it.
The argument must be a tuple of (x, y, width, height)
angle_end, segments):
* x and y represent the bottom-left position of the rectangle
* width and height represent the size
The line is automatically closed.
Usage::
Line(rectangle=(0, 0, 200, 200))
.. versionadded:: 1.4.1
'''
def __set__(self, args):
if args == None:
raise GraphicException(
'Invalid rectangle value: {0!r}'.format(args))
if len(args) != 4:
raise GraphicException('Invalid number of arguments: '
'{0} instead of 4.'.format(len(args)))
self._mode_args = tuple(args)
self._mode = LINE_MODE_RECTANGLE
self.flag_update()
cdef void prebuild_rectangle(self):
cdef double x, y, width, height
cdef int angle_dir, segments = 0
cdef double angle_range
cdef tuple args = self._mode_args
if args == None:
raise GraphicException(
'Invalid ellipse value: {0!r}'.format(args))
if len(args) == 4:
x, y, width, height = args
else:
assert(0)
self._points = [x, y, x + width, y, x + width, y + height, x, y + height]
self._close = 1