{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n# Menu\n\nUsing texts to construct a simple menu.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "from dataclasses import dataclass\n\nimport matplotlib.pyplot as plt\n\nimport matplotlib.artist as artist\nimport matplotlib.patches as patches\nfrom matplotlib.typing import ColorType\n\n\n@dataclass\nclass ItemProperties:\n    fontsize: float = 14\n    labelcolor: ColorType = 'black'\n    bgcolor: ColorType = 'yellow'\n    alpha: float = 1.0\n\n\nclass MenuItem(artist.Artist):\n    padx = 0.05  # inches\n    pady = 0.05\n\n    def __init__(self, fig, labelstr, props=None, hoverprops=None,\n                 on_select=None):\n        super().__init__()\n\n        self.set_figure(fig)\n        self.labelstr = labelstr\n\n        self.props = props if props is not None else ItemProperties()\n        self.hoverprops = (\n            hoverprops if hoverprops is not None else ItemProperties())\n        if self.props.fontsize != self.hoverprops.fontsize:\n            raise NotImplementedError(\n                'support for different font sizes not implemented')\n\n        self.on_select = on_select\n\n        # specify coordinates in inches.\n        self.label = fig.text(0, 0, labelstr, transform=fig.dpi_scale_trans,\n                              size=props.fontsize)\n        self.text_bbox = self.label.get_window_extent(\n            fig.canvas.get_renderer())\n        self.text_bbox = fig.dpi_scale_trans.inverted().transform_bbox(self.text_bbox)\n\n        self.rect = patches.Rectangle(\n            (0, 0), 1, 1, transform=fig.dpi_scale_trans\n        )  # Will be updated later.\n\n        self.set_hover_props(False)\n\n        fig.canvas.mpl_connect('button_release_event', self.check_select)\n\n    def check_select(self, event):\n        over, _ = self.rect.contains(event)\n        if not over:\n            return\n        if self.on_select is not None:\n            self.on_select(self)\n\n    def set_extent(self, x, y, w, h, depth):\n        self.rect.set(x=x, y=y, width=w, height=h)\n        self.label.set(position=(x + self.padx, y + depth + self.pady / 2))\n        self.hover = False\n\n    def draw(self, renderer):\n        self.rect.draw(renderer)\n        self.label.draw(renderer)\n\n    def set_hover_props(self, b):\n        props = self.hoverprops if b else self.props\n        self.label.set(color=props.labelcolor)\n        self.rect.set(facecolor=props.bgcolor, alpha=props.alpha)\n\n    def set_hover(self, event):\n        \"\"\"\n        Update the hover status of event and return whether it was changed.\n        \"\"\"\n        b, _ = self.rect.contains(event)\n        changed = (b != self.hover)\n        if changed:\n            self.set_hover_props(b)\n        self.hover = b\n        return changed\n\n\nclass Menu:\n    def __init__(self, fig, menuitems):\n        self.figure = fig\n\n        self.menuitems = menuitems\n\n        maxw = max(item.text_bbox.width for item in menuitems)\n        maxh = max(item.text_bbox.height for item in menuitems)\n        depth = max(-item.text_bbox.y0 for item in menuitems)\n\n        x0 = 1\n        y0 = 4\n\n        width = maxw + 2 * MenuItem.padx\n        height = maxh + MenuItem.pady\n\n        for item in menuitems:\n            left = x0\n            bottom = y0 - maxh - MenuItem.pady\n\n            item.set_extent(left, bottom, width, height, depth)\n\n            fig.artists.append(item)\n            y0 -= maxh + MenuItem.pady\n\n        fig.canvas.mpl_connect('motion_notify_event', self.on_move)\n\n    def on_move(self, event):\n        if any(item.set_hover(event) for item in self.menuitems):\n            self.figure.canvas.draw()\n\n\nfig = plt.figure()\nfig.subplots_adjust(left=0.3)\nprops = ItemProperties(labelcolor='black', bgcolor='yellow',\n                       fontsize=15, alpha=0.2)\nhoverprops = ItemProperties(labelcolor='white', bgcolor='blue',\n                            fontsize=15, alpha=0.2)\n\nmenuitems = []\nfor label in ('open', 'close', 'save', 'save as', 'quit'):\n    def on_select(item):\n        print(f'you selected {item.labelstr}')\n    item = MenuItem(fig, label, props=props, hoverprops=hoverprops,\n                    on_select=on_select)\n    menuitems.append(item)\n\nmenu = Menu(fig, menuitems)\nplt.show()"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.14.6"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}