├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── demo ├── Dockerfile ├── README.md └── demo.py ├── docs ├── .requirements.txt ├── Makefile ├── _static │ └── overview.svg ├── about.rst ├── app │ ├── api.rst │ └── index.rst ├── cli.rst ├── conf.py ├── configuration.rst ├── contributing.rst ├── event │ ├── api.rst │ └── index.rst ├── eventsystembackground.rst ├── examples │ └── README.md ├── freeze.rst ├── guide │ ├── assets_data.rst │ ├── debugging.rst │ ├── event_system.rst │ ├── index.rst │ ├── notebooks.rst │ ├── patterns.rst │ ├── pscript_modules_scope.rst │ ├── reactions.rst │ ├── running.rst │ ├── widget_basics.rst │ └── widgets_components.rst ├── index.rst ├── make.bat ├── motivation.rst ├── overview.rst ├── reference.rst ├── releasenotes.rst ├── scripts │ ├── flexxdocsgen.py │ ├── gencommonast.py │ ├── genexamples.py │ ├── genuiclasses.py │ └── uiexample.py ├── start.rst ├── ui │ └── index.rst └── util │ ├── config.rst │ ├── index.rst │ ├── logging.rst │ └── minify.rst ├── flexx ├── README.md ├── __init__.py ├── __main__.py ├── _config.py ├── app │ ├── __init__.py │ ├── _app.py │ ├── _asset.py │ ├── _assetstore.py │ ├── _clientcore.py │ ├── _component2.py │ ├── _flaskhelpers.py │ ├── _flaskserver.py │ ├── _funcs.py │ ├── _modules.py │ ├── _server.py │ ├── _session.py │ ├── _tornadoserver.py │ ├── bsdf_lite.py │ ├── live_tester.py │ └── tests │ │ ├── test_app.py │ │ ├── test_asset.py │ │ ├── test_assetstore.py │ │ ├── test_component2.py │ │ ├── test_disposing2.py │ │ ├── test_dump_export.py │ │ ├── test_funcs.py │ │ ├── test_live.py │ │ ├── test_module.py │ │ └── test_session.py ├── event │ ├── __init__.py │ ├── _action.py │ ├── _attribute.py │ ├── _component.py │ ├── _dict.py │ ├── _emitter.py │ ├── _js.py │ ├── _loop.py │ ├── _property.py │ ├── _reaction.py │ ├── both_tester.py │ └── tests │ │ ├── test_actions.py │ │ ├── test_asyncio.py │ │ ├── test_bothtester.py │ │ ├── test_component.py │ │ ├── test_dict.py │ │ ├── test_disposing.py │ │ ├── test_emitters.py │ │ ├── test_loop.py │ │ ├── test_no_dups.py │ │ ├── test_properties.py │ │ ├── test_properties2.py │ │ ├── test_reactions.py │ │ ├── test_reactions_dynamism.py │ │ ├── test_reactions_fancy.py │ │ └── test_threading.py ├── flx.py ├── flx_flask.py ├── resources │ ├── README.md │ ├── bb64.js │ ├── bsdf.js │ ├── flexx.ico │ ├── flexx.png │ └── iconmaker.py ├── ui │ ├── README.md │ ├── __init__.py │ ├── _widget.py │ ├── layouts │ │ ├── __init__.py │ │ ├── _form.py │ │ ├── _grid.py │ │ ├── _hv.py │ │ ├── _layout.py │ │ ├── _pinboard.py │ │ ├── _stack.py │ │ └── _tabs.py │ ├── pywidgets │ │ ├── __init__.py │ │ └── _filebrowser.py │ └── widgets │ │ ├── __init__.py │ │ ├── _bokeh.py │ │ ├── _button.py │ │ ├── _canvas.py │ │ ├── _color.py │ │ ├── _dropdown.py │ │ ├── _group.py │ │ ├── _iframe.py │ │ ├── _label.py │ │ ├── _lineedit.py │ │ ├── _markdown.py │ │ ├── _media.py │ │ ├── _plotly.py │ │ ├── _plotwidget.py │ │ ├── _progressbar.py │ │ ├── _slider.py │ │ └── _tree.py └── util │ ├── __init__.py │ ├── config.py │ ├── freeze.py │ ├── getresource.py │ ├── logging.py │ ├── minify.py │ ├── screenshot.py │ ├── testing.py │ └── tests │ ├── test_cli.py │ ├── test_config.py │ ├── test_import.py │ └── test_logging.py ├── flexxamples ├── README.md ├── __init__.py ├── demos │ ├── __init__.py │ ├── app_layout.py │ ├── chatroom.py │ ├── circles.py │ ├── colab_painting.py │ ├── d3_collision.py │ ├── demo.py │ ├── drawing.py │ ├── mondriaan.py │ ├── monitor.py │ ├── plotly_gdp.py │ ├── sine.py │ ├── splines.py │ ├── themed_form.py │ ├── twente.py │ └── video_viewer.py ├── howtos │ ├── __init__.py │ ├── adding_handlers.py │ ├── array_props.py │ ├── basic_emit.py │ ├── bokehdemo.py │ ├── bootstrap.py │ ├── box_vs_fix_layout.py │ ├── buttons.py │ ├── control_with_keys.py │ ├── cookies.py │ ├── deep_event_connections.py │ ├── echarts_example.py │ ├── editor_ace.py │ ├── editor_cm.py │ ├── flask_backend.py │ ├── flask_server.py │ ├── hello_world.py │ ├── icons.py │ ├── jquery.py │ ├── leaflet.py │ ├── local_assets.py │ ├── mutual_dependent_props.py │ ├── oneliners.py │ ├── openlayers.py │ ├── python_in_js.py │ ├── python_side_widget2.py │ ├── react_to_props.py │ ├── redirect.py │ ├── scrollable.py │ ├── send_data.py │ ├── serve_data.py │ ├── serve_multiple1.py │ ├── serve_multiple2.py │ ├── serve_ssl.py │ ├── serve_with_aiohttp.py │ ├── serve_with_asgineer.py │ ├── serve_with_flask.py │ ├── splitters.py │ ├── static │ │ ├── css │ │ │ └── style.css │ │ └── js │ │ │ ├── data.json │ │ │ └── script.js │ ├── store.py │ ├── threaded.py │ └── tree.py ├── testers │ ├── __init__.py │ ├── benchmark.py │ ├── deep1.py │ ├── deep2.py │ ├── errors.py │ ├── find_prime.py │ ├── hv_layout.py │ ├── minsize.py │ ├── mouse_and_touch.py │ ├── tricky_events.py │ └── ws_speed.py └── ui_usage │ ├── dropdown_container.py │ ├── group.py │ ├── label.py │ ├── markdown.py │ ├── stack.py │ └── tabs.py ├── setup.cfg ├── setup.py └── tasks ├── README.md ├── __init__.py ├── __main__.py ├── _config.py ├── clean.py ├── copyright.py ├── demo.py ├── docs.py ├── help.py ├── pscript.py ├── test.py └── ws.py /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - 'v*' 9 | pull_request: 10 | branches: 11 | - master 12 | 13 | jobs: 14 | 15 | lint-build: 16 | name: Linting 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: false 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Set up Python 3.9 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: 3.9 26 | - name: Install dev dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install -U invoke pycodestyle flake8 30 | - name: Lint 31 | run: | 32 | invoke test --style; 33 | 34 | docs-build: 35 | name: Docs 36 | runs-on: ubuntu-latest 37 | strategy: 38 | fail-fast: false 39 | steps: 40 | - uses: actions/checkout@v2 41 | - name: Set up Python 3.9 42 | uses: actions/setup-python@v2 43 | with: 44 | python-version: 3.9 45 | - name: Install dev dependencies 46 | run: | 47 | python -m pip install --upgrade pip 48 | pip install -U invoke sphinx tornado pscript>=0.5.6 webruntime dialite; 49 | - name: Build docs 50 | run: | 51 | python -c 'import flexx.ui'; 52 | invoke docs --clean --build; 53 | 54 | test-builds: 55 | name: ${{ matrix.name }} 56 | runs-on: ${{ matrix.os }} 57 | strategy: 58 | fail-fast: false 59 | matrix: 60 | include: 61 | - name: Test Linux py35 62 | os: ubuntu-latest 63 | pyversion: '3.5' 64 | - name: Test Linux py36 65 | os: ubuntu-latest 66 | pyversion: '3.6' 67 | - name: Test Linux py37 68 | os: ubuntu-latest 69 | pyversion: '3.7' 70 | - name: Test Linux py38 71 | os: ubuntu-latest 72 | pyversion: '3.8' 73 | - name: Test Linux py39 74 | os: ubuntu-latest 75 | pyversion: '3.9' 76 | steps: 77 | - uses: actions/checkout@v2 78 | - name: Set up Python ${{ matrix.pyversion }} 79 | uses: actions/setup-python@v2 80 | with: 81 | python-version: ${{ matrix.pyversion }} 82 | - name: Install dev dependencies 83 | run: | 84 | python -m pip install --upgrade pip 85 | pip install -U tornado pscript>=0.5.6 webruntime dialite; 86 | pip install -U invoke pytest pytest-cov; 87 | - name: Unit tests 88 | uses: GabrielBB/xvfb-action@v1 89 | with: 90 | run: invoke test --unit 91 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # IDEs and such 6 | .idea 7 | .vscode 8 | 9 | # Specific to here 10 | exp/html_examples.txt 11 | flexx/resources/phosphor-all.*.js 12 | flexx/resources/phosphor-all.*.css 13 | _flexx-notebooks 14 | _flexx-book 15 | _feedstock 16 | flexx_legacy 17 | _phosphor-all 18 | examples/ui/*.html 19 | 20 | # C extensions 21 | *.so 22 | 23 | # Distribution / packaging 24 | .Python 25 | env/ 26 | bin/ 27 | build/ 28 | develop-eggs/ 29 | dist/ 30 | eggs/ 31 | parts/ 32 | sdist/ 33 | var/ 34 | *.egg-info/ 35 | .installed.cfg 36 | *.egg 37 | .ipynb_checkpoints 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .coverage 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | .pytest_cache 51 | 52 | # Translations 53 | *.mo 54 | 55 | # Mr Developer 56 | .mr.developer.cfg 57 | .project 58 | .pydevproject 59 | 60 | # Rope 61 | .ropeproject 62 | 63 | # Django stuff: 64 | *.log 65 | *.pot 66 | 67 | # Sphinx documentation and website 68 | docs/_build/ 69 | doc/_build/ 70 | _website/ 71 | _live/ 72 | _gh-pages/ 73 | 74 | # Eclipse 75 | .settings/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | First off, thank you for considering contributing to Flexx! Following 4 | these guidelines should help to streamline the communication between 5 | you and the developers. 6 | 7 | There are many ways to contribute, from writing tutorials or blog posts, 8 | improving the docs, submitting bug reports and feature requests or 9 | writing code to be included in Flexx itself. 10 | 11 | As there are currently no other channels for communication of support questions, 12 | these can be asked using Github issues (for now). For more formal support, you 13 | can contact almarklein via gmail. 14 | 15 | 16 | ## Ground Rules 17 | 18 | * Communicate respectfully. 19 | * Be welcoming to newcomers and encourage diverse new contributors from 20 | all backgrounds. See the [Python Community Code of 21 | Conduct](https://www.python.org/psf/codeofconduct/). 22 | * Keep in mind that the code must be cross platform (Window, OSX, Linux, at least). 23 | * In most cases, new code should be covered by tests 24 | * Please make sure that new code follows the style guides. Flexx intends 25 | to not be overly dogmatic about style (e.g. most whitespace-related 26 | rules are ignored) but style is enforced by tests. 27 | 28 | 29 | ## Your First Contribution 30 | 31 | If you're relatively new to Flexx, but would want to contribute, it is already 32 | helpful to just try it out and report any problems that you run into (e.g. things 33 | thare are unclear, counterintuitive or not documented well). Writing a 34 | blog post about something cool that you made with Flexx is also very 35 | helpful. 36 | 37 | At this point, we do not have a special label for "easy issues" to work on. 38 | 39 | Never contributed to open source before? Here are some useful reads: 40 | http://makeapullrequest.com/ and http://www.firsttimersonly.com/ 41 | 42 | Feel free to ask for help; everyone is a beginner at first :) 43 | 44 | 45 | ## How to report a bug 46 | 47 | If you find a security vulnerability, do NOT open an issue. Send 48 | almarklein an email via gmail instead. 49 | 50 | When filing an issue, make sure to answer these five questions: 51 | 52 | * What version of Flexx are you using? 53 | * What operating system and processor architecture are you using? 54 | * What browser are you using (if applicable)? 55 | * What did you do? 56 | * What did you expect to see? 57 | * What did you see instead? 58 | 59 | 60 | ## How to suggest a feature or enhancement 61 | 62 | Please first check whether the feature (or a very similar feature) has 63 | already been requested. If so, subscribe to that issue. Otherwise, 64 | create a new issue describing the proposed feature and why it is 65 | relevant (to you). 66 | 67 | 68 | ## Code review process 69 | 70 | Pull requests are not merged if they the tests do not pass. Though review 71 | may start before the pull request is completely finished. 72 | 73 | All pull requests are reviewed before they are merged. Reviewers may ask 74 | to clarify code or make suggestions to improve readability and style. 75 | Don't feel bad if your contribution does not go as smooth as you had 76 | hoped; we are gratefull with your contribution and the review 77 | process is there to ensure code quality. 78 | 79 | A reviewer may not always respond within a day; it might just be a busy 80 | week. If a reviewer has not responded in say a week or two, feel free 81 | to post a comment with a gentle reminder. 82 | 83 | 84 | ## Community 85 | 86 | All community traffic goes through Github for now. 87 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2020, Flexx developers 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE README.md 2 | recursive-include docs * 3 | recursive-include demo * 4 | recursive-include flexx README.md 5 | 6 | prune docs/_build 7 | prune docs/ext/__pycache__ 8 | 9 | recursive-include tasks * 10 | recursive-include flexx/app/tests * 11 | recursive-include flexx/event/tests * 12 | recursive-include flexx/util/tests * 13 | 14 | global-exclude .git* 15 | global-exclude *.pyo 16 | global-exclude *.pyc 17 | -------------------------------------------------------------------------------- /demo/Dockerfile: -------------------------------------------------------------------------------- 1 | # mypaas.service = flexx.demo 2 | # 3 | # mypaas.url = https://demo.flexx.app 4 | # mypaas.url = https://demo1.flexx.app 5 | # 6 | # mypaas.scale = 0 7 | # mypaas.maxmem = 256m 8 | 9 | FROM ubuntu:20.04 10 | 11 | RUN apt update \ 12 | && apt install -y python3-pip \ 13 | && pip3 --no-cache-dir install pip --upgrade \ 14 | && pip3 --no-cache-dir install psutil markdown tornado 15 | 16 | WORKDIR /root 17 | COPY . . 18 | 19 | RUN pip3 --no-cache-dir install dialite webruntime pscript \ 20 | && pip3 --no-cache-dir install https://github.com/flexxui/flexx/archive/master.zip 21 | 22 | CMD ["python3", "demo.py", "--flexx-hostname=0.0.0.0", "--flexx-port=80"] 23 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # Flexx demo 2 | 3 | This directory contains a simple demo server script, and a Dockerfile to deploy it as a server. 4 | -------------------------------------------------------------------------------- /demo/demo.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demo server. 3 | """ 4 | 5 | import sys 6 | import asyncio 7 | 8 | from flexx import app 9 | 10 | from flexxamples.demos.monitor import Monitor 11 | from flexxamples.demos.chatroom import ChatRoom 12 | from flexxamples.demos.demo import Demo 13 | from flexxamples.demos.colab_painting import ColabPainting 14 | from flexxamples.demos.d3_collision import CollisionDemo 15 | from flexxamples.demos.plotly_gdp import PlotlyGeoDemo 16 | 17 | 18 | async def exit_server_after_a_while(): 19 | # Exit the server after 12 hours, after which it will start up again 20 | # (using Docker with auto-restart). This makes sure that the server 21 | # is not bother too much in case we have a memory leak. 22 | await asyncio.sleep(12 * 3600) 23 | sys.exit() 24 | 25 | 26 | asyncio.ensure_future(exit_server_after_a_while()) 27 | 28 | 29 | if __name__ == "__main__": 30 | # This example is setup as a server app 31 | # app.serve(Monitor) 32 | # app.serve(ChatRoom) 33 | app.serve(ColabPainting) 34 | app.serve(CollisionDemo) 35 | # app.serve(PlotlyGeoDemo) # CORS fail? 36 | app.serve(Demo) 37 | app.start() 38 | -------------------------------------------------------------------------------- /docs/.requirements.txt: -------------------------------------------------------------------------------- 1 | sphinxcontrib-napoleon 2 | tornado 3 | pscript 4 | webruntime 5 | dialite 6 | -------------------------------------------------------------------------------- /docs/about.rst: -------------------------------------------------------------------------------- 1 | About 2 | ===== 3 | 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | 8 | releasenotes 9 | contributing 10 | freeze 11 | overview 12 | motivation 13 | eventsystembackground 14 | -------------------------------------------------------------------------------- /docs/app/api.rst: -------------------------------------------------------------------------------- 1 | App reference 2 | ============= 3 | 4 | Functions related to the event loop 5 | ----------------------------------- 6 | 7 | 8 | .. autofunction:: flexx.app.start 9 | 10 | .. autofunction:: flexx.app.run 11 | 12 | .. autofunction:: flexx.app.stop 13 | 14 | .. autofunction:: flexx.app.init_notebook 15 | 16 | .. autofunction:: flexx.app.create_server 17 | 18 | .. autofunction:: flexx.app.current_server 19 | 20 | 21 | Wrapping a Component into an Application 22 | ---------------------------------------- 23 | 24 | .. autoclass:: flexx.app.App 25 | :members: 26 | 27 | .. autofunction:: flexx.app.serve 28 | 29 | .. autofunction:: flexx.app.launch 30 | 31 | .. autofunction:: flexx.app.export 32 | 33 | 34 | The Component classes 35 | --------------------- 36 | 37 | .. autoclass:: flexx.app.BaseAppComponent 38 | :members: 39 | 40 | .. autoclass:: flexx.app.PyComponent 41 | :members: 42 | 43 | .. autoclass:: flexx.app.JsComponent 44 | :members: 45 | 46 | .. autoclass:: flexx.app.StubComponent 47 | :members: 48 | 49 | .. autofunction:: flexx.app.get_component_classes 50 | 51 | 52 | .. autoclass:: flexx.app.LocalProperty 53 | :members: 54 | 55 | 56 | Session and Assets 57 | ------------------ 58 | 59 | An asset is represented using an ``Asset`` object that defines its sources, 60 | dependencies, etc. Assets can be specific to the session or shared across sessions. 61 | The ``AssetStore`` provides all shared assets for clients connected to the current 62 | process. The global store is at ``flexx.app.assets``. 63 | The session object handles the connection between Python and JavaScript, 64 | and it allows adding client-side assets, which for instance makes it 65 | easy to create extensions based on existing JS libraries. 66 | 67 | 68 | 69 | .. autoclass:: flexx.app.Asset 70 | :members: 71 | 72 | 73 | .. autoclass:: flexx.app._assetstore.AssetStore 74 | :members: 75 | 76 | .. autoclass:: flexx.app.Session 77 | :inherited-members: 78 | :members: 79 | -------------------------------------------------------------------------------- /docs/app/index.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | --------- 4 | flexx.app 5 | --------- 6 | 7 | The app module builds upont the ``event`` module to provide 8 | :class:`PyComponent ` and 9 | :class:`JsComponent `, and handles the connection 10 | between Python and a browser. 11 | 12 | .. toctree:: 13 | :maxdepth: 2 14 | 15 | api 16 | -------------------------------------------------------------------------------- /docs/cli.rst: -------------------------------------------------------------------------------- 1 | ---------------------- 2 | Command line interface 3 | ---------------------- 4 | 5 | .. automodule:: flexx.__main__ 6 | -------------------------------------------------------------------------------- /docs/configuration.rst: -------------------------------------------------------------------------------- 1 | ----------------- 2 | Configuring Flexx 3 | ----------------- 4 | 5 | This page lists the configuration options available to Flexx, implemented 6 | via the :class:`Config ` class. Configuration 7 | options are read from ``/.flexx.cfg`` (check 8 | ``flexx.util.config.appdata_dir()`` for the actual location), 9 | and can also be set using 10 | environment variables and command line arguments, as explained below. 11 | Alternatively, options can be set directly in Python via 12 | ``flexx.config.foo = 3``. 13 | 14 | .. autodata:: flexx.config 15 | :annotation: 16 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing to Flexx 2 | --------------------- 3 | 4 | If you like Flexx and want to help out, there's a few things you can do. 5 | First off, feedback; it's very useful to hear what you like and what you 6 | struggle with. Feel free to use the 7 | `Github issue tracker `_ for this. 8 | 9 | You can help by contributing to the documentation, and/or by creating nice 10 | examples. 11 | 12 | If you would like to help out with the codebase itself, e.g. by fixing bugs, 13 | making improvements, or submitting new types of widgets, you probably need 14 | to a few extra libraries (don't worry, it's 'nothing fancy): 15 | 16 | * pytest and pytest-cov (get them via conda or pip) 17 | * flake8 (get it via conda or pip) 18 | * Nodejs 19 | * Firefox 20 | 21 | We follow the Github workflow: 22 | 23 | * You fork the `Github repo `_ and make a clone 24 | to your local computer. 25 | * You update to the latest version: ``git checkout master && git pull`` 26 | * You make a new branch: ``git checkout -b mywork`` 27 | * You make changes to the code and commit these: ``git commit`` 28 | * You push to your fork ``git push`` 29 | * Create a Pull Request from the Github interface. 30 | -------------------------------------------------------------------------------- /docs/event/api.rst: -------------------------------------------------------------------------------- 1 | Event reference 2 | =============== 3 | 4 | 5 | Component 6 | --------- 7 | 8 | .. autoclass:: flexx.event.Component 9 | :members: 10 | :private-members: 11 | 12 | 13 | .. autofunction:: flexx.event.mutate_array 14 | 15 | .. autofunction:: flexx.event.mutate_dict 16 | 17 | 18 | Attributes 19 | ---------- 20 | 21 | .. autoclass:: flexx.event.Attribute 22 | :members: 23 | 24 | Properties 25 | ---------- 26 | 27 | .. autoclass:: flexx.event.Property 28 | 29 | .. autoclass:: flexx.event.AnyProp 30 | 31 | .. autoclass:: flexx.event.BoolProp 32 | 33 | .. autoclass:: flexx.event.TriStateProp 34 | 35 | .. autoclass:: flexx.event.IntProp 36 | 37 | .. autoclass:: flexx.event.FloatProp 38 | 39 | .. autoclass:: flexx.event.StringProp 40 | 41 | .. autoclass:: flexx.event.TupleProp 42 | 43 | .. autoclass:: flexx.event.ListProp 44 | 45 | .. autoclass:: flexx.event.DictProp 46 | 47 | .. autoclass:: flexx.event.ComponentProp 48 | 49 | .. autoclass:: flexx.event.FloatPairProp 50 | 51 | .. autoclass:: flexx.event.EnumProp 52 | 53 | .. autoclass:: flexx.event.ColorProp 54 | 55 | 56 | Actions 57 | ------- 58 | 59 | .. autofunction:: flexx.event.action 60 | 61 | .. autoclass:: flexx.event.Action 62 | :members: 63 | 64 | Reactions 65 | --------- 66 | 67 | .. autofunction:: flexx.event.reaction 68 | 69 | .. autoclass:: flexx.event.Reaction 70 | :members: 71 | 72 | Emitter 73 | ------- 74 | 75 | .. autofunction:: flexx.event.emitter 76 | 77 | .. autoclass:: flexx.event.Emitter 78 | :members: 79 | 80 | 81 | Dict 82 | ---- 83 | 84 | .. autoclass:: flexx.event.Dict 85 | :members: 86 | 87 | loop 88 | ---- 89 | 90 | .. autoclass:: flexx.event.Loop 91 | :members: 92 | -------------------------------------------------------------------------------- /docs/event/index.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | ----------- 4 | flexx.event 5 | ----------- 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | 10 | api 11 | -------------------------------------------------------------------------------- /docs/examples/README.md: -------------------------------------------------------------------------------- 1 | This is where all the autogenerated examples go. 2 | -------------------------------------------------------------------------------- /docs/freeze.rst: -------------------------------------------------------------------------------- 1 | Freezing Flexx apps 2 | ------------------- 3 | 4 | Flexx needs special care when freezing, because it needs access to the Python 5 | source code in order to compile it to JavaScript. 6 | 7 | The easy way 8 | ============ 9 | 10 | There is experimental support to make freezing very easy: 11 | 12 | .. code-block:: py 13 | 14 | from flexx import flx 15 | 16 | class Example(flx.Widget): 17 | def init(self): 18 | flx.Button(text="Hi there") 19 | 20 | if __name__ == "__main__": 21 | app = flx.App(Example) 22 | app.freeze("~/Desktop/flexx_apps") 23 | 24 | 25 | The more explicit way 26 | ===================== 27 | 28 | The above approach does most of the magic behind the scenes. For more control, 29 | you can also use a more explicit approach. 30 | 31 | First, create a script that represents your application entry point. It 32 | is important that this script does not define any new Flexx widgets. It 33 | should look something like this: 34 | 35 | .. code-block:: py 36 | 37 | # Install hook so we we can import modules from source when frozen. 38 | from flexx.util import freeze 39 | freeze.install() 40 | 41 | # Run your app as usual 42 | from flexx import flx 43 | from my_module import MyWidget 44 | app = flx.App(MyWidget) 45 | app.launch("firefox-app") 46 | flx.run() 47 | 48 | 49 | Next, use ``PyInstaller`` as usual to create an app directory. Consider 50 | using ``--exclude-module numpy`` (PyInstaller thinks that Flexx needs Numpy, but this not the case). 51 | 52 | Next, copy the source code of the modules that define Flexx widgets. If you 53 | run PyInstaller from a script (using ``PyInstaller.__main__.run()``) then you 54 | can combine it. 55 | 56 | .. code-block:: py 57 | 58 | from flexx.util import freeze 59 | 60 | freeze.copy_module("flexx", app_dir) # always 61 | freeze.copy_module("flexxamples", app_dir) # used something from here? 62 | freeze.copy_module("my_module", app_dir) 63 | 64 | That should be it! 65 | -------------------------------------------------------------------------------- /docs/guide/assets_data.rst: -------------------------------------------------------------------------------- 1 | ------------------------ 2 | Handling assets and data 3 | ------------------------ 4 | 5 | Asset management 6 | ---------------- 7 | 8 | When writing code that relies on a certain JS or CSS library, that library 9 | can be loaded in the client by associating it with the module that needs it. 10 | Flexx will then automatically (and only) load it when code from that module is used in JS. 11 | Flexx itself uses this mechanism is some widgets e.g. for Leaflet maps or the CodeMirror editor. 12 | 13 | 14 | .. code-block:: py 15 | 16 | # Associate asset 17 | flx.assets.associate_asset(__name__, 'mydep.js', js_code) 18 | 19 | # Sometimes a more lightweight *remote* asset is prefered 20 | flx.assets.associate_asset(__name__, 'http://some.cdn/lib.css') 21 | 22 | # Create component (or Widget) that needs the asset at the client 23 | class MyComponent(flx.JsComponent): 24 | .... 25 | 26 | It is also possible to provide assets that are not automatically loaded 27 | on the main app page, e.g. for sub-pages or web workers: 28 | 29 | .. code-block:: py 30 | 31 | # Register asset 32 | asset_url = flx.assets.add_shared_asset('mydep.js', js_code) 33 | 34 | 35 | Data management 36 | --------------- 37 | 38 | Data can be provided per session or shared between sessions: 39 | 40 | .. code-block:: py 41 | 42 | # Add session-specific data. You need to call this inside a PyComponent 43 | # and use the link in the JS component that needs the data. 44 | link = my_component.session.add_data('some_name.png', binary_blob) 45 | 46 | # Add shared data. This can be called at the module level. 47 | link = flx.assets.add_shared_data('some_name.png', binary_blob) 48 | 49 | Note that it is also possible to send data from Python to JS via an 50 | action invokation (the data is send over the websocket in this case). 51 | The latter also works for numpy arrays. 52 | 53 | Next 54 | ---- 55 | 56 | Next up: :doc:`Sensible usage patterns `. 57 | -------------------------------------------------------------------------------- /docs/guide/debugging.rst: -------------------------------------------------------------------------------- 1 | --------- 2 | Debugging 3 | --------- 4 | 5 | Debugging can be hard. Especially if your app runs partly in Python and partly 6 | in JavaScript. Here are some tips that may help. 7 | 8 | 9 | Be clear about where the offending code is running 10 | -------------------------------------------------- 11 | 12 | This may sound obvious, but it's important to do this before moving on. 13 | Sometimes the bug presents itself due to the interaction between Python 14 | and JavaScript. The same rules apply, but you'd have to dig into both ends. 15 | 16 | 17 | Digging in the Python side 18 | -------------------------- 19 | 20 | All the normal Python debugging tips apply here. GUI applications run in 21 | an event loop, which makes debugging harder. E.g. using breakpoints is not 22 | always possible. A strategically placed ``print()`` can sometimes help a lot. 23 | 24 | It can be worthwhile to run the app in an IDE that can integrate the 25 | event loop, so that you can use a Python REPL to inspect an application 26 | while it is running. E.g. with `Pyzo `_ with asyncio GUI integration. 27 | 28 | 29 | Digging in the JavaScript side 30 | ------------------------------ 31 | 32 | People unfamiliar with web technology might be hesitant to try and debug 33 | using the browser, but you'll be amazed by how awesome the debugging tools 34 | of Firefox and Chrome are! 35 | 36 | Firstly, hit the F12 key to pop up the developer console. From here, there 37 | are a few things that you can do: 38 | 39 | You can run JavaScript commands to inspect and control your app. There 40 | is a global ``flexx`` object, and you can get access to all components 41 | using ``flexx.s1.instances.xxx``. Use autocompletion to select the 42 | component you need. You can inspect the component's properties and 43 | invoke its actions. 44 | 45 | If the problem is related to appearance, you can activate the element selector 46 | and then click on the element on the page to select it. This will allow you 47 | to inspect the HTML DOM structure and inspect the CSS of all elements. 48 | 49 | 50 | End 51 | --- 52 | 53 | This concluses the Flexx guide. Have fun! 54 | -------------------------------------------------------------------------------- /docs/guide/index.rst: -------------------------------------------------------------------------------- 1 | Guide 2 | ===== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | widget_basics 8 | widgets_components 9 | event_system 10 | reactions 11 | pscript_modules_scope 12 | assets_data 13 | patterns 14 | running 15 | debugging 16 | -------------------------------------------------------------------------------- /docs/guide/notebooks.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | Notebook tutorials 4 | ------------------ 5 | 6 | * `Using the webruntime module `_ 7 | * `The basics of PScript `_ 8 | * `The basics of the event system `_ 9 | * `The basics of flexx.app `_ 10 | * `Basic GUI building `_ 11 | 12 | (Note that these `do not work in JupyterLab `_ 13 | at the moment.) 14 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Flexx documentation master file, created by 2 | sphinx-quickstart on Fri Apr 10 15:35:18 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Flexx's documentation! 7 | ================================= 8 | 9 | .. automodule:: flexx 10 | 11 | 12 | Contents 13 | -------- 14 | 15 | .. toctree:: 16 | :maxdepth: 2 17 | 18 | start 19 | guide/index 20 | reference 21 | examples/index 22 | about 23 | 24 | 25 | Indices and tables 26 | ------------------ 27 | 28 | * :ref:`genindex` 29 | * :ref:`modindex` 30 | * :ref:`search` 31 | -------------------------------------------------------------------------------- /docs/motivation.rst: -------------------------------------------------------------------------------- 1 | Motivation 2 | ---------- 3 | 4 | The primary motivation for Flexx is the undeniable fact that the web 5 | (i.e. browser technology) has become an increasingly popular method for 6 | delivering applications to users, also for (interactive) scientific 7 | content. 8 | 9 | The purpose of Flexx is to provide a single application framework to 10 | create desktop applications and web apps. By making use of browser 11 | technology, the library itself can be relatively small and pure Python, 12 | making it widely and easily available. 13 | 14 | By making use of PScript (Python to JavaScript translation), the entire 15 | library is written without (hardly) a line of JavaScript. This makes it easier 16 | to develop than if we would have a corresponding "flexx.js" to maintain. 17 | Further, it allows users to easily define callback methods that are 18 | executed in JavaScript, allowing for higher performance when needed. 19 | 20 | Libraries written for Python, but not *in* Python have a much harder 21 | time to survive, because users don't easily become contributors. This 22 | is one of the reasons of the success of e.g. scikit-image, while e.g. Mayavi 23 | has a much harder time attracting developers. Since Flexx is written 24 | in a combination of Python and PScript, its user community is more 25 | likely to take an active role in its development. 26 | -------------------------------------------------------------------------------- /docs/overview.rst: -------------------------------------------------------------------------------- 1 | Flexx overview 2 | -------------- 3 | 4 | .. image:: _static/overview.svg 5 | 6 | The image above outlines the structure of Flexx. 7 | The *event* module provides a powerful property and event system that 8 | makes it easy to connect different parts of your application. Central to 9 | the event system is the ``Component`` class. 10 | The *app* module runs the server to which the web runtime connects (via a 11 | websocket). Further, it extends the ``Component`` class into the 12 | ``PyComponent`` and ``JsComponent`` classes. Objects of these classes 13 | live in Python and JavaScript respectively, but (can) have a representation 14 | on the other side, from which properties can be accessed, and actions be invoked. 15 | The *ui* module defines all widgets (based on ``JsComponent``). 16 | 17 | The external *webruntime* package is used to launch a browser looking like 18 | a dektop app. The *pscript* library is used throughout Flexx to compile 19 | Python code to JavaScript. 20 | -------------------------------------------------------------------------------- /docs/reference.rst: -------------------------------------------------------------------------------- 1 | Reference 2 | ========= 3 | 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | 8 | ui/api 9 | app/api 10 | event/api 11 | util/index 12 | cli 13 | configuration -------------------------------------------------------------------------------- /docs/scripts/flexxdocsgen.py: -------------------------------------------------------------------------------- 1 | """ Run scripts to generate docs for Flexx 2 | """ 3 | 4 | #import examplesgenerator 5 | import genuiclasses 6 | import genexamples 7 | import gencommonast 8 | 9 | 10 | def init(): 11 | print('GENERATING DOCS ...') 12 | 13 | print(' Generating docs for UI classes.') 14 | genuiclasses.main() 15 | print(' Generating examples.') 16 | genexamples.main() 17 | 18 | 19 | def clean(app, *args): 20 | genuiclasses.clean() 21 | genexamples.clean() 22 | 23 | 24 | def setup(app): 25 | init() 26 | app.connect('build-finished', clean) 27 | 28 | -------------------------------------------------------------------------------- /docs/scripts/gencommonast.py: -------------------------------------------------------------------------------- 1 | """ Generate docs for commonast. 2 | """ 3 | 4 | import os 5 | import sys 6 | from types import ModuleType 7 | from pscript import commonast 8 | 9 | # Hack 10 | sys.modules['commonast'] = commonast 11 | 12 | THIS_DIR = os.path.dirname(os.path.abspath(__file__)) 13 | DOC_DIR = os.path.abspath(os.path.join(THIS_DIR, '..')) 14 | OUTPUT_DIR = os.path.join(DOC_DIR, 'pscript') 15 | 16 | created_files = [] 17 | 18 | def main(): 19 | 20 | # Create overview doc page 21 | docs = 'Common AST' 22 | docs += '\n' + '=' * len(docs) + '\n\n' 23 | docs += '.. automodule:: commonast\n\n' 24 | docs += '.. autofunction:: commonast.parse\n\n' 25 | 26 | docs += '----\n\n' 27 | docs += 'The nodes\n---------\n\n' 28 | 29 | docs += '.. autoclass:: commonast.Node\n :members:\n\n' 30 | 31 | code = open(commonast.__file__, 'rb').read().decode() 32 | status = 0 33 | for line in code.splitlines(): 34 | if status == 0: 35 | if line.startswith('## --'): 36 | status = 1 37 | elif status == 1: 38 | if line.startswith('## --'): 39 | break 40 | elif line.startswith('## '): 41 | title = line[3:].strip() 42 | docs += '%s\n%s\n\n' % (title, '-' * len(title)) 43 | elif line.startswith('class '): 44 | clsname = line[6:].split('(')[0] 45 | docs += '.. autoclass:: %s\n\n' % ('commonast.' + clsname) 46 | cls = getattr(commonast, clsname) 47 | #cls.__doc__ = '%s(%s)\n%s' % (clsname, ', '.join(cls.__slots__), cls.__doc__) 48 | cls.__doc__ = '%s()\n%s' % (clsname, cls.__doc__) 49 | 50 | # Write overview doc page 51 | filename = os.path.join(OUTPUT_DIR, 'commonast.rst') 52 | created_files.append(filename) 53 | open(filename, 'wt', encoding='utf-8').write(docs) 54 | 55 | print(' generated commonast page') 56 | 57 | 58 | def clean(): 59 | while created_files: 60 | filename = created_files.pop() 61 | if os.path.isfile(filename): 62 | os.remove(filename) 63 | -------------------------------------------------------------------------------- /docs/start.rst: -------------------------------------------------------------------------------- 1 | --------------- 2 | Getting started 3 | --------------- 4 | 5 | Installation 6 | ------------ 7 | 8 | * ``pip install flexx`` 9 | * ``conda install flexx -c conda-forge`` 10 | * Old school: ``python setup.py install`` 11 | * Clone the repo and add the root dir to your PYTHONPATH (developer mode) 12 | 13 | When using Pip or Conda, the dependencies will be installed automatically. 14 | 15 | 16 | Dependencies 17 | ------------ 18 | 19 | Being pure Python and cross platform, Flexx should work (almost) 20 | anywhere where there's Python and a browser. Flexx is written for Python 21 | 3.5+ and also works on Pypy. 22 | 23 | Flexx further depends on the following packages (all are pure Python, 24 | and the latter three are projects under the flexxui umbrella): 25 | 26 | * `Tornado `_ 27 | * `PScript `_ 28 | * `Webruntime `_ 29 | * `Dialite `_ 30 | 31 | 32 | Supported browsers 33 | ------------------ 34 | 35 | Flexx aims to support all modern browsers, including Firefox, Chrome and Edge. 36 | Internet Explorer version 10 and up should work, but some things may be flaky. 37 | 38 | To run apps that look like desktop apps, we recommend having Firefox or nw.js installed. 39 | 40 | 41 | Current status 42 | -------------- 43 | 44 | Flexx is in beta status; some 45 | parts of the API may change, but we do care about backwards compatibility. 46 | -------------------------------------------------------------------------------- /docs/ui/index.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | -------- 4 | flexx.ui 5 | -------- 6 | 7 | The ui module provides a variety of :class:`widget ` 8 | classes based on :class:`JsComponent `. 9 | 10 | .. toctree:: 11 | :maxdepth: 1 12 | 13 | api 14 | -------------------------------------------------------------------------------- /docs/util/config.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | Configuration class 3 | =================== 4 | 5 | This page documents the Config class. For learning how to configure Flexx, 6 | see :func:`configuring flexx `. 7 | 8 | .. autoclass:: flexx.util.config.Config 9 | :members: 10 | -------------------------------------------------------------------------------- /docs/util/index.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | ========== 4 | flexx.util 5 | ========== 6 | 7 | Flexx' ``util`` module contains utilities that are used internally, 8 | some of which can be useful outside of Flexx. Note that most modules 9 | in ``flexx.util`` are independent; using them does not import any other 10 | Flexx modules. 11 | 12 | 13 | .. toctree:: 14 | :maxdepth: 1 15 | 16 | config 17 | logging 18 | minify 19 | -------------------------------------------------------------------------------- /docs/util/logging.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | Logging in Flexx 3 | ================ 4 | 5 | Flexx uses the standard Python logging facilities, but adds functionality, 6 | most notably the ability to filter messages by a string or regexp. 7 | 8 | .. autofunction:: flexx.set_log_level 9 | 10 | .. autoclass:: flexx.util.logging.capture_log 11 | -------------------------------------------------------------------------------- /docs/util/minify.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | JavaScript minification 3 | ======================= 4 | 5 | 6 | .. autofunction:: flexx.util.minify.minify 7 | -------------------------------------------------------------------------------- /flexx/README.md: -------------------------------------------------------------------------------- 1 | flexx package 2 | ------------- 3 | 4 | The root package for flexx.ui, flexx.app, flexx.event, 5 | and the combined flexx.flx namespace. 6 | -------------------------------------------------------------------------------- /flexx/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | `Flexx `_ is a pure Python toolkit for 3 | creating graphical user interfaces (GUI's), that uses web technology 4 | for its rendering. Apps are written purely in Python; The 5 | `PScript `_ transpiler generates the 6 | necessary JavaScript on the fly. 7 | 8 | You can use Flexx to create (cross platform) desktop applications, web 9 | applications, and export an app to a standalone HTML document. It also 10 | works in the Jupyter notebook. 11 | 12 | The docs are on `Readthedocs `_, 13 | the code is on `Github `_, 14 | and there is a `demo server `_. 15 | Once you've got started, the most important page is probably the 16 | `Widget reference `_. 17 | 18 | ---- 19 | 20 | For more information, see http://flexx.readthedocs.io. 21 | """ 22 | 23 | # NOTES ON DOCS: 24 | # There are 2 places that define the short summary of Flexx: the 25 | # __init__.py and the README.md. Their summaries should be kept equal. 26 | # The index.rst for the docs uses the summary from __init__.py (the 27 | # part after the "----" is stripped. The long-description for Pypi is 28 | # obtained by converting README.md to RST. 29 | 30 | __version__ = '0.8.4' 31 | 32 | # Assert compatibility 33 | import sys 34 | if sys.version_info < (3, 5): # pragma: no cover 35 | raise RuntimeError('Flexx needs at least Python 3.5') 36 | 37 | # Import config object 38 | from ._config import config # noqa 39 | from .util.logging import set_log_level # noqa 40 | set_log_level(config.log_level) 41 | 42 | del sys 43 | -------------------------------------------------------------------------------- /flexx/_config.py: -------------------------------------------------------------------------------- 1 | # fmt: off 2 | from .util.config import Config 3 | 4 | config = Config('flexx', '~appdata/.flexx.cfg', 5 | 6 | # General 7 | log_level=('info', str, 'The log level to use (DEBUG, INFO, WARNING, ERROR)'), 8 | browser_stacktrace=(True, bool, 'Show server stack traces in browser window'), 9 | 10 | # flexx.app 11 | hostname=('localhost', str, 'The default hostname to serve apps.'), 12 | port=(0, int, 'The default port to serve apps. Zero means auto-select.'), 13 | host_whitelist=('', str, 'Comma separated list of allowed : ' 14 | 'values to pass cross-origin checks.'), 15 | ws_timeout=(20, int, 'If the websocket is idle for this amount of seconds, ' 16 | 'it is closed.'), 17 | ssl_certfile=('', str, 'The cert file for https server.'), 18 | ssl_keyfile=('', str, 'The key file for https server.'), 19 | cookie_secret=('flexx_secret', str, 'The secret key to encode cookies.'), 20 | 21 | # flexx.webruntime 22 | webruntime=('', str, 'The default web runtime to use. ' 23 | 'Default is "app or browser".'), 24 | 25 | # tornado 26 | tornado_debug=('false', bool, 'Setting the tornado application debug flag ' 27 | 'allows autoreload and other debugging features.'), 28 | 29 | # flask 30 | flask_debug=('false', bool, 'Setting the flask application debug flag ' 31 | 'allows autoreload and other debugging features.'), 32 | 33 | ) 34 | -------------------------------------------------------------------------------- /flexx/app/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Flexx application system. 3 | 4 | The app module implements the connection between Python and JavaScript. 5 | It runs a web server and websocket server based on Tornado, provides 6 | an asset (and data) management system, and provides the PyComponent and 7 | JsComponent classes, which form the basis for e.g. Widgets. 8 | """ 9 | 10 | _DEV_NOTES = """ 11 | Overview of classes: 12 | 13 | * PyComponent and JsComponent: the base class for creating Python/JS component. 14 | * JSModule: represents a module in JS that corresponds to a Python module. 15 | * Asset: represents an asset. 16 | * Bundle: an Asset subclass to represent a collecton of JSModule's in one asset. 17 | * AssetStore: one instance of this class is used to provide all client 18 | assets in this process (JS, CSS, images, etc.). It also keeps track 19 | of modules. 20 | * SessionAssets: base class for Session that implements the assets/data part. 21 | * Session: object that handles connection between Python and JS. Has a 22 | websocket, and optionally a reference to the runtime. 23 | * WebSocket: tornado WS handler. 24 | * AppManager: keeps track of what apps are registered. Has functionality 25 | to instantiate apps and connect the websocket to them. 26 | * Server: handles http requests. Uses manager to create new app 27 | instances or get the page for a pending session. Hosts assets by using 28 | the global asset store. 29 | * Flexx class (in _clientcore.py): more or less the JS side of a session. 30 | 31 | """ 32 | 33 | import logging 34 | logger = logging.getLogger(__name__) 35 | del logging 36 | 37 | # flake8: noqa 38 | from ._app import App, manager 39 | from ._asset import Asset, Bundle 40 | from ._component2 import BaseAppComponent, LocalComponent, ProxyComponent 41 | from ._component2 import PyComponent, JsComponent, StubComponent 42 | from ._component2 import get_component_classes, LocalProperty 43 | 44 | from ._funcs import run, start, stop 45 | from ._funcs import init_notebook, serve, launch, export 46 | from ._server import create_server, current_server 47 | from ._session import Session 48 | from ._modules import JSModule 49 | from ._assetstore import assets 50 | from ._clientcore import serializer 51 | 52 | # Resolve cyclic dependencies, and explicit exports to help cx_Freeze 53 | # from . import _tornadoserver -- no, we don't want Tornado unless really needed 54 | from . import _component2 55 | _component2.manager = manager 56 | -------------------------------------------------------------------------------- /flexx/app/live_tester.py: -------------------------------------------------------------------------------- 1 | """ 2 | Logic to run live tests. 3 | """ 4 | 5 | import gc 6 | import sys 7 | import time 8 | import asyncio 9 | 10 | from flexx.event import loop 11 | from flexx import app 12 | 13 | from flexx.event.both_tester import FakeStream, smart_compare 14 | 15 | 16 | async def roundtrip(*sessions): 17 | """ Coroutine to await a roundtrip to all given sessions. 18 | """ 19 | ok = [] 20 | def up(): 21 | ok.append(1) 22 | for session in sessions: 23 | session.call_after_roundtrip(up) 24 | # timeout = time.time() + 5.0 25 | while len(ok) < len(sessions): 26 | await asyncio.sleep(0.02) 27 | loop.iter() 28 | 29 | 30 | def launch(cls, *args, **kwargs): 31 | """ Shorthand for app.launch() that also returns session. 32 | """ 33 | # from flexx import app 34 | c = app.App(cls, *args, **kwargs).launch('firefox-app') 35 | return c, c.session 36 | 37 | 38 | def filter_stdout(text): 39 | py_lines = [] 40 | js_lines = [] 41 | for line in text.strip().splitlines(): 42 | if 'JS: ' in line: 43 | js_lines.append(line.split('JS: ', 1)[1]) 44 | elif not line.startswith(('[I', '[D')): 45 | py_lines.append(line) 46 | return '\n'.join(py_lines), '\n'.join(js_lines) 47 | 48 | 49 | def run_live(func): 50 | """ Decorator to run a live test. 51 | """ 52 | 53 | def runner(): 54 | # Run with a fresh server and loop 55 | loop.reset() 56 | #asyncio_loop = asyncio.get_event_loop() 57 | asyncio_loop = asyncio.new_event_loop() 58 | app.create_server(port=0, loop=asyncio_loop) 59 | 60 | print('running', func.__name__, '...', end='') 61 | orig_stdout = sys.stdout 62 | orig_stderr = sys.stderr 63 | fake_stdout = FakeStream() 64 | sys.stdout = sys.stderr = fake_stdout 65 | t0 = time.time() 66 | try: 67 | # Call function - it could be a co-routine 68 | cr = func() 69 | if asyncio.iscoroutine(cr): 70 | asyncio_loop.run_until_complete(cr) 71 | gc.collect() 72 | finally: 73 | sys.stdout = orig_stdout 74 | sys.stderr = orig_stderr 75 | 76 | # Clean up / shut down 77 | print('done in %f seconds' % (time.time()-t0)) 78 | for appname in app.manager.get_app_names(): 79 | if 'default' not in appname: 80 | sessions = app.manager.get_connections(appname) 81 | for session in sessions: 82 | if session.app is not None: 83 | session.app.dispose() 84 | session.close() 85 | loop.reset() 86 | 87 | # Get reference text 88 | pyresult, jsresult = filter_stdout(fake_stdout.getvalue()) 89 | reference = '\n'.join(line[4:] for line in func.__doc__.splitlines()) 90 | parts = reference.split('-'*10) 91 | pyref = parts[0].strip(' \n') 92 | jsref = parts[-1].strip(' \n-') 93 | 94 | # Compare 95 | smart_compare(func, ('Python', pyresult, pyref), 96 | ('JavaScript', jsresult, jsref)) 97 | 98 | return runner 99 | -------------------------------------------------------------------------------- /flexx/app/tests/test_app.py: -------------------------------------------------------------------------------- 1 | from flexx.util.testing import run_tests_if_main, raises 2 | 3 | from flexx import app, event 4 | 5 | 6 | class MyPropClass1(app.PyComponent): 7 | foo = event.IntProp(1, settable=True) 8 | 9 | 10 | class MyPropClass2(MyPropClass1): 11 | def init(self, foo_val=11): 12 | self.set_foo(foo_val) 13 | 14 | 15 | def test_launching_with_props(): 16 | 17 | m = app.launch(MyPropClass1) 18 | assert m.foo == 1 19 | m.session.close() 20 | 21 | m = app.App(MyPropClass1, foo=3).launch() 22 | assert m.foo == 3 23 | m.session.close() 24 | 25 | 26 | def test_launching_with_init_args(): 27 | 28 | m = app.launch(MyPropClass2) 29 | event.loop.iter() 30 | assert m.foo == 11 31 | m.session.close() 32 | 33 | m = app.App(MyPropClass2, 13).launch() 34 | event.loop.iter() 35 | assert m.foo == 13 36 | m.session.close() 37 | 38 | 39 | run_tests_if_main() 40 | -------------------------------------------------------------------------------- /flexx/event/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Flexx event system. 3 | """ 4 | 5 | import logging 6 | logger = logging.getLogger(__name__) 7 | del logging 8 | 9 | import sys 10 | assert sys.version_info > (3, 5), "Flexx.event needs Python 3.5+" 11 | del sys 12 | 13 | # flake8: noqa 14 | from ._dict import Dict 15 | from ._loop import Loop, loop 16 | from ._action import Action, action 17 | from ._reaction import Reaction, reaction 18 | from ._emitter import emitter, Emitter 19 | from ._attribute import Attribute 20 | from ._property import * 21 | from ._component import Component, mutate_array, mutate_dict 22 | -------------------------------------------------------------------------------- /flexx/event/_attribute.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implements the attribute class. 3 | """ 4 | 5 | from ._action import BaseDescriptor 6 | 7 | 8 | class Attribute(BaseDescriptor): 9 | """ Attributes are (readonly, and usually static) values associated with 10 | Component classes. They expose and document a value without 11 | providing means of observing changes like ``Property`` does. (The 12 | actual value is taken from ``component._xx``, with "xx" the name 13 | of the attribute.) 14 | 15 | """ 16 | 17 | def __init__(self, doc=''): 18 | # Set doc 19 | if not isinstance(doc, str): 20 | raise TypeError('event.Attribute() doc must be a string.') 21 | self._doc = doc 22 | self._set_name('anonymous_attribute') 23 | 24 | def _set_name(self, name): 25 | self._name = name # or func.__name__ 26 | self.__doc__ = self._format_doc('attribute', name, self._doc) 27 | 28 | def __set__(self, instance, value): 29 | t = 'Cannot set attribute %r.' 30 | raise AttributeError(t % self._name) 31 | 32 | def __get__(self, instance, owner): 33 | if instance is None: 34 | return self 35 | return getattr(instance, '_' + self._name) 36 | -------------------------------------------------------------------------------- /flexx/event/_dict.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implementation of a dict class with attribute access. 3 | """ 4 | 5 | import re 6 | 7 | try: # pragma: no cover 8 | from collections import OrderedDict as _dict 9 | except ImportError: 10 | _dict = dict 11 | 12 | 13 | def isidentifier(s): 14 | # http://stackoverflow.com/questions/2544972/ 15 | if not isinstance(s, str): 16 | return False 17 | return re.match(r'^\w+$', s, re.UNICODE) and re.match(r'^[0-9]', s) is None 18 | 19 | 20 | class Dict(_dict): 21 | """ A dict in which the items can be get/set as attributes. 22 | 23 | This provides a lean way to represent structured data, and works 24 | well in combination with autocompletion. Keys can be anything that 25 | are otherwise valid keys, but keys that are not valid identifiers 26 | or that are methods of the dict class (e.g. 'items' or 'copy') 27 | can only be get/set in the classic way. 28 | 29 | Example: 30 | 31 | .. code-block:: python 32 | 33 | >> d = Dict(foo=3) 34 | >> d.foo 35 | 3 36 | >> d['foo'] = 4 37 | >> d.foo 38 | 4 39 | >> d.bar = 5 40 | >> d.bar 41 | 5 42 | 43 | """ 44 | 45 | __reserved_names__ = dir(_dict()) # Also from OrderedDict 46 | __pure_names__ = dir(dict()) 47 | 48 | __slots__ = [] 49 | 50 | def __repr__(self): 51 | identifier_items = [] 52 | nonidentifier_items = [] 53 | for key, val in self.items(): 54 | if isidentifier(key): 55 | identifier_items.append('%s=%r' % (key, val)) 56 | else: 57 | nonidentifier_items.append('(%r, %r)' % (key, val)) 58 | if nonidentifier_items: 59 | return 'Dict([%s], %s)' % (', '.join(nonidentifier_items), 60 | ', '.join(identifier_items)) 61 | else: 62 | return 'Dict(%s)' % (', '.join(identifier_items)) 63 | 64 | def __getattribute__(self, key): 65 | try: 66 | return object.__getattribute__(self, key) 67 | except AttributeError: 68 | if key in self: 69 | return self[key] 70 | else: 71 | raise 72 | 73 | def __setattr__(self, key, val): 74 | if key in Dict.__reserved_names__: 75 | # Either let OrderedDict do its work, or disallow 76 | if key not in Dict.__pure_names__: # pragma: no cover 77 | return _dict.__setattr__(self, key, val) 78 | else: 79 | raise AttributeError('Reserved name, this key can only ' + 80 | 'be set via ``d[%r] = X``' % key) 81 | else: 82 | # if isinstance(val, dict): val = Dict(val) -> no, makes a copy! 83 | self[key] = val 84 | 85 | def __dir__(self): 86 | names = [k for k in self.keys() if isidentifier(k)] 87 | return Dict.__reserved_names__ + names 88 | -------------------------------------------------------------------------------- /flexx/event/_emitter.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implements the emitter decorator, class and desciptor. 3 | """ 4 | 5 | import weakref 6 | 7 | from ._action import BaseDescriptor 8 | 9 | 10 | def emitter(func): 11 | """ Decorator to turn a method of a Component into an 12 | :class:`Emitter `. 13 | 14 | An emitter makes it easy to emit specific events, and is also a 15 | placeholder for documenting an event. 16 | 17 | .. code-block:: python 18 | 19 | class MyObject(event.Component): 20 | 21 | @emitter 22 | def spam(self, v): 23 | return dict(value=v) 24 | 25 | m = MyObject() 26 | m.spam(42) # emit the spam event 27 | 28 | The method being decorated can have any number of arguments, and 29 | should return a dictionary that represents the event to generate. 30 | The method's docstring is used as the emitter's docstring. 31 | """ 32 | if not callable(func): 33 | raise TypeError('The event.emitter() decorator needs a function.') 34 | if getattr(func, '__self__', None) is not None: # builtin funcs have __self__ 35 | raise TypeError('Invalid use of emitter decorator.') 36 | return EmitterDescriptor(func, func.__name__, func.__doc__) 37 | 38 | 39 | class EmitterDescriptor(BaseDescriptor): 40 | """ Placeholder for documentation and easy emitting of the event. 41 | """ 42 | 43 | def __init__(self, func, name, doc): 44 | self._func = func 45 | self._name = name 46 | self.__doc__ = self._format_doc('emitter', name, doc, func) 47 | 48 | def __get__(self, instance, owner): 49 | if instance is None: 50 | return self 51 | 52 | private_name = '_' + self._name + '_emitter' 53 | try: 54 | emitter = getattr(instance, private_name) 55 | except AttributeError: 56 | emitter = Emitter(instance, self._func, self._name, self.__doc__) 57 | setattr(instance, private_name, emitter) 58 | 59 | emitter._use_once(self._func) # make super() work, see _action.py 60 | return emitter 61 | 62 | 63 | class Emitter: 64 | """ Emitter objects are wrappers around Component methods. They take 65 | care of emitting an event when called and function as a placeholder 66 | for documenting an event. This class should not be instantiated 67 | directly; use ``event.emitter()`` instead. 68 | """ 69 | 70 | def __init__(self, ob, func, name, doc): 71 | assert callable(func) 72 | 73 | # Store func, name, and docstring (e.g. for sphinx docs) 74 | self._ob1 = weakref.ref(ob) 75 | self._func = func 76 | self._func_once = func 77 | self._name = name 78 | self.__doc__ = doc 79 | 80 | def __repr__(self): 81 | cname = self.__class__.__name__ 82 | return '<%s %r at 0x%x>' % (cname, self._name, id(self)) 83 | 84 | def _use_once(self, func): 85 | """ To support super(). 86 | """ 87 | self._func_once = func 88 | 89 | def __call__(self, *args): 90 | """ Emit the event. 91 | """ 92 | func = self._func_once 93 | self._func_once = self._func 94 | ob = self._ob1() 95 | if ob is not None: 96 | ev = func(ob, *args) 97 | if ev is not None: 98 | ob.emit(self._name, ev) 99 | -------------------------------------------------------------------------------- /flexx/event/tests/test_asyncio.py: -------------------------------------------------------------------------------- 1 | """ 2 | This just tests some assumptioms that we make about asyncio. These are 3 | mostly documented, but still good to see in action. 4 | """ 5 | 6 | import asyncio 7 | import threading 8 | 9 | 10 | ## 11 | 12 | def append_current_loop(container, make_new_loop=False): 13 | if make_new_loop: 14 | loop = asyncio.new_event_loop() 15 | asyncio.set_event_loop(loop) 16 | try: 17 | container.append(asyncio.get_event_loop()) 18 | except Exception as err: 19 | container.append(str(err)) 20 | 21 | 22 | def test_asyncio_thread1(): 23 | # Tests that asyncio.get_event_loop() returns a different loop instance 24 | # for each thread. 25 | 26 | r = [] 27 | r.append(asyncio.get_event_loop()) 28 | 29 | t = threading.Thread(target=append_current_loop, args=(r, False)) 30 | t.start() 31 | t.join() 32 | 33 | t = threading.Thread(target=append_current_loop, args=(r, True)) 34 | t.start() 35 | t.join() 36 | 37 | r.append(asyncio.get_event_loop()) 38 | 39 | assert len(r) == 4 40 | assert isinstance(r[1], str) and 'no current event loop in thread' in r[1] 41 | assert r[0] is not r[2] 42 | assert r[0] is r[3] 43 | 44 | return r 45 | 46 | 47 | ## 48 | 49 | 50 | def make_new_loop_and_run(): 51 | loop = asyncio.new_event_loop() 52 | loop.call_later(0.2, lambda: print('greeting 1 from thread', threading.current_thread().getName())) 53 | loop.call_later(0.7, lambda: print('greeting 2 from thread', threading.current_thread().getName())) 54 | loop.call_later(0.9, loop.stop) 55 | loop.run_forever() 56 | 57 | 58 | def test_asyncio_thread2(): 59 | # Run multiple loops in multiple threads at the same time. 60 | 61 | loop = asyncio.get_event_loop() 62 | assert not loop.is_running() 63 | 64 | tt = [] 65 | for i in range(5): 66 | t = threading.Thread(target=make_new_loop_and_run) 67 | tt.append(t) 68 | for t in tt: 69 | t.start() 70 | make_new_loop_and_run() 71 | for t in tt: 72 | t.join() 73 | 74 | 75 | if __name__ == '__main__': 76 | r = test_asyncio_thread1() 77 | -------------------------------------------------------------------------------- /flexx/event/tests/test_dict.py: -------------------------------------------------------------------------------- 1 | 2 | from flexx.util.testing import run_tests_if_main, skipif, skip, raises 3 | 4 | from flexx import event 5 | from flexx.event._dict import isidentifier 6 | 7 | 8 | def test_isidentifier(): 9 | 10 | for name in ['foo', 'bar', 'asdasdaskjdbf', 'Bar', 'FOO', # simple 11 | 'fóó', 'é', 'élé', # unicode 12 | '_', '_1', '_foo', 'f_', 'k_k', # with underscore 13 | 'f1', 'x0ff', # with numbers 14 | ]: 15 | assert isidentifier(name) 16 | 17 | for name in ['', '*', '(', '^', 'foo,', 'b&b', # empty string and strange chars 18 | ' ', ' 12', '', 'k k', ' foo', 'foo ', '\tf', 'f\t', ' _', # with whitespace 19 | '2', '42', '2foo', '2_', '123', '0xff', # numbers 20 | 3, None, [], # not a string 21 | ]: 22 | assert not isidentifier(name) 23 | 24 | 25 | def test_dict_ok(): 26 | 27 | d = event.Dict(foo=3) 28 | assert d.foo == 3 29 | assert d['foo'] == 3 30 | 31 | d.foo = 4 32 | assert d.foo == 4 33 | assert d['foo'] == 4 34 | 35 | d['foo'] = 5 36 | assert d.foo == 5 37 | assert d['foo'] == 5 38 | 39 | d._x = 9 40 | assert d._x == 9 41 | 42 | d.__x = 8 43 | assert d.__x == 8 44 | 45 | d.x0123 = 7 46 | assert d.x0123 == 7 47 | 48 | with raises(AttributeError): 49 | d.bladibla 50 | 51 | 52 | def test_dict_keys_that_are_methods(): 53 | 54 | d = event.Dict(foo=3) 55 | 56 | with raises(AttributeError): 57 | d.copy = 3 58 | d['copy'] = 3 59 | assert d.copy != 3 60 | assert d['copy'] == 3 61 | 62 | 63 | def test_dir_and_repr(): 64 | 65 | d = event.Dict(foo=3, bar=4) 66 | d.spam = 5 67 | d.eggs = 6 68 | 69 | names = dir(d) 70 | r = repr(d) 71 | for name in ['foo', 'bar', 'spam', 'eggs']: 72 | assert name in names 73 | assert ('%s=' % name) in r 74 | 75 | # Add a non-identifier element 76 | d[42] = None 77 | 78 | names = dir(d) 79 | r = repr(d) 80 | for name in ['foo', 'bar', 'spam', 'eggs']: 81 | assert name in names 82 | assert ('%s=' % name) in r 83 | 84 | assert '(42, None)' in r 85 | assert '42' not in names 86 | assert 42 not in names 87 | 88 | 89 | run_tests_if_main() 90 | -------------------------------------------------------------------------------- /flexx/event/tests/test_no_dups.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def test_that_tests_dont_have_multiple_functions_with_same_name(): 4 | dir = os.path.dirname(__file__) 5 | for fname in os.listdir(dir): 6 | if not (fname.startswith('test_') and fname.endswith('.py')): 7 | continue 8 | print(fname) 9 | text = open(os.path.join(dir, fname), 'rb').read().decode() 10 | func_names = set() 11 | 12 | for line in text.splitlines(): 13 | line = line.split('(')[0].strip() 14 | if line.startswith('def '): 15 | func_name = line[4:] 16 | if func_name.startswith('test_'): 17 | print(func_name) 18 | assert func_name not in func_names, (fname, func_name) 19 | func_names.add(func_name) 20 | 21 | 22 | if __name__ == '__main__': 23 | test_that_tests_dont_have_multiple_functions_with_same_name() 24 | -------------------------------------------------------------------------------- /flexx/event/tests/test_threading.py: -------------------------------------------------------------------------------- 1 | from flexx.util.testing import run_tests_if_main, raises 2 | 3 | import time 4 | import asyncio 5 | import threading 6 | 7 | from flexx import event 8 | 9 | 10 | def test_in_thread2(): 11 | """ Test running a Component object in another thread. 12 | """ 13 | res = [] 14 | 15 | class MyComp1(event.Component): 16 | foo = event.IntProp(0, settable=True) 17 | 18 | @event.reaction('foo') 19 | def on_foo(self, *events): 20 | for ev in events: 21 | res.append(ev.new_value) 22 | 23 | def main(): 24 | # Create fresh ioloop and make flexx use it 25 | # event.loop.reset() 26 | loop = asyncio.new_event_loop() 27 | event.loop.integrate(loop, reset=True) 28 | # Create component and manipulate prop 29 | component = MyComp1() 30 | component.set_foo(3) 31 | component.set_foo(4) 32 | # Run mainloop for one iterartion 33 | loop.call_later(0.2, loop.stop) 34 | loop.run_forever() 35 | 36 | t = threading.Thread(target=main) 37 | t.start() 38 | t.join() 39 | event.loop.integrate(reset=True) # restore 40 | 41 | assert res == [0, 3, 4] 42 | 43 | 44 | def test_in_thread3(): 45 | """ Test hotswapping the loop to another thread. 46 | """ 47 | res = [] 48 | 49 | class MyComp1(event.Component): 50 | foo = event.IntProp(0, settable=True) 51 | 52 | @event.reaction('foo') 53 | def on_foo(self, *events): 54 | for ev in events: 55 | res.append(ev.new_value) 56 | 57 | def main(): 58 | # Create fresh ioloop and make flexx use it 59 | # event.loop.reset() 60 | loop = asyncio.new_event_loop() 61 | event.loop.integrate(loop, reset=False) # no reset! 62 | # Run mainloop for one iterartion 63 | loop.call_later(0.2, loop.stop) 64 | loop.run_forever() 65 | 66 | # Create component and manipulate prop 67 | event.loop.reset() 68 | component = MyComp1() 69 | component.set_foo(3) 70 | component.set_foo(4) 71 | 72 | t = threading.Thread(target=main) 73 | t.start() 74 | t.join() 75 | event.loop.integrate(reset=True) # restore 76 | 77 | assert res == [0, 3, 4] 78 | 79 | 80 | def test_in_thread4(): 81 | """ Test invoking actions from another thread. 82 | """ 83 | res = [] 84 | 85 | class MyComp1(event.Component): 86 | foo = event.IntProp(0, settable=True) 87 | 88 | @event.reaction('foo') 89 | def on_foo(self, *events): 90 | for ev in events: 91 | res.append(ev.new_value) 92 | 93 | def main(): 94 | # Create fresh ioloop and make flexx use it 95 | # event.loop.reset() 96 | loop = asyncio.new_event_loop() 97 | event.loop.integrate(loop, reset=False) # no reset! 98 | # set foo 99 | component.set_foo(3) 100 | # Run mainloop for one iterartion 101 | loop.call_later(0.4, loop.stop) 102 | loop.run_forever() 103 | 104 | # Create component and manipulate prop 105 | event.loop.reset() 106 | component = MyComp1() 107 | 108 | t = threading.Thread(target=main) 109 | t.start() 110 | time.sleep(0.2) 111 | component.set_foo(4) # invoke from main thread 112 | t.join() 113 | event.loop.integrate(reset=True) # restore 114 | 115 | assert res == [0, 3, 4] 116 | 117 | 118 | run_tests_if_main() 119 | -------------------------------------------------------------------------------- /flexx/flx.py: -------------------------------------------------------------------------------- 1 | """ 2 | The flexx.flx module provides a namspace combining all the things from 3 | flexx.app, flexx.event, and flexx.ui. 4 | """ 5 | 6 | # flake8: noqa 7 | 8 | from . import __version__, config, set_log_level 9 | from .event import * 10 | from .app import * 11 | from .ui import * 12 | -------------------------------------------------------------------------------- /flexx/flx_flask.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from .app._flaskhelpers import register_blueprints, serve, start_thread 3 | -------------------------------------------------------------------------------- /flexx/resources/README.md: -------------------------------------------------------------------------------- 1 | This is where static resources that Flexx needs are placed. -------------------------------------------------------------------------------- /flexx/resources/flexx.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flexxui/flexx/ce1eb56f82595f13f89590684627911aafbc4ede/flexx/resources/flexx.ico -------------------------------------------------------------------------------- /flexx/resources/flexx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flexxui/flexx/ce1eb56f82595f13f89590684627911aafbc4ede/flexx/resources/flexx.png -------------------------------------------------------------------------------- /flexx/resources/iconmaker.py: -------------------------------------------------------------------------------- 1 | """ 2 | Simple script to generate Flexx' icon for different sizes. 3 | """ 4 | 5 | import os 6 | import base64 7 | 8 | import numpy as np 9 | 10 | import flexx 11 | from flexx.util.icon import Icon 12 | 13 | 14 | # colors: 15 | # (70, 140, 210) - Python blue 16 | # (240, 80, 80) - a strong red 17 | 18 | 19 | def create_icon(N=16, COLOR=(240, 80, 80)): 20 | 21 | im = np.zeros((N, N), np.bool) 22 | 23 | row_index = [0, 1, 1, 1, 1, 0, 2, 2, 2, 2, 0, 3, 3, 3, 3, 0] 24 | col_index1 = [0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 25 | col_index2 = [0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0] 26 | col_index3 = [0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0] 27 | col_index = None, col_index1, col_index2, col_index3 28 | 29 | 30 | # Create template image 31 | for y in range(N): 32 | for x in range(N): 33 | row16 = int(y * 16 / N) 34 | col16 = int(x * 16 / N) 35 | inrow = row_index[row16] 36 | 37 | if inrow: 38 | incol = col_index[inrow][col16] 39 | 40 | if incol: 41 | im[y, x] = True 42 | 43 | im = np.flipud(im) # images have y up 44 | 45 | # Colorize 46 | rgba = np.zeros((N, N, 4), np.uint8) 47 | for y in range(N): 48 | for x in range(N): 49 | if im[y, x]: 50 | rgba[y, x, :3] = COLOR 51 | rgba[y, x, 3] = 255 52 | elif im[max(0, y-1):y+2, max(0, x-1):x+2].any(): 53 | factor = im[max(0, y-1):y+2, max(0, x-1):x+2].sum() 54 | rgba[y, x, :3] = COLOR 55 | rgba[y, x, :3] //= 2 56 | rgba[y, x, 3] = 64 * (0.66 if factor == 1 else 1) 57 | # else: 58 | # rgba[y, x, :3] = 0, 0, 0 59 | # rgba[y, x, 3] = 128 60 | 61 | return rgba 62 | 63 | 64 | def create_icons(): 65 | icon = Icon() 66 | for n in (16, 32, 48, 64, 128, 256): 67 | icon.add(create_icon(n).tobytes()) 68 | icon.write(os.path.join(flexx.__path__[0], 'resources', 'flexx.ico')) 69 | 70 | 71 | def create_silly_icon(): 72 | 73 | im = np.zeros((16, 16, 4), 'uint8') 74 | im[3:-3, 3:-3] = 200 75 | im[:, :, 3] = 255 76 | 77 | icon = Icon() 78 | icon.add(im.tobytes()) 79 | bb = icon._to_png(icon._ims[16]) 80 | print(base64.encodebytes(bb).decode()) 81 | 82 | 83 | if __name__ == '__main__': 84 | 85 | rgba = create_icon(48) 86 | 87 | import visvis as vv 88 | vv.figure(1) 89 | vv.clf() 90 | vv.imshow(rgba) 91 | 92 | create_icons() 93 | -------------------------------------------------------------------------------- /flexx/ui/README.md: -------------------------------------------------------------------------------- 1 | flexx.ui subpackage 2 | ------------------- 3 | 4 | A GUI toolkit based on web technologies (HTML5/CSS/JS), with a Pythonic 5 | API. 6 | -------------------------------------------------------------------------------- /flexx/ui/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | The Flexx widgets. 3 | """ 4 | 5 | import logging 6 | logger = logging.getLogger(__name__) 7 | del logging 8 | 9 | # flake8: noqa 10 | 11 | # We follow the convention of having one module per widget class (or a 12 | # small set of closely related classes). In order not to pollute the 13 | # namespaces, we prefix the module names with an underscrore. 14 | 15 | from ._widget import Widget, PyWidget, create_element 16 | from .layouts import * 17 | from .widgets import * 18 | from .pywidgets import * 19 | -------------------------------------------------------------------------------- /flexx/ui/layouts/__init__.py: -------------------------------------------------------------------------------- 1 | """ Namespace for all layout widgets. 2 | """ 3 | 4 | # flake8: noqa 5 | 6 | from .._widget import Widget 7 | 8 | from ._layout import Layout 9 | from ._hv import HVLayout, HBox, VBox, HFix, VFix, HSplit, VSplit 10 | from ._stack import StackLayout 11 | from ._tabs import TabLayout 12 | from ._pinboard import PinboardLayout 13 | from ._form import FormLayout 14 | from ._grid import GridLayout 15 | -------------------------------------------------------------------------------- /flexx/ui/layouts/_form.py: -------------------------------------------------------------------------------- 1 | """ FormLayout 2 | 3 | Layout a series of (input) widgets in a form. Example: 4 | 5 | .. UIExample:: 200 6 | 7 | from flexx import ui 8 | 9 | class Example(ui.Widget): 10 | def init(self): 11 | with ui.FormLayout(): 12 | self.b1 = ui.LineEdit(title='Name:') 13 | self.b2 = ui.LineEdit(title="Age:") 14 | self.b3 = ui.LineEdit(title="Favorite color:") 15 | ui.Widget(flex=1) # Spacing 16 | 17 | Also see examples: :ref:`themed_form.py`. 18 | 19 | """ 20 | 21 | from pscript import window 22 | 23 | from . import Layout 24 | from .. import create_element 25 | 26 | 27 | class FormLayout(Layout): 28 | """ A layout widget that vertically alligns its child widgets in a form. 29 | A label is placed to the left of each widget (based on the widget's title). 30 | 31 | The ``node`` of this widget is a 32 | `
`_, 33 | which lays out it's child widgets and their labels using 34 | `CSS grid `_. 35 | """ 36 | 37 | CSS = """ 38 | .flx-FormLayout { 39 | display: grid; 40 | grid-template-columns: auto 1fr; 41 | justify-content: stretch; 42 | align-content: stretch; 43 | justify-items: stretch; 44 | align-items: center; 45 | 46 | } 47 | .flx-FormLayout > .flx-title { 48 | text-align: right; 49 | padding-right: 5px; 50 | } 51 | """ 52 | 53 | def _create_dom(self): 54 | return window.document.createElement('div') 55 | 56 | def _render_dom(self): 57 | rows = [] 58 | row_templates = [] 59 | for widget in self.children: 60 | rows.extend([ 61 | create_element('div', {'class': 'flx-title'}, widget.title), 62 | widget.outernode, 63 | ]) 64 | flex = widget.flex[1] 65 | row_templates.append(flex + "fr" if flex > 0 else "auto") 66 | self.node.style['grid-template-rows'] = " ".join(row_templates) 67 | return rows 68 | 69 | def _query_min_max_size(self): 70 | """ Overload to also take child limits into account. 71 | """ 72 | 73 | # Collect contributions of child widgets 74 | mima1 = [0, 1e9, 0, 0] 75 | for child in self.children: 76 | mima2 = child._size_limits 77 | mima1[0] = max(mima1[0], mima2[0]) 78 | mima1[1] = min(mima1[1], mima2[1]) 79 | mima1[2] += mima2[2] 80 | mima1[3] += mima2[3] 81 | 82 | # Dont forget padding and spacing 83 | extra_padding = 2 84 | extra_spacing = 2 85 | for i in range(4): 86 | mima1[i] += extra_padding 87 | mima1[2] += extra_spacing 88 | mima1[3] += extra_spacing 89 | 90 | # Own limits 91 | mima3 = super()._query_min_max_size() 92 | 93 | # Combine own limits with limits of children 94 | return [max(mima1[0], mima3[0]), 95 | min(mima1[1], mima3[1]), 96 | max(mima1[2], mima3[2]), 97 | min(mima1[3], mima3[3])] 98 | -------------------------------------------------------------------------------- /flexx/ui/layouts/_layout.py: -------------------------------------------------------------------------------- 1 | """ Layout 2 | """ 3 | 4 | from . import Widget 5 | 6 | 7 | class Layout(Widget): 8 | """ Abstract class for widgets that layout their child widgets. 9 | """ 10 | 11 | CSS = """ 12 | 13 | body { 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | .flx-Layout { 19 | /* sizing of widgets/layouts inside layout is defined per layout */ 20 | width: 100%; 21 | height: 100%; 22 | margin: 0; 23 | padding: 0; 24 | border-spacing: 0; 25 | border: 0; 26 | } 27 | 28 | """ 29 | -------------------------------------------------------------------------------- /flexx/ui/layouts/_pinboard.py: -------------------------------------------------------------------------------- 1 | """ PinboardLayout 2 | 3 | Free positioning (absolute or relative) of child widgets. Example: 4 | 5 | .. UIExample:: 200 6 | 7 | from flexx import app, ui 8 | 9 | class Example(ui.Widget): 10 | 11 | def init(self): 12 | 13 | with ui.PinboardLayout(): 14 | self.b1 = ui.Button(text='Stuck at (20, 20)', 15 | style='left:20px; top:20px;') 16 | self.b2 = ui.Button(text='Dynamic at (30%, 30%)', 17 | style='left:30%; top:30%; height:100px;') 18 | self.b3 = ui.Button(text='Dynamic at (50%, 70%)', 19 | style='left:50%; top:70%;') 20 | 21 | """ 22 | 23 | from . import Layout 24 | 25 | 26 | class PinboardLayout(Layout): 27 | """ Unconstrained absolute and relative positioning of child widgets. 28 | 29 | This simply places child widgets using CSS "position: absolute". Use 30 | CSS "left" and "top" to position the widget (using a "px" or "%" suffix). 31 | Optionally "width", "height", "right" and "bottom" can also be used. 32 | 33 | The ``node`` of this widget is a 34 | `
`_. 35 | """ 36 | 37 | CSS = """ 38 | .flx-PinboardLayout > .flx-Widget { 39 | position: absolute; 40 | } 41 | """ 42 | -------------------------------------------------------------------------------- /flexx/ui/layouts/_stack.py: -------------------------------------------------------------------------------- 1 | """ StackLayout 2 | 3 | Show only one child at any time. Example: 4 | 5 | .. UIExample:: 200 6 | 7 | from flexx import app, event, ui 8 | 9 | class Example(ui.Widget): 10 | 11 | def init(self): 12 | with ui.HBox(): 13 | with ui.VBox(): 14 | self.buta = ui.Button(text='red') 15 | self.butb = ui.Button(text='green') 16 | self.butc = ui.Button(text='blue') 17 | ui.Widget(flex=1) # space filler 18 | with ui.StackLayout(flex=1) as self.stack: 19 | self.buta.w = ui.Widget(style='background:#a00;') 20 | self.butb.w = ui.Widget(style='background:#0a0;') 21 | self.butc.w = ui.Widget(style='background:#00a;') 22 | 23 | @event.reaction('buta.pointer_down', 'butb.pointer_down', 'butc.pointer_down') 24 | def _stacked_current(self, *events): 25 | button = events[-1].source 26 | self.stack.set_current(button.w) 27 | """ 28 | 29 | from ... import event 30 | from . import Layout 31 | 32 | 33 | class StackLayout(Layout): 34 | """ A layout widget which shows only one of its children at a time. 35 | 36 | The ``node`` of this widget is a 37 | `
`_. 38 | """ 39 | 40 | CSS = """ 41 | .flx-StackLayout > .flx-Widget { 42 | position: absolute; 43 | left: 0; 44 | top: 0; 45 | right: 0; 46 | bottom: 0; 47 | width: 100%; 48 | height: 100%; 49 | } 50 | .flx-StackLayout > .flx-Widget:not(.flx-current) { 51 | display: none; 52 | } 53 | """ 54 | 55 | current = event.ComponentProp(doc=""" 56 | The currently shown widget (or None). 57 | """) 58 | 59 | @event.action 60 | def set_current(self, current): 61 | """ Setter for current widget. Can also set using an integer index. 62 | """ 63 | if isinstance(current, (float, int)): 64 | current = self.children[int(current)] 65 | self._mutate_current(current) 66 | 67 | @event.reaction 68 | def __set_current_widget(self): 69 | current = self.current 70 | children = self.children 71 | 72 | if len(children) == 0: 73 | if current is not None: 74 | self.set_current(None) 75 | else: 76 | if current is None: 77 | current = children[0] 78 | self.set_current(current) 79 | 80 | for widget in self.children: 81 | if widget is current: 82 | widget.outernode.classList.add('flx-current') 83 | widget.check_real_size() 84 | else: 85 | widget.outernode.classList.remove('flx-current') 86 | -------------------------------------------------------------------------------- /flexx/ui/pywidgets/__init__.py: -------------------------------------------------------------------------------- 1 | """ Namespace for all PyWidgets (widgets that operate in Python). 2 | """ 3 | # flake8: noqa 4 | 5 | from .. import PyWidget 6 | from ._filebrowser import FileBrowserWidget 7 | -------------------------------------------------------------------------------- /flexx/ui/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | """ Namespace for all widgets (that are not layouts). 2 | """ 3 | 4 | # flake8: noqa 5 | 6 | from .. import Widget 7 | 8 | from ._button import BaseButton, Button, ToggleButton, RadioButton, CheckBox 9 | from ._lineedit import LineEdit, MultiLineEdit 10 | from ._label import Label 11 | from ._group import GroupWidget 12 | from ._iframe import IFrame 13 | from ._canvas import CanvasWidget 14 | from ._color import ColorSelectWidget 15 | from ._media import ImageWidget, VideoWidget, YoutubeWidget 16 | 17 | from ._progressbar import ProgressBar 18 | from ._slider import Slider, RangeSlider 19 | from ._tree import TreeWidget, TreeItem 20 | from ._dropdown import ComboBox, DropdownContainer 21 | 22 | from ._plotwidget import PlotWidget 23 | from ._plotly import PlotlyWidget 24 | from ._bokeh import BokehWidget 25 | from ._markdown import Markdown 26 | -------------------------------------------------------------------------------- /flexx/ui/widgets/_color.py: -------------------------------------------------------------------------------- 1 | """ ColorSelectWidget 2 | 3 | .. UIExample:: 50 4 | 5 | from flexx import event, ui 6 | 7 | class Example(ui.Widget): 8 | 9 | def init(self): 10 | self.c = ui.ColorSelectWidget() 11 | 12 | @event.reaction 13 | def _color_changed(self): 14 | self.node.style.background = self.c.color.hex 15 | """ 16 | 17 | from ... import event 18 | from . import Widget 19 | 20 | 21 | class ColorSelectWidget(Widget): 22 | """ A widget used to select a color. 23 | 24 | The ``node`` of this widget is an 25 | ` `_ 26 | element of type ``color``. This is supported at least 27 | on Firefox and Chrome, but not on IE. 28 | """ 29 | 30 | DEFAULT_MIN_SIZE = 28, 28 31 | 32 | color = event.ColorProp('#000000', settable=True, doc=""" 33 | The currently selected color. 34 | """) 35 | 36 | disabled = event.BoolProp(False, settable=True, doc=""" 37 | Whether the color select is disabled. 38 | """) 39 | 40 | def _create_dom(self): 41 | global window 42 | node = window.document.createElement('input') 43 | try: 44 | node.type = 'color' 45 | except Exception: # This widget simply does not work on IE 46 | node = window.document.createElement('div') 47 | node.innerHTML = 'Not supported' 48 | self._addEventListener(node, 'input', self._color_changed_from_dom, 0) 49 | return node 50 | 51 | @event.emitter 52 | def user_color(self, color): 53 | """ Event emitted when the user changes the color. Has ``old_value`` 54 | and ``new_value`` attributes. 55 | """ 56 | d = {'old_value': self.color, 'new_value': color} 57 | self.set_color(color) 58 | return d 59 | 60 | @event.reaction('color') 61 | def _color_changed(self, *events): 62 | self.node.value = self.color.hex # hex is html-compatible, color.css is not 63 | 64 | def _color_changed_from_dom(self, e): 65 | self.user_color(self.node.value) 66 | 67 | @event.reaction('disabled') 68 | def __disabled_changed(self, *events): 69 | if self.disabled: 70 | self.node.setAttribute("disabled", "disabled") 71 | else: 72 | self.node.removeAttribute("disabled") 73 | -------------------------------------------------------------------------------- /flexx/ui/widgets/_group.py: -------------------------------------------------------------------------------- 1 | """ GroupWidget 2 | 3 | Visually group a collection of input widgets. Example: 4 | 5 | .. UIExample:: 150 6 | 7 | from flexx import app, event, ui 8 | 9 | class Example(ui.GroupWidget): 10 | def init(self): 11 | self.set_title('A silly panel') 12 | with ui.VBox(): 13 | self.progress = ui.ProgressBar(min=0, max=9, 14 | text='Clicked {value} times') 15 | self.but = ui.Button(text='click me') 16 | 17 | @event.reaction('but.pointer_down') 18 | def _button_pressed(self, *events): 19 | self.progress.set_value(self.progress.value + 1) 20 | """ 21 | 22 | from ... import event 23 | from . import Widget 24 | 25 | 26 | class GroupWidget(Widget): 27 | """ Widget to collect widgets in a named group. 28 | It does not provide a layout. This is similar to a QGroupBox or an 29 | HTML fieldset. 30 | 31 | The ``node`` of this widget is a 32 | `
`_. 33 | """ 34 | 35 | CSS = """ 36 | 37 | .flx-GroupWidget { 38 | margin: 0; 39 | padding: 5px; 40 | border: 2px solid #ccc; 41 | border-radius: 3px; 42 | } 43 | .flx-GroupWidget > .flx-Layout { 44 | width: calc(100% - 10px) !important; 45 | height: calc(100% - 25px) !important; 46 | } 47 | 48 | """ 49 | 50 | def _create_dom(self): 51 | global window 52 | node = window.document.createElement('fieldset') 53 | self._legend = window.document.createElement('legend') 54 | node.appendChild(self._legend) 55 | return node 56 | 57 | def _render_dom(self): 58 | nodes = [self._legend] 59 | for widget in self.children: 60 | nodes.append(widget.outernode) 61 | return nodes 62 | 63 | def _query_min_max_size(self): 64 | w1, w2, h1, h2 = super()._query_min_max_size() 65 | w1 += 10 66 | h1 += 30 67 | return w1, w2, h1, h2 68 | 69 | @event.reaction('title') 70 | def _title_changed(self, *events): 71 | self._legend.textContent = '\u00A0' + self.title + '\u00A0' 72 | -------------------------------------------------------------------------------- /flexx/ui/widgets/_iframe.py: -------------------------------------------------------------------------------- 1 | """ IFrame 2 | 3 | .. UIExample:: 100 4 | 5 | with ui.HSplit(): 6 | ui.IFrame(url='bsdf.io') 7 | ui.IFrame(url='http://flexx.readthedocs.io') 8 | # Note: the rtd page does not seem to load on Firefox 57.04 9 | 10 | """ 11 | 12 | from ... import event 13 | from . import Widget 14 | 15 | 16 | class IFrame(Widget): 17 | """ An iframe element, i.e. a container to show web-content. 18 | Note that some websites do not allow themselves to be rendered in 19 | a cross-source iframe. 20 | 21 | The ``node`` of this widget is a 22 | `