magicgui icon indicating copy to clipboard operation
magicgui copied to clipboard

Adding uninteractive labels to `@magicgui` functions

Open multimeric opened this issue 2 years ago • 9 comments

❓ 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)

multimeric avatar Jul 21 '23 00:07 multimeric

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

tlambert03 avatar Aug 02 '23 13:08 tlambert03

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.

multimeric avatar Aug 03 '23 10:08 multimeric

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

tlambert03 avatar Aug 03 '23 10:08 tlambert03

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.

multimeric avatar Aug 03 '23 10:08 multimeric

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)

tlambert03 avatar Aug 03 '23 11:08 tlambert03

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?

multimeric avatar Aug 03 '23 11:08 multimeric

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

tlambert03 avatar Aug 03 '23 11:08 tlambert03

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

tlambert03 avatar Aug 03 '23 12:08 tlambert03

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

multimeric avatar Aug 04 '23 14:08 multimeric