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

Adding uninteractive labels to @magicgui functions #567

Open
1 task done
multimeric opened this issue Jul 21, 2023 · 9 comments
Open
1 task done

Adding uninteractive labels to @magicgui functions #567

multimeric opened this issue Jul 21, 2023 · 9 comments

Comments

@multimeric
Copy link

multimeric commented Jul 21, 2023

❓ Questions and Help

Let's say I have a simple function that I want made into a GUI, but I want to display some text to the user as a label that isn't treated as a user input. For example it might be instructions to the user. I'm not sure how to do this with magicgui. It doesn't seem to use the docstring, nor is there any argument to @magicgui that helps here. I could created a Label widget as a string argument that is immutable, but that gives me a confusing function signature.

With this example, how could I add the label "Here is some descriptive text I want to show my users" to the top of the form?

from typing import Annotated, Literal
from magicgui import magicgui

@magicgui
def my_function(
    param_a: int,
    param_b: Annotated[int, {'widget_type': "Slider", 'max': 100}] = 42,
    param_c: Literal["First", "Second", "Third"] = "Second"
):
    """Here is some descriptive text I want to show my users"""
    print("param_a:", param_a)
    print("param_b:", param_b)
    print("param_c:", param_c)

my_function.show(run=True)
@tlambert03
Copy link
Member

hi @multimeric,

if you want to add widgets to the layout that are not parameters to your function, then you're better off using the direct widget API

You could create a widgets.Container() then add two widgets to it: a widget.Label, and your my_function widget:
https://pyapp-kit.github.io/magicgui/widgets/#containerwidget

@multimeric
Copy link
Author

Thanks for the suggestion! I just worry that that solution loses some of the elegance of a pure @magicgui function because it's no longer just a function definition, and instead I have to imperatively build a widget container somewhere, so I probably have to make a factory function etc. This makes it worse for magicclass which is where I'm using it.

@tlambert03
Copy link
Member

i'm really not sure there's an "elegant" solution here that avoids an unnatural/magic API. the @magicgui decorator provides a way to create a widget from a group of type annotated parameters. But if you want specifically want to have a widget for something that is not in those parameters, then we need some way of adding it.

If you don't want to make container combining your FunctionGui with a Label, you can also insert the Label into your FunctionGui (which, itself is also just a container):

from magicgui import magic_factory, widgets

def add_label(widget: widgets.FunctionGui):
    widget.insert(0, widgets.Label(value="Hello World!"))

@magic_factory(widget_init=add_label)
def my_func(x: int, y: str):
    ...

widget = my_func()
widget.show(run=True)
Screen Shot 2023-08-03 at 6 23 54 AM

I'm not sure I want overload the magic_factory/magicgui signature any more than that to make it less imperative, since the number of possible ways that someone might want to arrange new widgets in their container is so variable

@multimeric
Copy link
Author

multimeric commented Aug 3, 2023

Thanks, point taken that there is no easy API for this. I like your widget_init suggestion. I wonder if it would be possible to do something even neater and define a add_label decorator that I can decorate my function with, and somehow return a copy of the FunctionGui but with a new widget added on at the top? e.g.

def add_label(label: str) -> Callable[[FunctionGui], FunctionGui]:
    def _decorator(f: FunctionGui) -> FunctionGui:
        # Somehow return a copy of `f` with a new widget added,
        # whose text is `label`, without actually modifying `f` itself
    return _decorator
      
@add_label("Hello world")
@magic_factory(widget_init=add_label)
def my_func(x: int, y: str):
    ...

One other suggestion is you could you use the docstring (ie .__doc__) of the function, and use the description part as a label. Maybe this would need to be locked behind a flag for backwards compatibility. I swear I've seen this done before, but I can't think of where it is.

@tlambert03
Copy link
Member

docs strings are already parsed and used for tooltips (see the tooltips parameter to magicgui, which defaults to True). So if you're using numpy style docstrings, you should already be getting your __doc__ in a tooltip for each parameter.

But I do agree that it would be good to have a more immediately obvious visual indicator of that functionality (like a little info button icon you could hover on, or perhaps a flag to show them as labels)

@multimeric
Copy link
Author

Cool, thanks, I didn't realise that. Is the docstring description part (ie the function's overall description, not the argument descriptions) used for anything though?

@tlambert03
Copy link
Member

nope, looks like it's only parameters at the moment

@tlambert03
Copy link
Member

would you want a feature that makes the first line of the docstring a label at the top?

@multimeric
Copy link
Author

If you think it suits the design of the project, then yes. Probably defaulting to disabled for backwards compatibility.

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