diff --git a/.github/workflows/pypen_ci.yml b/.github/workflows/pypen_ci.yml index 8b81005..84e03de 100644 --- a/.github/workflows/pypen_ci.yml +++ b/.github/workflows/pypen_ci.yml @@ -2,11 +2,7 @@ name: PyPen CI on: push: - branches: - - master pull_request: - branches: - - master jobs: test: diff --git a/README.md b/README.md index f9a4e41..613341b 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,20 @@ TL;DR: 3. type ```pypen example``` and a window should popup with something looking like this:



4. edit the example.py file by checking out PyPen's documentation and [examples](./examples/) +## Inspiration from our Examples: + +| Example 002 | Example 003 | +|:-----------:|:-----------:| +|![Example 002 Gif](./examples/gifs/002.gif)|![Example 003 Gif](./examples/gifs/003.gif)| + +| Example 006 | Example 007 | +|:-----------:|:-----------:| +|![Example 006 Gif](./examples/gifs/006.gif)|![Example 007 Gif](./examples/gifs/007.gif)| + +| Example 010 | Example 011 | +|:-----------:|:-----------:| +|![Example 010 Gif](./examples/gifs/010.gif)|![Example 011 Gif](./examples/gifs/011.gif)| + ## _(For Maintainers)_ How does PyPen work? We have an entire page dedicated to explaining the inner workings of PyPen. If you are a developer interested in helping PyPen's development, you should check it out: [How does PyPen _Really_ Work?](https://pypen.readthedocs.io/en/latest/behind_the_scenes.html#behind-the-scenes) diff --git a/examples/example_007.py b/examples/example_007.py index 95a3d3e..640668d 100644 --- a/examples/example_007.py +++ b/examples/example_007.py @@ -13,6 +13,7 @@ def __init__(self, x, y): self.secondary_color = "#565656" def update(self): + reset_style() save() translate(self.x, self.y) rotate(TIME) @@ -40,7 +41,7 @@ def rectangle(self): def circle(self): circle(0, 0, self.width/2, self.color) if self.variation > 0.6: - arc(0, 0, self.width/2, 0, PI, self.secondary_color) + arc(0, 0, self.width/2, 0, PI, None, self.secondary_color, 2) def start(): diff --git a/examples/example_008.py b/examples/example_008.py index d8260ed..ce1e7a6 100644 --- a/examples/example_008.py +++ b/examples/example_008.py @@ -9,19 +9,19 @@ def update(): fill_screen("orange") save() - translate(60,60) + translate(60, 60) rotate(T*2.5) ellipse(0, 0, 50, 30, "red", "blue", 3) restore() save() - translate(200,200) + translate(200, 200) rotate(T*1.5+0.5) ellipse(40*sin(T), 0, 30, 50, stroke_width=10) restore() - + save() - translate(400,300) + translate(400, 300) rotate(T) ellipse(0, 100*cos(T*2), 30, 100, "green") - restore() \ No newline at end of file + restore() diff --git a/examples/example_009.py b/examples/example_009.py new file mode 100644 index 0000000..667c06f --- /dev/null +++ b/examples/example_009.py @@ -0,0 +1,42 @@ +from pypen import * + + +def start(): + settings.fps = 60 + + +def update(): + fill_screen("#343434") + + begin_shape() + vertex(40, 40) + vertex(40, 140) + vertex(FRAME, 140) + end_shape("red") + + begin_shape() + vertex(40+FRAME, 240+FRAME) + vertex(40, 340) + vertex(FRAME, 340) + vertex(340, 280) + + end_shape("yellow", "blue", 5) + + reset_style() + + angle = 0 + points = 10 + sin(TIME*2)*5 + radius = 100 + + save() + translate(WIDTH/2, HEIGHT/2) + + begin_shape() + + while angle <= TAU: + angle += TAU/points + vertex(sin(angle)*radius, cos(angle)*radius) + + end_shape("orange") + restore() + reset_style() diff --git a/examples/example_010.py b/examples/example_010.py new file mode 100644 index 0000000..efd6d50 --- /dev/null +++ b/examples/example_010.py @@ -0,0 +1,19 @@ +from pypen import * + + +def start(): + settings.fps = 60 + +def update(): + save() + translate(WIDTH/2, HEIGHT/2) + + x1 = 200 * cos(TIME - 10 * sin(TIME)) * cos(TIME) + y1 = 200 * sin(TIME - FRAME/10) * sin(TIME) + + x2 = 200 * cos(TIME + FRAME ** 0.5) * cos(TIME-0.5) + y2 = 200 * sin(TIME + FRAME/10 - 10*cos(TIME/10)) * sin(TIME - 0.5) + + line(x1, y1, x2, y2, (150, 30, 230, 10), 0.2) + + restore() diff --git a/examples/example_011.py b/examples/example_011.py new file mode 100644 index 0000000..3ca8b8a --- /dev/null +++ b/examples/example_011.py @@ -0,0 +1,22 @@ +from pypen import * + + +def pseudo_random(seed: float): + return abs(sin(seed % 478562)*cos(seed % 3579)) + +def update(): + clear_screen() + + i = 0 + offset_y = TIME*170 + + for x, y in grid(spacing=30): + seed = i+x*y + + actual_y = (y + offset_y) % HEIGHT - 30 + percentage = (1-(actual_y/HEIGHT)**5) + color = (80, 130, 50, pseudo_random(seed)*255*percentage) + + circle(x, actual_y, pseudo_random(seed)*14*percentage, color) + + i += 1 diff --git a/examples/gifs/002.gif b/examples/gifs/002.gif new file mode 100644 index 0000000..8de0b85 Binary files /dev/null and b/examples/gifs/002.gif differ diff --git a/examples/gifs/003.gif b/examples/gifs/003.gif new file mode 100644 index 0000000..a2a686d Binary files /dev/null and b/examples/gifs/003.gif differ diff --git a/examples/gifs/006.gif b/examples/gifs/006.gif new file mode 100644 index 0000000..e5cb686 Binary files /dev/null and b/examples/gifs/006.gif differ diff --git a/examples/gifs/007.gif b/examples/gifs/007.gif new file mode 100644 index 0000000..3b938a2 Binary files /dev/null and b/examples/gifs/007.gif differ diff --git a/examples/gifs/010.gif b/examples/gifs/010.gif new file mode 100644 index 0000000..b7275cc Binary files /dev/null and b/examples/gifs/010.gif differ diff --git a/examples/gifs/011.gif b/examples/gifs/011.gif new file mode 100644 index 0000000..a98aa08 Binary files /dev/null and b/examples/gifs/011.gif differ diff --git a/pypen/drawing/__init__.py b/pypen/drawing/__init__.py index 0501720..edf3ede 100644 --- a/pypen/drawing/__init__.py +++ b/pypen/drawing/__init__.py @@ -8,4 +8,4 @@ from pypen.drawing.pypen_window import PyPenWindow # Primitives used for intellisense and documentation -from pypen.drawing.fake_primitives import * +from pypen.drawing.fake_pypen import * diff --git a/pypen/drawing/fake_primitives.py b/pypen/drawing/fake_primitives.py index 757f68e..3988413 100644 --- a/pypen/drawing/fake_primitives.py +++ b/pypen/drawing/fake_primitives.py @@ -1,4 +1,33 @@ +def rotate(angle): + """Rotates the context""" + raise NotImplementedError("rotate() is not implemented") + + +def translate(x, y): + """Translates the context by x and y""" + raise NotImplementedError("translate() is not implemented") + + +def scale(factor): + """Scales the context by the provided factor""" + raise NotImplementedError("scale() is not implemented") + + +def save(): + """Saves the current context's translation, rotation and scaling""" + raise NotImplementedError("save() is not implemented") + + +def restore(): + """Restores the context's translation, rotation and scaling to that of the latest save""" + raise NotImplementedError("restore() is not implemented") + + +def reset_style(): + """Resets PyPen's current setting surrounding style to their default_values, which includes fill_color, stroke_color, stroke_width""" + raise NotImplementedError("reset_style() is not implemented") + def clear_screen(): """Clears the screen""" @@ -20,6 +49,21 @@ def fill(): raise NotImplementedError("fill() not implemented") +def begin_shape(): + """Tells PyPen that a shape is a bout to be created""" + raise NotImplementedError("begin_shape() not implemented") + + +def vertex(x, y): + """Adds a vertex to current shape at (x, y)""" + raise NotImplementedError("vertex() not implemented") + + +def end_shape(fill_color="", stroke_color="", stroke_width=-1): + """Ends shape and styles it""" + raise NotImplementedError("end_shape() not implemented") + + def rectangle(x, y, width, height, fill_color="", stroke_color="", stroke_width=-1): """Draws a rectangle on the given coordinate with the given width, height and color""" raise NotImplementedError("rectangle() not implemented") @@ -39,33 +83,10 @@ def arc(x, y, radius, start_angle, stop_angle, fill_color="", stroke_color="", s """Draws an arc on the given coordinate with the given radius, angles and color""" raise NotImplementedError("arc() not implemented") +def triangle(x1, y1, x2, y2, x3, y3, fill_color="", stroke_color="", stroke_width=-1): + """Draws a triangle between the supplied coordinates with the given color""" + raise NotImplementedError("triangle() not implemented") -def rotate(angle): - """Rotates the context""" - raise NotImplementedError("rotate() is not implemented") - - -def translate(x, y): - """Translates the context by x and y""" - raise NotImplementedError("translate() is not implemented") - - -def scale(factor): - """Scales the context by the provided factor""" - raise NotImplementedError("scale() is not implemented") - - -def save(): - """Saves the current context's translation, rotation and scaling""" - raise NotImplementedError("save() is not implemented") - - -def restore(): - """Restores the context's translation, rotation and scaling to that of the latest save""" - raise NotImplementedError("restore() is not implemented") - - -def reset_style(): - """Resets PyPen's current setting surrounding style to their default_values, which includes fill_color, stroke_color, stroke_width""" - raise NotImplementedError("reset_style() is not implemented") - +def line(x1, y1, x2, y2, stroke_color="", stroke_width=-1): + """Draws a line between the supplied coordinates with the given stroke_color and stroke_width""" + raise NotImplementedError("triangle() not implemented") diff --git a/pypen/drawing/fake_pypen.py b/pypen/drawing/fake_pypen.py new file mode 100644 index 0000000..cdfa756 --- /dev/null +++ b/pypen/drawing/fake_pypen.py @@ -0,0 +1,126 @@ + +def rotate(angle): + """Rotates the context. + + Args: + angle (float): The amount by which the context should be rotated. + """ + raise NotImplementedError("rotate() is not implemented") + + +def translate(x, y): + """Translates the context by x and y. + + Args: + x (float): Horizontal coordinate. + y (float): Vertical coordinate. + """ + raise NotImplementedError("translate() is not implemented") + + +def scale(factor): + """Scales the context by the provided factor + + Args: + factor (float): The amount by which the current context should be scaled. + """ + raise NotImplementedError("scale() is not implemented") + + +def save(): + """Saves the current context's translation, rotation and scaling""" + raise NotImplementedError("save() is not implemented") + + +def restore(): + """Restores the context's translation, rotation and scaling to that of the latest save""" + raise NotImplementedError("restore() is not implemented") + + +def reset_style(): + """Resets PyPen's current setting surrounding style to their default_values, which includes fill_color, stroke_color, stroke_width""" + raise NotImplementedError("reset_style() is not implemented") + + +def clear_screen(): + """Clears the screen""" + raise NotImplementedError("clear_screen() not implemented") + + +def clear(): + """Clears the screen""" + raise NotImplementedError("clear() not implemented") + + +def fill_screen(fill_color): + """Fills the screen with the specified color + + Args: + fill_color (Color): The color by which to fill the screen. Defaults to the theme's default background color. + """ + raise NotImplementedError("fill_screen() not implemented") + + +def fill(): + """Fills the current path. Different from fill_screen.""" + raise NotImplementedError("fill() not implemented") + + +def rectangle(x, y, width, height, fill_color, stroke_color, stroke_width): + """Draws a rectangle on the given coordinate with the given width, height and color + + Args: + x (float): Horizontal coordinate. + y (float): Vertical coordinate. + width (float): Width of the triangle. + height (float): Height of the triangle. + fill_color (Color, optional): The color by which to fill the rectangle. + stroke_color (Color, optional): The color of the rectangle's outline + stroke_width (float, optional): The width of the outline. + """ + raise NotImplementedError("rectangle() not implemented") + + +def circle(x, y, radius, fill_color, stroke_color, stroke_width): + """Draws a circle on the given coordinate with the given radius and color + + Args: + x (float): Horizontal coordinate. + y (float): Vertical coordinate. + radius (float): Radius of the circle. + fill_color (Color, optional): The color by which to fill the circle. + stroke_color (Color, optional): The color of the circle's outline + stroke_width (float, optional): The width of the outline. + """ + raise NotImplementedError("circle() not implemented") + + +def ellipse(x, y, width, height, fill_color="", stroke_color="", stroke_width=-1): + """Draws an ellipse on the given coordinate with the given width, height and color + + Args: + x (float): Horizontal coordinate. + y (float): Vertical coordinate. + width (float): Width of the ellipse. + height (float): Height of the ellipse. + fill_color (Color, optional): The color by which to fill the ellipse. + stroke_color (Color, optional): The color of the ellipse's outline + stroke_width (float, optional): The width of the outline. + """ + raise NotImplementedError("ellipse() not implemented") + + +def arc(x, y, radius, start_angle, stop_angle, fill_color="", stroke_color="", stroke_width=-1): + """Draws an arc on the given coordinate with the given radius, angles and color + + Args: + x (float): Horizontal coordinate. + y (float): Vertical coordinate. + radius (float): Radius of the arc. + start_angle (float): The angle from which to begin drawing the arc. + stop_angle (float): The angle at which to stop drawing the arc. + fill_color (Color, optional): The color by which to fill the arc. + stroke_color (Color, optional): The color of the arc's outline + stroke_width (float, optional): The width of the outline. + """ + raise NotImplementedError("arc() not implemented") diff --git a/pypen/drawing/pypen_class.py b/pypen/drawing/pypen_class.py index 31cfbac..6c31127 100644 --- a/pypen/drawing/pypen_class.py +++ b/pypen/drawing/pypen_class.py @@ -2,7 +2,7 @@ from pypen.drawing.color import Color from pypen.utils.math import TAU -from pypen.settings import default_settings +from pypen.settings import default_settings import cairo from pyglet import gl, image @@ -22,10 +22,15 @@ def _fix_primitive_functions(self): self.user_sketch.clear_screen = self.clear_screen self.user_sketch.clear = self.clear + self.user_sketch.begin_shape = self.begin_shape + self.user_sketch.vertex = self.vertex + self.user_sketch.end_shape = self.end_shape self.user_sketch.rectangle = self.rectangle self.user_sketch.circle = self.circle self.user_sketch.ellipse = self.ellipse self.user_sketch.arc = self.arc + self.user_sketch.triangle = self.triangle + self.user_sketch.line = self.line self.user_sketch.arc = self.arc @@ -104,6 +109,25 @@ def fill_screen(self, color="default_background_color"): self.context.fill() self.context.restore() + def begin_shape(self): + self.user_sketch.settings._shape_begun = True + + def vertex(self, x, y): + if self.user_sketch.settings._shape_begun: + self.context.move_to(x, y) + self.user_sketch.settings._starting_point = (x, y) + self.user_sketch.settings._shape_begun = False + else: + self.context.line_to(x, y) + + def end_shape(self, fill_color="", stroke_color="", stroke_width=-1): + if self.user_sketch.settings._starting_point is not None: + starting_point = self.user_sketch.settings._starting_point + self.context.line_to(starting_point[0], starting_point[1]) + self.user_sketch.settings._starting_point = None + self._stroke(stroke_color, stroke_width) + self._fill(fill_color) + def rectangle(self, x, y, width, height, fill_color="", stroke_color="", stroke_width=-1): self.context.rectangle(x, y, width, height) self._stroke(stroke_color, stroke_width) @@ -127,3 +151,36 @@ def arc(self, x, y, radius, start_angle, stop_angle, fill_color="", stroke_color self.context.arc(x, y, radius, start_angle, stop_angle) self._stroke(stroke_color, stroke_width) self._fill(fill_color) + + def triangle(self, x1_or_x, y1_or_y, x2_or_width, y2_or_height, x3_or_p=0.5, y3=None, fill_color="", stroke_color="", stroke_width=-1): + if y3 is not None: + x1 = x1_or_x + y1 = y1_or_y + x2 = x2_or_width + y2 = y2_or_height + x3 = x3_or_p + else: + x = x1_or_x + y = y1_or_y + width = x2_or_width + height = y2_or_height + p = x3_or_p + + x1 = x - width/2 + y1 = y + height/2 + x2 = x + width/2 + y2 = x + height/2 + x3 = (x2 - x1) * p + x1 + y3 = y - height/2 + + self.context.move_to(x1, y1) + self.context.line_to(x2, y2) + self.context.line_to(x3, y3) + self.context.line_to(x1, y1) + self._stroke(stroke_color, stroke_width) + self._fill(fill_color) + + def line(self, x1, y1, x2, y2, stroke_color="", stroke_width=-1): + self.context.move_to(x1, y1) + self.context.line_to(x2, y2) + self._stroke(stroke_color, stroke_width) diff --git a/pypen/settings.py b/pypen/settings.py index d22a597..24b86a6 100644 --- a/pypen/settings.py +++ b/pypen/settings.py @@ -2,7 +2,8 @@ class Settings: def __init__(self, fps, width, height, default_pypen_name, _is_executing_with_python, - _user_has_start, _user_has_update, fill_color, stroke_color, stroke_width): + _user_has_start, _user_has_update, fill_color, stroke_color, stroke_width, + _shape_begun, _starting_point): self.fps = fps self.width = width self.height = height @@ -15,6 +16,9 @@ def __init__(self, fps, width, height, default_pypen_name, _is_executing_with_py self._user_has_start = _user_has_start self._user_has_update = _user_has_update + self._shape_begun = False + self._starting_point = None + settings = Settings(width=640, height=480, @@ -26,6 +30,9 @@ def __init__(self, fps, width, height, default_pypen_name, _is_executing_with_py _is_executing_with_python=False, _user_has_start=True, - _user_has_update=True) + _user_has_update=True, + + _shape_begun=False, + _starting_point=None) default_settings = copy(settings)