Skip to content

Conversation

@tlambert03
Copy link
Member

@tlambert03 tlambert03 commented Nov 13, 2022

this adds a guiclass decorator that combines psygnal.evented and dataclasses.dataclass as well as a descriptor method for building a widget, as a one-stop-shop for easily creating a dataclass that can create a gui with two-way communication with the underlying object. It also implements @brisvag's suggestion for the @button decorator

as with #475 ... this is all still private and internally experimental

see test for example ... which looks like this:

from magicgui._guiclass import button, guiclass

@guiclass
class Foo:
    a: int = 1
    b: str = "bar"

    @button
    def func(self):
        print(self)

foo = Foo()
foo.gui.show()

Screen Shot 2022-11-12 at 9 59 52 PM

@brisvag
Copy link
Contributor

brisvag commented Nov 14, 2022

I love this, and will be an avid user :) I assume it's intended to also allow keywords for extra customizability the same as with functions?

@tlambert03
Copy link
Member Author

tlambert03 commented Nov 14, 2022

All widget specific customizations should come either in Annotated type annotations, or in a data classes.field() metadata entry.

(I dislike the "kwargs with the same name as the parameter" pattern in magicgui... and want to move towards type annotations only)

I'm still thinking about where the container level config (like layout, etc) should go

@brisvag
Copy link
Contributor

brisvag commented Nov 14, 2022

I see; I like the move to annotations only, I often found it counterintuitive (especially to explain) whether something was supposed to be in the annotation or in the kwarg. I mean, there's still a bit the problem, but at least they're on the same line :P So you mean something like:

from magicgui._guiclass import button, guiclass
from dataclasses import field

@guiclass
class Foo:
    a: int = field(widget_type='Slider', ...)
    b: str = "bar"

    @button
    def func(self):
        print(self)

foo = Foo()
foo.gui.show()

@tlambert03
Copy link
Member Author

tlambert03 commented Nov 14, 2022

yeah, something like that (though note that since you're using dataclasses.field it would need to be field(metadata=dict(widget='Slider'))

More generally though, the goal here is to support any sort of dataclass (pydantic, attrs, dataclasses) etc... have a look through this test to see all the patterns that we can build widgets from.

So really here, the key bit about guiclass is just adding a descriptor object (called gui by default) that builds a widget:

class GuiBuilder:
    def __get__(self, instance, owner):
        wdg = build_widget(instance or owner)
        if instance:
            # this part assumes we have an evented instance
            bind_gui_to_instance(wdg, instance)
        return wdg

@evented
@dataclass
class MyClass:
    x: int

    gui = GuiBuilder()

# or, with guiclass, just:

@guiclass
class MyClass:
    x: int

and so, the widget-level control can come in so many different ways:

dataclasses

@dataclass
class Foo:
    c: float = field(default=0.0, metadata={"widget": "FloatSlider", "maximum": 100})

attrs

import attrs

@attrs.define
class Foo:
    c: float = attrs.field(default=0.0, metadata={"widget": "FloatSlider", "maximum": 100})

pydantic

class Foo(pydantic.BaseModel):
    c: float = pydantic.Field(0, widget="FloatSlider", le=100)

annotated_types

from magicgui.schema import UiField

@dataclass
class Foo:
    c: Annotated[float, UiField(widget='Slider', maximum=100)] = 0

# or

from annotated_types import Le
@dataclass
class Foo:
    c: Annotated[float, Le(100)] = 0

so the magicgui-specific part is really quite "light" here. just accepting any form of dataclass and building a widget. and then, optionally offering a .gui descriptor to add a property-like object that can build a widget

@codecov
Copy link

codecov bot commented Nov 16, 2022

Codecov Report

Base: 89.61% // Head: 89.75% // Increases project coverage by +0.13% 🎉

Coverage data is based on head (de9c2e7) compared to base (376933a).
Patch coverage: 96.59% of modified lines in pull request are covered.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #498      +/-   ##
==========================================
+ Coverage   89.61%   89.75%   +0.13%     
==========================================
  Files          36       37       +1     
  Lines        4353     4441      +88     
==========================================
+ Hits         3901     3986      +85     
- Misses        452      455       +3     
Impacted Files Coverage Δ
src/magicgui/schema/_ui_field.py 96.17% <ø> (ø)
src/magicgui/schema/_guiclass.py 96.59% <96.59%> (ø)

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@tlambert03
Copy link
Member Author

This will go through more iterations before it becomes public, but I'm going to merge it as is to play with it some more. Will probably release a long-lived rc soon

@tlambert03 tlambert03 merged commit 90f30e2 into pyapp-kit:main Nov 26, 2022
@tlambert03 tlambert03 deleted the guiclass branch November 26, 2022 18:40
@tlambert03 tlambert03 added the enhancement New feature or request label Nov 26, 2022
@multimeric multimeric mentioned this pull request Oct 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants