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

Nested menus that only load when selected. #8

Open
dbkosky opened this issue May 4, 2021 · 1 comment
Open

Nested menus that only load when selected. #8

dbkosky opened this issue May 4, 2021 · 1 comment

Comments

@dbkosky
Copy link

dbkosky commented May 4, 2021

Firstly, thanks for making this, it's been very useful for me.
I just wanted to open this as an issue since it was a problem I had and other users might find this to be a useful solution.
I haven't fully tested it so it might not work fully with other aspects of the application but it might be useful in the future.

The problem I was having was that with menus of a sufficiently large size (e.g. hundreds of items across multiple nested menus),
the performance at runtime was suffering as a result of having to build all of these items at the start of execution.
The solution I have is an extension of the nested menu class that builds the sub menu at runtime, and only if it has been
a) selected in the previous execution of the menu,
b) the item that has been selected is a descendant of the nested menu,
c) the last_active_menu was a descendant of the nested menu and there is no selected item (i.e. when there has been user input in a descendant menu)

import asyncio
from rofi_menu import NestedMenu, Operation, constants

class DynamicNestedMenu(NestedMenu):
    """
    Item used to provide selectable playlists in the playlist menu 
    """
    def __init__(self, text=None, sub_menu_type=None, **sub_menu_args):
        super().__init__(text=text)
        self.sub_menu_type = sub_menu_type
        self.sub_menu_args = sub_menu_args
        
    async def build(self, parent_menu, item_id, meta):
        """
        The normal implementation of this function builds the submenu by default.
        Since the subenu is only needed upon selection (or selection of items within the submenu), 
        we will only build the submenu at this point if the meta.selected_id is deeper than this level, 
        and matches the nested menu. 
        """
        obj = await super().build(parent_menu=parent_menu, item_id=item_id, meta=meta)
        # Only build the sub menu if the item has been selected or the nested menu was part of the
        # previous menu selection and there is no selected item (i.e. there was user input)
        if (item_id is not None and meta.selected_id is not None and\
            len(item_id) < len(meta.selected_id) and\
            meta.selected_id[len(item_id)-1] == item_id[-1])\
            or\
            (meta.selected_id is None and\
             meta.session.get('last_active_menu') and\
             meta.session['last_active_menu'][:len(item_id)] == item_id):

            if asyncio.iscoroutinefunction(self.sub_menu_type):
                self.sub_menu = await self.sub_menu_type(**self.sub_menu_args)
                obj.sub_menu = await self.sub_menu.build(menu_id=obj.id, meta=meta)
            else:
                self.sub_menu = self.sub_menu_type(**self.sub_menu_args)
                obj.sub_menu = await self.sub_menu.build(menu_id=obj.id, meta=meta)

        return obj

    async def on_select(self, meta):

        if asyncio.iscoroutinefunction(self.sub_menu_type):
            self.sub_menu = await self.sub_menu_type(**self.sub_menu_args)
            self.sub_menu = await self.sub_menu.build(menu_id=meta.selected_id, meta=meta)
        else:
            self.sub_menu = self.sub_menu_type(**self.sub_menu_args)
            self.sub_menu = await self.sub_menu.build(menu_id=meta.selected_id, meta=meta)        
            
        return Operation(constants.OP_OUTPUT, await self.sub_menu.handle_render(meta))

I should note that the asyncio.iscoroutine conditional is there because some of my constructors are async classmethods.
It's not really necessary if none of the constructor methods are async.

It is called like so:

DynamicNestedMenu(
    text="dynamic nested menu",
    sub_menu_type=rofi_menu.Menu
    prompt="this will be the prompt of the sub menu"
)

Hope this is useful at some point

@miphreal
Copy link
Owner

miphreal commented May 7, 2021

Thanks, @dbkosky for the suggestion! I think lazy loading of the nested menus should be the default behaviour.

Perhaps, we can introduce this feature the following way:

diff --git a/rofi_menu/menu.py b/rofi_menu/menu.py
index 3680247..0e2a06f 100755
--- a/rofi_menu/menu.py
+++ b/rofi_menu/menu.py
@@ -208,7 +208,21 @@ class NestedMenu(Item):
         building for submenu and return "bound" element.
         """
         obj = await super().build(parent_menu=parent_menu, item_id=item_id, meta=meta)
-        obj.sub_menu = await self.sub_menu.build(menu_id=obj.id, meta=meta)
+
+        latest_menu_id = meta.state_manager.get("last_active_menu")
+        is_active_menu = bool(
+            # It was selected
+            meta.selected_id
+            and item_id == meta.selected_id[: len(item_id)]
+            # or user entered some text
+            or meta.user_input
+            and latest_menu_id
+            and item_id == latest_menu_id[: len(item_id)]
+        )
+
+        if is_active_menu:
+            obj.sub_menu = await self.sub_menu.build(menu_id=obj.id, meta=meta)
+

I won't be available for the next 2 weeks, so will return to this feature after

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants