{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "%matplotlib inline"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Customize Trame toolbar {#customize_trame_toolbar_example}\n\nBring more of the power of trame to the jupyter view.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import asyncio\n\nimport pyvista as pv\nfrom pyvista.trame.ui.vuetify3 import button, divider, select, slider, text_field"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Let\\'s first create the menu items we want to add to the trame\\'s\ntoolbar. Here we want a \\\"play\\\" button that will be later connected to\na slider through the `button_play` function. The slider itself will\nrepresent the \\\"resolution\\\" of the model we will render, a text field\nwhere the value of the \\\"resolution\\\" will be displayed. We will also\nadd a dropdown menu to toggle the visibility of the model. The dividers\nare the same as already used to divide and organize the toolbar.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "def custom_tools():\n    divider(vertical=True, classes='mx-1')\n    button(\n        click=button_play,\n        icon='mdi-play',\n        tooltip='Play',\n    )\n\n    slider(\n        model=(\"resolution\", 10),\n        tooltip=\"Resolution slider\",\n        min=3,\n        max=20,\n        step=1,\n        dense=True,\n        hide_details=True,\n        style=\"width: 300px\",\n        classes='my-0 py-0 ml-1 mr-1',\n    )\n    text_field(\n        model=(\"resolution\", 10),\n        tooltip=\"Resolution value\",\n        readonly=True,\n        type=\"number\",\n        dense=True,\n        hide_details=True,\n        style=\"min-width: 40px; width: 60px\",\n        classes='my-0 py-0 ml-1 mr-1',\n    )\n\n    divider(vertical=True, classes='mx-1')\n    select(\n        model=(\"visibility\", \"Show\"),\n        tooltip=\"Toggle visibility\",\n        items=['Visibility', [\"Hide\", \"Show\"]],\n        hide_details=True,\n        dense=True,\n    )"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The button callback function `button_play` needs to be created before\nstarting the server. This function will toggle the boolean state\nvariable `play` and flush the server, i.e. \\\"force\\\" the server to see\nthe change. We will see more on the state variables in a bit, but we\nneed to create the function here otherwise the server will complain\n`button_play` does not exist.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "def button_play():\n    state.play = not state.play\n    state.flush()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "We will do a simple rendering of a Cone using [ConeSouce]{.title-ref}.\n\nWhen using the `pl.show` method. The function we created `custom_tools`\nshould be passed as a `jupyter_kwargs` argument under the key\n`add_menu_items`.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "pl = pv.Plotter(notebook=True)\nalgo = pv.ConeSource()\nmesh_actor = pl.add_mesh(algo)\n\nwidget = pl.show(jupyter_kwargs=dict(add_menu_items=custom_tools), return_viewer=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "To interact with `trame`\\'s server we need to get the server\\'s state.\n\nWe initialize the `play` variable in the shared state and this will be\ncontrolled by the play button we created. Note that when creating the\n`slider`, the `text_field` and the `select` tools, we passed something\nlike\n`model=(\"variable\", value). This will automatically create the variable \"variable\" with value`value`in the server's shared state, so we do not need to create`state.resolution`or`state.visibility\\`\\`.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "state, ctrl = widget.viewer.server.state, widget.viewer.server.controller\nstate.play = False\nctrl.view_update = widget.viewer.update"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Now we can create the callback functions for our menu items.\n\nThe functions are decorated with a `state.change(\"variable\")`. This\nmeans they will be called when this specific variable has its value\nchanged in the server\\'s shared state. When `resolution` changes, we\nwant to update the resolution of our cone algorithm. When `visibility`\nchanges, we want to toggle the visibility of our cone.\n\nThe `play` variable is a little bit trickier. We want to start something\nlike a timer so that an animation can be set to play. To do that with\n`trame` we need to have an asynchronous function so we can continue to\ndo stuff while the \\\"timer\\\" function is running. The `_play` function\nwill be called when the `play` variable is changed (when we click the\nplay button, through the `button_play` callback). While `state.play` is\n`True` we want to play the animation. We change the `state.resolution`\nvalue, but to really call the `update_resolution` function we need to\n`flush` the server and force it to see the change in the shared\nvariables. When `state.play` changes to `False`, the animation stops.\n\nNote that using `while play: ...` would not work here because it is not\nthe actual state variable, but only an argument value passed to the\ncallback function.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "# trame callbacks\n@state.change(\"play\")\nasync def _play(play, **kwargs):\n    while state.play:\n        state.resolution += 1\n        state.flush()\n        if state.resolution >= 20:\n            state.play = False\n        await asyncio.sleep(0.3)\n\n\n@state.change(\"resolution\")\ndef update_resolution(resolution, **kwargs):\n    algo.resolution = resolution\n    ctrl.view_update()\n\n\n@state.change(\"visibility\")\ndef set_visibility(visibility, **kwargs):\n    toggle = {\"Hide\": 0, \"Show\": 1}\n    mesh_actor.visibility = toggle[visibility]\n    ctrl.view_update()\n\n\nwidget"
      ]
    }
  ],
  "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.12.2"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}