Skip to content

feat: more complete main window implementation#604

Open
tlambert03 wants to merge 43 commits into
pyapp-kit:mainfrom
tlambert03:main-window
Open

feat: more complete main window implementation#604
tlambert03 wants to merge 43 commits into
pyapp-kit:mainfrom
tlambert03:main-window

Conversation

@tlambert03

@tlambert03 tlambert03 commented Oct 20, 2023

Copy link
Copy Markdown
Member

This establishes the protocols and the qt backend for the main window elaboration discussed in #601.

This includes #597

it's turning into a large PR, so I might break it out into smaller ones, but this PR will serves as the cumulative progress.

cc @larsoner

for ease of review, here are the APIs this adds:

class MainWindowWidget(ContainerWidget):
    """Top level Application widget that can contain other widgets."""

    def add_dock_widget(
        self, widget: Widget, *, area: protocols.Area = "right"
    ) -> None:
        """Add a dock widget to the main window."""

    def add_tool_bar(self, widget: Widget, *, area: protocols.Area = "top") -> None:
        """Add a toolbar to the main window."""

    @property
    def menu_bar(self) -> MenuBarWidget:
        """Return the menu bar widget."""

    @menu_bar.setter
    def menu_bar(self, widget: MenuBarWidget | None) -> None:
        """Set the menu bar widget."""

    @property
    def status_bar(self) -> StatusBarWidget:
        """Return the status bar widget."""

    @status_bar.setter
    def status_bar(self, widget: StatusBarWidget | None) -> None:
        """Set the status bar widget."""

    @typing.deprecated
    def create_menu_item(
        self,
        menu_name: str,
        item_name: str,
        callback: Callable | None = None,
        shortcut: str | None = None,
    ) -> None:
        ...


class StatusBarWidget(Widget):
    def add_widget(self, widget: Widget) -> None:
        """Add a widget to the statusbar."""

    def insert_widget(self, position: int, widget: Widget) -> None:
        """Insert a widget at the given position."""

    def remove_widget(self, widget: Widget) -> None:
        """Remove a widget from the statusbar."""

    @property
    def message(self) -> str:
        """Return currently shown message, or empty string if None."""

    @message.setter
    def message(self, message: str) -> None:
        """Return the message timeout in milliseconds."""

    def set_message(self, message: str, timeout: int = 0) -> None:
        """Show a message in the status bar for a given timeout."""


class MenuBarWidget(Widget):
    """Menu bar containing menus. Can be added to a MainWindowWidget."""

    def __getitem__(self, key: str) -> MenuWidget:
        return self._menus[key]

    def add_menu(self, title: str, icon: str | None = None) -> MenuWidget:
        """Add a menu to the menu bar."""

    def clear(self) -> None:
        """Clear the menu bar."""


class MenuWidget(Widget):
    """Menu widget. Can be added to a MenuBarWidget or another MenuWidget."""

    def add_action(
        self,
        text: str,
        shortcut: str | None = None,
        icon: str | None = None,
        tooltip: str | None = None,
        callback: Callable | None = None,
    ) -> None:
        """Add an action to the menu."""

    def add_separator(self) -> None:
        """Add a separator line to the menu."""

    def add_menu(self, title: str, icon: str | None = None) -> MenuWidget:
        """Add a menu to the menu."""

    def clear(self) -> None:
        """Clear the menu bar."""



class ToolBarWidget(Widget):

    def add_button(
        self, text: str = "", icon: str = "", callback: Callable | None = None
    ) -> None:
        """Add an action to the toolbar."""

    def add_separator(self) -> None:
        """Add a separator line to the toolbar."""

    def add_spacer(self) -> None:
        """Add a spacer to the toolbar."""

    def add_widget(self, widget: Widget) -> None:
        """Add a widget to the toolbar."""

    @property
    def icon_size(self) -> tuple[int, int] | None:
        """Return the icon size of the toolbar."""

    @icon_size.setter
    def icon_size(self, size: int | tuple[int, int] | None) -> None:
        """Set the icon size of the toolbar."""

    def clear(self) -> None:
        """Clear the toolbar."""
@codecov

codecov Bot commented Oct 20, 2023

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 59.95204% with 167 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.42%. Comparing base (aa38630) to head (778c0ea).
⚠️ Report is 77 commits behind head on main.

Files with missing lines Patch % Lines
src/magicgui/backends/_qtpy/widgets.py 42.46% 84 Missing ⚠️
src/magicgui/backends/_ipynb/widgets.py 68.68% 31 Missing ⚠️
src/magicgui/widgets/bases/_menubar.py 46.55% 31 Missing ⚠️
src/magicgui/widgets/bases/_main_window.py 57.57% 14 Missing ⚠️
src/magicgui/widgets/bases/_statusbar.py 71.42% 6 Missing ⚠️
src/magicgui/application.py 90.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #604      +/-   ##
==========================================
- Coverage   87.75%   85.42%   -2.34%     
==========================================
  Files          40       43       +3     
  Lines        4705     5091     +386     
==========================================
+ Hits         4129     4349     +220     
- Misses        576      742     +166     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
@larsoner

Copy link
Copy Markdown

it's turning into a large PR, so I might break it out into smaller ones, but this PR will serves as the cumulative progress.

+963 −89 is large but not insurmountable. Let me know when it would help for me to look / try / whatever!

@tlambert03

Copy link
Copy Markdown
Member Author

Thanks for checking in @larsoner! I was actually just gonna ping you today. I think the protocol is in good shape, and the qt backend is working pretty well. So it actually would be a great time for you to play with it and see if you can improve the ipywidgest backend.

I have a simple example.py in the root of this PR that I've been playing with. the ipywidgets backend loaded at one point, it just (mostly) does nothing for now. I implemented a barebones version of the GridSpec pattern you proposed.

I think you know ipywidgets better than I do, so if you had time and wanted to tinker a bit, i think just running example.py in jupyter and working on styles and implementing methods in the ipynb backend would be super useful! 🙏

@tlambert03

Copy link
Copy Markdown
Member Author

+963 −89 is large but not insurmountable

it's true, and most of this PR is really just boilerplate adding the new protocols

@larsoner

Copy link
Copy Markdown

I think you know ipywidgets better than I do

I am actually a bit of an ipywidgets newcomer but happy to give it a shot! Should be able to look next week

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants