Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Launcher: support Component icons inside apworlds #3629

Merged
merged 7 commits into from
Nov 30, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions Launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,8 @@ def launch(exe, in_terminal=False):


def run_gui():
from kvui import App, ContainerLayout, GridLayout, Button, Label, ScrollBox, Widget
from kvui import App, ContainerLayout, GridLayout, Button, Label, ScrollBox, Widget, ApAsyncImage
from kivy.core.window import Window
from kivy.uix.image import AsyncImage
from kivy.uix.relativelayout import RelativeLayout

class Launcher(App):
Expand Down Expand Up @@ -199,8 +198,8 @@ def build_button(component: Component) -> Widget:
button.component = component
button.bind(on_release=self.component_action)
if component.icon != "icon":
image = AsyncImage(source=icon_paths[component.icon],
size=(38, 38), size_hint=(None, 1), pos=(5, 0))
image = ApAsyncImage(source=icon_paths[component.icon],
size=(38, 38), size_hint=(None, 1), pos=(5, 0))
box_layout = RelativeLayout(size_hint_y=None, height=40)
box_layout.add_widget(button)
box_layout.add_widget(image)
Expand Down
40 changes: 40 additions & 0 deletions kvui.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import sys
import typing
import re
import io
import pkgutil
from collections import deque

if sys.platform == "win32":
Expand Down Expand Up @@ -35,6 +37,7 @@
from kivy.core.window import Window
from kivy.core.clipboard import Clipboard
from kivy.core.text.markup import MarkupLabel
from kivy.core.image import ImageLoader, ImageLoaderBase, ImageData
from kivy.base import ExceptionHandler, ExceptionManager
from kivy.clock import Clock
from kivy.factory import Factory
Expand All @@ -61,6 +64,7 @@
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from kivy.animation import Animation
from kivy.uix.popup import Popup
from kivy.uix.image import AsyncImage

fade_in_animation = Animation(opacity=0, duration=0) + Animation(opacity=1, duration=0.25)

Expand Down Expand Up @@ -770,6 +774,42 @@ def fix_heights(self):
element.height = max_height


class ApAsyncImage(AsyncImage):
def is_uri(self, filename):
qwint marked this conversation as resolved.
Show resolved Hide resolved
if filename[:3] == "ap:":
qwint marked this conversation as resolved.
Show resolved Hide resolved
return True
else:
return super().is_uri(filename)


class ImageLoaderPkgutil(ImageLoaderBase):
def load(self, filename):
qwint marked this conversation as resolved.
Show resolved Hide resolved
# take off the "ap:" prefix
module, path = filename[3:].split("/", 1)
data = pkgutil.get_data(module, path)
return self._bytes_to_data(data)

def _bytes_to_data(self, data):
qwint marked this conversation as resolved.
Show resolved Hide resolved
from PIL import Image as PImage
p_im = PImage.open(io.BytesIO(data)).convert("RGBA")
im_d = ImageData(p_im.size[0], p_im.size[1], p_im.mode.lower(), p_im.tobytes())
return [im_d]


# grab the default loader method so we can override it but use it as a fallback
DefaultLoad = ImageLoader.load
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

variable name

  • This isn't a type, so this isn't the right capitalization pattern for it.
  • Is there any reason for people to import this from the module and use it directly? If not it should begin with underscore (_)

maybe _original_load or _original_image_load or _original_image_loader_load



def load_override(filename, default_load=DefaultLoad, **kwargs):
qwint marked this conversation as resolved.
Show resolved Hide resolved
if filename[:3] == "ap:":
qwint marked this conversation as resolved.
Show resolved Hide resolved
return ImageLoaderPkgutil(filename)
else:
return default_load(filename, **kwargs)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're overriding load, why aren't we returning the same type that load returns?
It looks like the default load returns an instance of Image, while the new load would return an instance of ImageLoaderBase (which itself has a load function that returns a list of ImageData).
Can you explain how this works?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if ImageLoader.load() needs to load asynchronously it returns an ImageLoaderBase that can handle the request
https://github.com/kivy/kivy/blob/master/kivy/core/image/__init__.py#L454
this is mimicking that workflow but just short-circuiting it as early as possible because the current loader picking code in kivy cannot handle the information we need in the filename



ImageLoader.load = load_override


class E(ExceptionHandler):
logger = logging.getLogger("Client")

Expand Down
Loading