mirror of https://github.com/kivy/kivy.git
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:
parent
70d722ed5a
commit
66b78ec63f
|
@ -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()
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue