Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dan Yeaw - 5 Steps to Build Python Native GUI Widgets for BeeWare

Dan Yeaw - 5 Steps to Build Python Native GUI Widgets for BeeWare

Have you ever wanted to write a GUI application in Python that you can run on both your laptop and your phone? Have you been looking to contribute to an open source project, but you don't know where to start?

BeeWare is a set of software libraries for cross-platform native app development from a single Python codebase and tools to simplify app deployment. The project aims to build, deploy, and run apps for Windows, Linux, macOS, Android, iPhone, and the web. It is native because it is actually using your platform's native GUI widgets, not a theme, icon pack, or webpage wrapper.

This talk will teach you how Toga, the BeeWare GUI toolkit, is architected and then show you how you can contribute to Toga by creating your own GUI widget in five easy steps.

https://us.pycon.org/2019/schedule/presentation/235/

PyCon 2019

May 05, 2019
Tweet

More Decks by PyCon 2019

Other Decks in Programming

Transcript

  1. 5 STEPS TO BUILD PYTHON
    5 STEPS TO BUILD PYTHON
    NATIVE GUI WIDGETS FOR
    NATIVE GUI WIDGETS FOR

    View Slide

  2. BeeWare is
    CROSS-PLATFORM
    CROSS-PLATFORM
    NATIVE
    NATIVE
    APP DEVELOPMENT
    APP DEVELOPMENT
    WITH PYTHON
    WITH PYTHON
    and
    SIMPLE APP DEPLOYMENT
    SIMPLE APP DEPLOYMENT

    View Slide

  3. TOGA
    TOGA
    BEEWARE'S GUI TOOLKIT
    BEEWARE'S GUI TOOLKIT

    View Slide

  4. HELLO WORLD
    HELLO WORLD

    View Slide

  5. import toga
    class HelloWorld(toga.App):
    def startup(self):
    self.main_window = toga.MainWindow(title=self.name)
    main_box = toga.Box()
    self.main_window.content = main_box
    self.main_window.show()
    def main():
    return HelloWorld('Hello World', 'org.pybee.helloworld')

    View Slide

  6. BACKGROUND
    BACKGROUND
    A widget is the controls and logic that a user interacts
    with when using a GUI
    A Canvas widget will be used as an example

    View Slide

  7. TOGA BLACKBOX
    TOGA BLACKBOX

    View Slide

  8. BRIDGE OR
    BRIDGE OR
    TRANSPILER
    TRANSPILER

    View Slide

  9. TOGA WHITEBOX
    TOGA WHITEBOX

    View Slide

  10. MORE TERMS
    MORE TERMS

    View Slide

  11. TOGA_IMPL FACTORY
    TOGA_IMPL FACTORY
    PATTERN
    PATTERN

    View Slide

  12. STEP 0
    STEP 0
    DEVELOPMENT PLATFORM
    DEVELOPMENT PLATFORM
    Normally pick the platform that you are most
    familiar with
    macOS and GTK are the most developed �
    Is this a mobile only widget (camera, GPS, etc)?

    View Slide

  13. STEP 1
    STEP 1
    RESEARCH YOUR WIDGET
    RESEARCH YOUR WIDGET
    Abstraction requires knowledge of specific
    examples
    Create use cases or user stories
    Get feedback

    View Slide

  14. RESEARCH YOUR WIDGET
    RESEARCH YOUR WIDGET
    Tkinter
    canvas = tk.Canvas()
    canvas.create_rectangle(10, 10, 100, 100, fill="red")
    canvas.pack()

    View Slide

  15. RESEARCH YOUR WIDGET
    RESEARCH YOUR WIDGET
    GTK
    drawingarea = Gtk.DrawingArea()
    drawingarea.connect("draw", draw)
    def draw(da, ctx):
    ctx.set_source_rgb(200, 0, 0)
    ctx.rectangle(10, 10, 100, 100)
    ctx.fill()

    View Slide

  16. RESEARCH YOUR WIDGET
    RESEARCH YOUR WIDGET
    Use Cases

    View Slide

  17. STEP 2
    STEP 2
    WRITE DOCS
    WRITE DOCS
    Write your API documentation first
    The API provides the set of clearly defined methods
    of communication (layers) between the so�ware
    components
    Documentation Driven Development
    This is iterative with Step 1

    View Slide

  18. WRITE DOCS
    WRITE DOCS
    The canvas is used for creating a blank widget that you can
    draw on.
    ## Usage
    An example of simple usage is to draw a colored rectangle on
    the screen using the `rect` drawing object:
    import toga
    canvas = toga.Canvas(style=Pack(flex=1))
    with canvas.fill(color=rgb(200, 0, 0)) as fill:
    fill.rect(10, 10, 100, 100)

    View Slide

  19. WRITE CODE OUTLINE /
    WRITE CODE OUTLINE /
    DOCSTRINGS
    DOCSTRINGS
    class Canvas(Widget):
    """Create new canvas.
    Args:
    id (str): An identifier for this widget.
    style (:obj:`Style`): An optional style object.
    factory (:obj:`module`): A python module that is
    capable to return a implementation of this class.

    View Slide

  20. STEP 3
    STEP 3
    IMPLEMENT TOGA_CORE
    IMPLEMENT TOGA_CORE
    (WITH TDD)
    (WITH TDD)
    First write tests for Toga_core
    Then code the outline created in Step 2

    View Slide

  21. WRITE TESTS FOR TOGA_CORE
    WRITE TESTS FOR TOGA_CORE
    def test_widget_created():
    assertEqual(canvas._impl.interface, canvas)
    self.assertActionPerformed(canvas, "create Canvas")

    View Slide

  22. WRITE TESTS FOR TOGA_CORE
    WRITE TESTS FOR TOGA_CORE
    def test_rect_modify():
    rect = canvas.rect(-5, 5, 10, 15)
    rect.x = 5
    rect.y = -5
    rect.width = 0.5
    rect.height = -0.5
    canvas.redraw()
    self.assertActionPerformedWith(
    canvas, "rect", x=5, y=-5, width=0.5, height=-0.5
    )

    View Slide

  23. CODE TOGA_CORE
    CODE TOGA_CORE
    class Canvas(Widget):
    def __init__(self, id=None, style=None, factory=None):
    super().__init__(id=id, style=style, factory=factory)
    self._impl = self.factory.Canvas(interface=self)
    def rect(self, x, y, width, height):
    self.impl.rect(
    self.x, self.y, self.width, self.height
    )

    View Slide

  24. STEP 4
    STEP 4
    IMPLEMENT TOGA_IMPL
    IMPLEMENT TOGA_IMPL
    DUMMY BACKEND
    DUMMY BACKEND
    Dummy is for automatic testing without a native
    platform
    Code the implementation layer API endpoint, create
    a method for each call of the API
    Check that all tests now pass

    View Slide

  25. IMPLEMENT TOGA_IMPL
    IMPLEMENT TOGA_IMPL
    DUMMY BACKEND
    DUMMY BACKEND
    class Canvas(Widget):
    def create(self):
    self._action("create Canvas")
    def rect(self, x, y, width, height):
    self._action(
    "rect", x=x, y=y, width=width, height=height
    )

    View Slide

  26. STEP 5
    STEP 5
    IMPLEMENT TOGA_IMPL
    IMPLEMENT TOGA_IMPL
    YOUR PLATFORM
    YOUR PLATFORM
    Copy toga_dummy and create a new endpoint for
    the platform you chose in Step 1
    Make use of the native interface API for this widget
    on your platform

    View Slide

  27. IMPLEMENT TOGA_IMPL
    IMPLEMENT TOGA_IMPL
    YOUR PLATFORM
    YOUR PLATFORM

    View Slide

  28. class Canvas(Widget):
    def create(self):
    self.native = Gtk.DrawingArea()
    self.native.connect("draw", self.gtk_draw_callback)
    def gtk_draw_callback(self, canvas, gtk_context):
    self.interface._draw(self, draw_context=gtk_context)
    def rect(self, x, y, width, height, draw_context):
    draw_context.rectangle(x, y, width, height)

    View Slide

  29. IMPLEMENT TOGA_IMPL
    IMPLEMENT TOGA_IMPL
    OTHER PLATFORMS
    OTHER PLATFORMS

    View Slide

  30. class TogaCanvas(NSView):
    @objc_method
    def drawRect_(self, rect: NSRect) -> None:
    context = NSGraphicsContext.currentContext.graphicsPort()
    class Canvas(Widget):
    def create(self):
    self.native = TogaCanvas.alloc().init()
    def rect(self, x, y, width, height, draw_context, *args, **kwargs):
    rectangle = CGRectMake(x, y, width, height)
    core_graphics.CGContextAddRect(draw_context, rectangle

    View Slide

  31. ITERATE
    ITERATE
    ITERATE THROUGH STEPS 1-5 TO
    ITERATE THROUGH STEPS 1-5 TO
    COMPLETE YOUR WIDGET
    COMPLETE YOUR WIDGET
    IMPLEMENTATION
    IMPLEMENTATION

    View Slide

  32. SUBMIT A PULL REQUEST!
    SUBMIT A PULL REQUEST!
    ���
    ���

    View Slide

  33. SUMMARY
    SUMMARY
    1. Research Your Widget
    2. Write Docs
    3. Toga_core
    4. Toga_impl - Dummy Backend
    5. Toga_impl - Your Platform

    View Slide

  34. @danyeaw
    github.com/danyeaw
    dan.yeaw.me
    linkedin.com/in/danyeaw
    [email protected]

    View Slide