From cf3d949369525f97b9f2bff25f5e659009bfcb5d Mon Sep 17 00:00:00 2001 From: Surferlul Date: Sat, 30 Jul 2022 23:03:15 +0200 Subject: [PATCH] Initial commit --- .gitignore | 2 + example.py | 43 +++++++++++++++ native_ui/__init__.py | 19 +++++++ native_ui/abstract/__init__.py | 82 +++++++++++++++++++++++++++++ native_ui/abstract/application.py | 14 +++++ native_ui/abstract/button.py | 45 ++++++++++++++++ native_ui/abstract/container.py | 23 ++++++++ native_ui/abstract/window.py | 11 ++++ native_ui/impl/gnome/__init__.py | 18 +++++++ native_ui/impl/gnome/application.py | 42 +++++++++++++++ native_ui/impl/gnome/button.py | 25 +++++++++ native_ui/impl/gnome/container.py | 27 ++++++++++ native_ui/impl/gnome/window.py | 24 +++++++++ 13 files changed, 375 insertions(+) create mode 100644 example.py create mode 100644 native_ui/__init__.py create mode 100644 native_ui/abstract/__init__.py create mode 100644 native_ui/abstract/application.py create mode 100644 native_ui/abstract/button.py create mode 100644 native_ui/abstract/container.py create mode 100644 native_ui/abstract/window.py create mode 100644 native_ui/impl/gnome/__init__.py create mode 100644 native_ui/impl/gnome/application.py create mode 100644 native_ui/impl/gnome/button.py create mode 100644 native_ui/impl/gnome/container.py create mode 100644 native_ui/impl/gnome/window.py diff --git a/.gitignore b/.gitignore index b6e4761..b97131e 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,5 @@ dmypy.json # Pyre type checker .pyre/ + +.idea/ diff --git a/example.py b/example.py new file mode 100644 index 0000000..e4e5a63 --- /dev/null +++ b/example.py @@ -0,0 +1,43 @@ +import native_ui +import sys + +native_ui.set_runtime_platform("gnome", "org.surferlul.native-ui.ExampleApp") +from native_ui import native, abstract + + +def main(): + window = native.Window() + window.set( + child=None + ) + + app = native.Application( + window=native.Window( + child=native.Container( + layout=abstract.Layout.VERTICAL, + ) + ) + ) + cont = app.window.child + + def on_pressed(button, container): + print("Hello world") + container.call("add_child", + native.Button( + label="Hello", + pressed=on_pressed, + pressed_args=[container] + )) + + cont.add_child(native.Button( + label="Hello", + pressed=on_pressed, + pressed_args=[cont], + )) + + app.build() + app.run(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/native_ui/__init__.py b/native_ui/__init__.py new file mode 100644 index 0000000..7b68842 --- /dev/null +++ b/native_ui/__init__.py @@ -0,0 +1,19 @@ +from . import abstract + +import importlib + + +native = abstract +runtime_platform = None + + +def set_runtime_platform(platform, *args, **kwargs): + global native + global runtime_platform + try: + native = importlib.import_module(f".{platform.lower()}", "native_ui.impl") + runtime_platform = platform.lower() + native.requirements(*args, **kwargs) + except ImportError: + raise ValueError(f"{platform} not implemented") + diff --git a/native_ui/abstract/__init__.py b/native_ui/abstract/__init__.py new file mode 100644 index 0000000..aff9130 --- /dev/null +++ b/native_ui/abstract/__init__.py @@ -0,0 +1,82 @@ +import native_ui +from typing import Callable, Any +from collections import defaultdict +from enum import Enum + + +class Layout(Enum): + HORIZONTAL = 0 + VERTICAL = 1 + + +def abstract_property(function: Callable): + def fget(self): + return getattr(self, f"_{function.__name__}") + + def fset(self, value): + function(self, value) + self.changed(function.__name__) + + return property(fget, fset) + + +class Abstract(object): + def __init__(self, platform: str = None): + self.is_builder = False + if not hasattr(self, "supported_platforms"): + self.supported_platforms = set() + if platform is not None: + self.supported_platforms.add(platform) + self.native = None + + def set(self, **kwargs): + for prop in kwargs: + self.set_property(prop, kwargs[prop]) + + def set_property(self, property_name: str, value: Any, index: int = None): + if index is None: + setattr(self, property_name, value) + else: + getattr(self, property_name)[index] = value + self.changed(property_name, index=index) + + def call(self, property_name: str, *args, **kwargs): + res = getattr(self, property_name)(*args, **kwargs) + self.called(property_name, res, *args, **kwargs) + return res + + def native_call(self, property_name: str, *args, **kwargs): + if native_ui.runtime_platform is None: + print("No runtime platform set") + if native_ui.runtime_platform not in self.supported_platforms: + print(f"Platform {native_ui.runtime_platform} not supported by {type(self).__name__}") + else: + return getattr(self, f"{property_name}_{native_ui.runtime_platform}")(*args, **kwargs) + + def changed(self, property_name: str, index: int = None): + if self.native is not None: + self.native_call("changed", property_name, index) + + def called(self, property_name: str, res: Any, *args, **kwargs): + if self.native is not None: + self.native_call("called", property_name, res, *args, **kwargs) + + def build(self, *args, **kwargs): + if self.native is not None: + return self.native + return self.native_call("build", *args, **kwargs) + + class __metaclass__(type): + __inheritors__ = defaultdict(list) + + def __new__(meta, name, bases, dct): + klass = type.__new__(meta, name, bases, dct) + for base in klass.mro()[1:-1]: + meta.__inheritors__[base].append(klass) + return klass + + +from .button import Button +from .container import Container +from .window import Window +from .application import Application diff --git a/native_ui/abstract/application.py b/native_ui/abstract/application.py new file mode 100644 index 0000000..8a62361 --- /dev/null +++ b/native_ui/abstract/application.py @@ -0,0 +1,14 @@ +from . import Abstract, abstract_property, Window + + +class Application(Abstract): + def __init__(self, window: Window = None, platform: str = None): + super().__init__(platform=platform) + self._window = window + + @abstract_property + def window(self, value): + self._window = value + + def run(self, *args, **kwargs): + self.native_call("run", *args, **kwargs) diff --git a/native_ui/abstract/button.py b/native_ui/abstract/button.py new file mode 100644 index 0000000..ea3b594 --- /dev/null +++ b/native_ui/abstract/button.py @@ -0,0 +1,45 @@ +from . import Abstract, abstract_property +from typing import Callable + + +class Button(Abstract): + def __init__(self, + label: str = "", + pressed: Callable = lambda button: None, + pressed_args: list = None, + pressed_kwargs: dict = None, + platform: str = None): + super().__init__(platform=platform) + self._label = label + self._pressed = pressed + if pressed_args is None: + pressed_args = [] + if pressed_kwargs is None: + pressed_kwargs = {} + self._pressed_args = pressed_args + self._pressed_kwargs = pressed_kwargs + + @abstract_property + def label(self, value): + self._label = value + + @abstract_property + def pressed(self, value): + self._pressed = value + + @abstract_property + def pressed_args(self, value): + self._pressed_args = value + + def add_pressed_arg(self, value): + self._pressed_args.append(value) + + @abstract_property + def pressed_kwargs(self, value): + self._pressed_kwargs = value + + def set_pressed_kwarg(self, key, value): + self._pressed_kwargs[key] = value + + def press(self, *args, **kwargs): + self.call("pressed", *args, *self._pressed_args, **kwargs, **self._pressed_kwargs) diff --git a/native_ui/abstract/container.py b/native_ui/abstract/container.py new file mode 100644 index 0000000..d5beb65 --- /dev/null +++ b/native_ui/abstract/container.py @@ -0,0 +1,23 @@ +from . import Abstract, Layout, abstract_property + + +class Container(Abstract): + def __init__(self, layout: Layout = Layout.HORIZONTAL, children: list = None, platform: str = None): + super().__init__(platform=platform) + if children is None: + children = [] + self._children = children + self._layout = layout + + @abstract_property + def layout(self, value): + self._layout = value + + def get_children(self): + return self._children + + def add_child(self, child): + self._children.append(child) + + def pop_child(self, index): + return self._children.pop(index) diff --git a/native_ui/abstract/window.py b/native_ui/abstract/window.py new file mode 100644 index 0000000..29a997c --- /dev/null +++ b/native_ui/abstract/window.py @@ -0,0 +1,11 @@ +from . import Abstract, abstract_property + + +class Window(Abstract): + def __init__(self, child: Abstract = None, platform: str = None): + super().__init__(platform=platform) + self._child = child + + @abstract_property + def child(self, value): + self._child = value diff --git a/native_ui/impl/gnome/__init__.py b/native_ui/impl/gnome/__init__.py new file mode 100644 index 0000000..2bc16e2 --- /dev/null +++ b/native_ui/impl/gnome/__init__.py @@ -0,0 +1,18 @@ +import gi + +gi.require_version('Gtk', '4.0') +gi.require_version('Adw', '1') +from gi.repository import Gtk, Adw + +application_id = None + + +def requirements(app_id: str): + global application_id + application_id = app_id + + +from .button import Button +from .container import Container +from .window import Window +from .application import Application diff --git a/native_ui/impl/gnome/application.py b/native_ui/impl/gnome/application.py new file mode 100644 index 0000000..1d46726 --- /dev/null +++ b/native_ui/impl/gnome/application.py @@ -0,0 +1,42 @@ +from native_ui import abstract +from . import Adw +from pathlib import Path +from typing import Any +from .. import gnome + +platform = Path(__file__).parent.name + + +class Application(abstract.Application): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs, platform=platform) + + def changed_gnome(self, property_name: str, index: int = None): + pass + + def called_gnome(self, property_name: str, res: Any, *args, **kwargs): + pass + + def build_gnome(self): + class GnomeApp(Adw.Application): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.connect('activate', self.on_activate) + self.abstract_window = None + self.window = None + + def on_activate(self, app): + if self.window is None: + if self.abstract_window is not None: + self.window = self.abstract_window.build(app) + else: + return + self.window.present() + + self.native = GnomeApp(application_id=gnome.application_id) + if self.window is not None: + self.native.abstract_window = self.window + return self.native + + def run_gnome(self, *args, **kwargs): + self.native.run(*args, **kwargs) diff --git a/native_ui/impl/gnome/button.py b/native_ui/impl/gnome/button.py new file mode 100644 index 0000000..1964433 --- /dev/null +++ b/native_ui/impl/gnome/button.py @@ -0,0 +1,25 @@ +from native_ui import abstract +from . import Gtk +from pathlib import Path +from typing import Any + +platform = Path(__file__).parent.name + + +class Button(abstract.Button): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs, platform=platform) + + def changed_gnome(self, property_name: str, index: int = None): + pass + + def called_gnome(self, property_name: str, res: Any, *args, **kwargs): + pass + + def clicked(self, _native_button): + self.call("press", self) + + def build_gnome(self): + self.native = Gtk.Button(label=self.label) + self.native.connect('clicked', self.clicked) + return self.native \ No newline at end of file diff --git a/native_ui/impl/gnome/container.py b/native_ui/impl/gnome/container.py new file mode 100644 index 0000000..6ca94cc --- /dev/null +++ b/native_ui/impl/gnome/container.py @@ -0,0 +1,27 @@ +from native_ui import abstract +from . import Gtk +from pathlib import Path +from typing import Any + +platform = Path(__file__).parent.name + + +class Container(abstract.Container): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs, platform=platform) + + def changed_gnome(self, property_name: str, index: int = None): + pass + + def called_gnome(self, property_name: str, res: Any, *args, **kwargs): + if property_name == "add_child": + self.native.append(args[0].build()) + + def build_gnome(self): + orientation = Gtk.Orientation.VERTICAL + if self.layout == abstract.Layout.HORIZONTAL: + orientation = Gtk.Orientation.HORIZONTAL + self.native = Gtk.Box(orientation=orientation) + for child in self.call("get_children"): + self.native.append(child.build()) + return self.native \ No newline at end of file diff --git a/native_ui/impl/gnome/window.py b/native_ui/impl/gnome/window.py new file mode 100644 index 0000000..efc3e85 --- /dev/null +++ b/native_ui/impl/gnome/window.py @@ -0,0 +1,24 @@ +from native_ui import abstract +from . import Gtk +from pathlib import Path +from typing import Any + +platform = Path(__file__).parent.name + + +class Window(abstract.Window): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs, platform=platform) + + def changed_gnome(self, property_name: str, index: int = None): + if "property_name" == "child": + self.native.set_child(self.child.build()) + + def called_gnome(self, property_name: str, res: Any, *args, **kwargs): + pass + + def build_gnome(self, app: abstract.Application = None): + self.native = Gtk.ApplicationWindow(application=app) + if self.child is not None: + self.native.set_child(self.child.build()) + return self.native \ No newline at end of file