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

A Cross-Platform Pure Python Declarative UI Framework

A Cross-Platform Pure Python Declarative UI Framework

This deck was created for PyCon APAC 2022 on or about July 20, 2022.

Yasushi Itoh

August 28, 2022
Tweet

More Decks by Yasushi Itoh

Other Decks in Programming

Transcript

  1. • Software Engineer at Mercari, Inc. in Japan • I

    like several programming languages. • I've been using Python regularly for over 20 years and my favorite programming language to use. • My past python experience: ◦ Hobby: i2y/mochi: Dynamically typed functional programming language (github.com) ◦ Job: document translation (combination of Natural Language Processing, Image Processing and ML) on AWS Step Functions Self Introduction
  2. • In the software I have been involved in developing,

    I have used various languages depending on the requirements. • In the applications and backend systems at my current employer, various programming languages are used depending on the purpose. Technology Stack | Mercari Engineering • This is common since different languages and frameworks have different strengths and weaknesses. Use of various programming languages and frameworks
  3. The reasons I think: • Switching Cost 1: If everything

    can be written in one language, it reduces the burden on the brain when switching a language to another language on programming. • Switching Cost 2: I may not need to acquire knowledge of multiple languages and frameworks. • Hiring Cost?: Hiring someone may be easier (if it’s not an obscure language). To avoid incurring these costs, there are two directions to take: write the web- frontend/app parts and backend parts in a single language or framework. Or write all UI code (all frontend/app parts) in less languages and frameworks. I don't want to write code from scratch for each platform, especially in the case of UI code, which can be mostly the same. I think its’ a common desire for other programmers too. Want to write apps/services using less languages or frameworks if I can..
  4. Interest in Flutter/Compose Multiplatform interest has been increasing in recent

    years Note: Please forgive that the search is based on Jetpack Compose Multiplatform instead of Compose Multiplatform, but it doesn't change most results.
  5. • For single programming language ◦ Flutter: Dart, Compose Multiplatform:

    Kotlin ◦ These are primarily implemented in that language and have APIs specific to that language. These are not wrappers for existing UI frameworks. So… it’s easy to use in that language and seems to be easy to hack or understand. • Declarative UI: UI can be described declaratively in the language ◦ Other than Flutter and Compose Multiplatform, most of the other UI frameworks that have emerged in the recent past claim to offer Declarative UI (React Native/React, SwiftUI, etc.) Common Features of Flutter and Compose Multiplatform
  6. What’s Declarative UI? I've done some research, but there doesn’t

    seem to be a clear definition. Of course, Wikipedia doesn’t have an entry for it. However, roughly speaking, it seems that the display (view) and state are defined declaratively, the view switches automatically when the state changes. Also, the view can be defined structurally. Non-declarative UI is sometimes called Imperative UI. # Declarative UI state = State(“text”) view = Page(Text(state), Button(“button”) .on_click(lambda: state.set(“text2”))) view.show() click
  7. What’s Declarative UI? # Imperative UI p = Page() txt

    = Text(“text”) p.add_child(txt) btn = Button(“button”) p.add_child(btn) def change_text(): p.remove_child(txt) new_txt = Text(“text2”) p.add_child(new_text) btn.on_click(change_text) p.show() # Declarative UI state = State(“text”) view = Page(Text(state), Button(“button”) .on_click(lambda: state.set(“text2”))) view.show() I've done some research, but there doesn’t seem to be a clear definition. Of course, Wikipedia doesn’t have an entry for it. However, roughly speaking, it seems that the display (view) and state are defined declaratively, the view switches automatically when the state changes. Also, the view can be defined structurally. Non-declarative UI is sometimes called Imperative UI.
  8. • I simply want or would like to see it.

    Especially something that isn’t a wrapper for an existing framework. • It may be tough to use for product applications, but it could be used for internal tools or for private use. Is there a cross-platform declarative UI framework in the Python World?
  9. Famous Cross-Platform UI Frameworks in the Python World (At least

    two platform supported) Name Declarative UI View can be defined declaratively in Python Not a wrapper Qt for Python (PySide6) ✔ (QML) (Qt) Kivy ✔ (Kv Language) ✔ Tkinter (tcl/tk) wxPython (WxWidgets) Enaml ✔ (Qt) Toga ✔ ✔ (OS Natives/Web) Flexx ✔ ✔ (Web) Edifice ✔ ✔ (Qt)
  10. My Guess It was common practice to use native look

    and feel. This has changed recently as web-based applications have become more common. ① Both Flutter and Compose Multiplatform use Skia. This is an important and essential part of the implementation, but it or an equivalent have only recently become available in Python. ③ The demand for writing views declaratively had been met by KV language of Kivy and others, so the motivation to create such an API from scratch was weak in the community and among individuals. ② CPU and GPU performance and the libraries to utilize them were lacking in the past. ④
  11. • google/skia: Skia is a complete 2D graphic library for

    drawing Text, Geometries, and Images. (github.com) • CanvasKit - Skia + WebAssembly | Skia Skia and CanvasKit
  12. The aforementioned reasons may no longer be relevant or exist.

    So, I think it’s time to trial a cross-platform declarative UI for Python.
  13. • Pure Python! ◦ Not a wrapper. It’s made for

    Python using Python from scratch. ◦ Except for dependent window management/rendering libraries • Declarative UI • Cross Platform ◦ Windows, Mac OS, Linux, Web Browsers ▪ Currently, it supports only desktop OS and browsers. Main Features of Castella
  14. • Pure Python! ◦ Not a wrapper. It’s made for

    Python using Python from scratch. ◦ Except for dependent window management/rendering libraries • Declarative UI • Cross Platform ◦ Windows, Mac OS, Linux, Web Browsers ▪ Currently, it supports only desktop OS and browsers. Main Features of Castella
  15. Castella is a Pure Python Framework • It’s pure python,

    except for dependent window management and rendering libraries. • For Desktop App: (GLFW or SDL2) and Skia • For Web Browser: CanvasKit and PyScript/PyOdide GLFW Castella Desktop OS Skia SDL2 Castella Desktop OS Skia PyScri pt Castella Web Browser Canva sKit ※ It’s important for Castella that there are nice bindings for each native libraries in Python. E.g., skia-python.
  16. Castella is a Pure Python Framework • The Core and

    each widget module is highly portable because it relies only on the Painter and Frame Protocols, which define the I/F common to each backend. If you add a backend that implements the common I/F for a certain platform, Core and each widget should work correctly. GLFW Castella Desktop OS Skia SDL2 Castella Desktop OS Skia PyScri pt Castella Web Browser Canvas Kit ※ It’s important for Castella that there are nice bindings for each native libraries in Python. E.g., skia-python. Painter Frame Painter Frame Painter Frame
  17. Main Features of Castella • Pure Python! ◦ Not a

    wrapper. It’s made for Python using Python from scratch. ◦ Except for dependent window management/rendering libraries • Declarative UI • Cross Platform ◦ Windows, Mac OS, Linux, Web Browsers ▪ Currently, it supports only desktop OS and browsers.
  18. Declarative UI class Counter(Component): def __init__(self): super().__init__() self._count = State(0)

    def view(self): return Column( Text(self._count), Row( Button("Up", font_size=50).on_click(self.up), Button("Down", font_size=50).on_click(self.down), ), ) def up(self, _): self._count += 1 def down(self, _): self._count -= 1 App(Frame("Counter", 800, 600), Counter()).run() View State Event Callbacks
  19. Declarative UI class Clock(Component): def __init__(self): super().__init__() self._state = State(datetime.now().time().strftime("%X"))

    self._thread = threading.Thread(target=self.run, args=()) self._thread.daemon = True self._thread.start() def run(self): while True: time.sleep(1) self._state.set(datetime.now().time().strftime("%X")) def view(self): return Text(self._state) App(Frame("Clock", 600, 200), Clock()).run() Thread updating the state (time) every second
  20. • Pure Python! ◦ Not a wrapper. It’s made for

    Python using Python from scratch. ◦ Except for dependent window management/rendering libraries • Declarative UI • Cross Platform ◦ Windows, Mac OS, Linux, Web Browsers ▪ Currently, it supports only desktop OS and browsers. Main Features of Castella
  21. Cross-Platform Castella Desktop OS Skia GLFW Castella Desktop OS Skia

    SDL2 Castella Web Browser Canvas Kit PyScript
  22. Layout Column Row Box First Second Third First Second Third

    Child Column( Text(“First"), Text(“Second"), Text(“Third"), ) Row( Text(“First"), Text(“Second"), Text(“Third"), ) Box(Text(“Child")) Column aligns child elements vertically. Row aligns child elements horizontally. Box takes only one child elements and if the child size is larger than the box, it provides scrollbars automatically.
  23. Size Policy Each widget has size policies for width and

    height. There are three types of policies that can be specified • Expanding: maximum size that will fit in the parent widget • Fixed: specified size • Content: size according to the content (e.g., for text, according to the size of that text) The default policy is different depending on the kind of widget, but it is basically “Expanding” for both width and height.
  24. Examples: Expanding Basic Behavior The default setting value of the

    example on the left without omission Each child element (widgets)’s default size policy is basically Expanding. In this example, the three child elements share their width equally. The height of each child element is the height of the row. First Second Third Row( Text(“First"), Text(“Second"), Text(“Third"), ) Row( Text(“First").WidthPolicy(SizePolicy.Expanding) .HeightPolicy(SizePolicy.Expanding), Text(“Second").WidthPolicy(SizePolicy.Expanding) .HeightPolicy(SizePolicy.Expanding), Text(“Third").WidthPolicy(SizePolicy.Expanding) .HeightPolicy(SizePolicy.Expanding), )
  25. Examples: Expanding Flex By specifying "flex" you can specify a

    percentage of the overall size. In this example, the width of the entire row is divided in the ratio 1:2:1. First Second Third Row( Text(“First").flex(1), Text(“Second").flex(2), Text(“Third").flex(1), ) Mix with Other Policies Of course, you can mix multiple size policies for the children of a single parent widget. In this example, the first element is displayed at the specified fixed size. The remaining two child elements share the remaining width in 2:1 ratio. First Second Third Row( Text(“First").fixed(300, 100), Text(“Second").flex(2), Text(“Third").flex(1), )
  26. Two Types of Custom Widget You can combine built-in widgets

    to create a custom widget. It is defined as a subclass of Component or a subclass of Stateful Component. (You can also define custom widgets without combining existing widgets, but that's another story.) • Component ◦ A component that doesn’t have the state of the component as a whole • Stateful Component ◦ A component that has state as the component
  27. Component class Counter(Component): def __init__(self): super().__init__() self._count = State(0) def

    view(self): c = self._count return Row( Button("Up", font_size=50).on_click(lambda _: c.set(c() + 1)), Text(c()), Button("Down", font_size=50).on_click(lambda _: c.set(c() - 1)), ) App(Frame("NumList", 800, 600), Counter()).run()
  28. Component class Counter(Component): def view(self): count = State(0) up =

    lambda _: count.set(count() + 1) down = lambda _: count.set(count() - 1) return Column( Text(count), Row( Button("+", font_size=50).on_click(up), Button("-", font_size=50).on_click(down), ), ) App(Frame("Counter", 800, 600), Counter()).run()
  29. Stateful Component class NumList(StatefulComponent): def __init__(self, n: State[int]): super().__init__(n) self._num:

    State[int] = n def view(self): return Column( Button(“Add”).fixed_height(40).on_click(lambda _: self.num.set(self._num() + 1)), *(Text(I + 1).fixed_height(40) for I in range(self._num())), scrollable=True, ) App(Frame("NumList", 480, 300), NumList(State(1))).run()
  30. • Cross Platform Declarative UI Frameworks seem to be gaining

    momentum. • In Declarative UI, view and state are defined declaratively, and the view switches automatically when the state changes. Also, the view can be defined structurally. • There are many good Cross Platform UI Frameworks available in Python. However, there doesn't seem to be a pure python one that allows you to write declarative UI in Python. • I tried making Castella. If you like, please try using it to create something. I hope this presentation has been of some use to you all. Summary