├── .cargo
└── config.toml
├── .changes
├── config.json
└── readme.md
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── feature_request.md
│ └── other.md
├── pull_request_template.md
└── workflows
│ ├── covector-status.yml
│ ├── covector-version-or-publish.yml
│ └── rust.yml
├── .gitignore
├── .vscode
└── settings.json
├── ARCHITECTURE.md
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Cargo.lock
├── Cargo.toml
├── Cranky.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── bacon.toml
├── clippy.toml
├── crates
├── eframe
│ ├── CHANGELOG.md
│ ├── Cargo.toml
│ ├── README.md
│ ├── data
│ │ └── icon.png
│ └── src
│ │ ├── epi
│ │ ├── icon_data.rs
│ │ └── mod.rs
│ │ ├── lib.rs
│ │ ├── native
│ │ ├── app_icon.rs
│ │ ├── epi_integration.rs
│ │ ├── file_storage.rs
│ │ ├── mod.rs
│ │ └── run.rs
│ │ └── web
│ │ ├── app_runner.rs
│ │ ├── backend.rs
│ │ ├── events.rs
│ │ ├── input.rs
│ │ ├── mod.rs
│ │ ├── panic_handler.rs
│ │ ├── screen_reader.rs
│ │ ├── storage.rs
│ │ ├── text_agent.rs
│ │ ├── web_logger.rs
│ │ ├── web_painter.rs
│ │ ├── web_painter_glow.rs
│ │ ├── web_painter_wgpu.rs
│ │ └── web_runner.rs
├── egui-winit
│ ├── CHANGELOG.md
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── clipboard.rs
│ │ ├── lib.rs
│ │ └── window_settings.rs
├── egui_demo_app
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── apps
│ │ ├── custom3d_glow.rs
│ │ ├── custom3d_wgpu.rs
│ │ ├── custom3d_wgpu_shader.wgsl
│ │ ├── fractal_clock.rs
│ │ ├── http_app.rs
│ │ └── mod.rs
│ │ ├── backend_panel.rs
│ │ ├── frame_history.rs
│ │ ├── lib.rs
│ │ ├── main.rs
│ │ ├── web.rs
│ │ └── wrap_app.rs
└── egui_glow
│ ├── CHANGELOG.md
│ ├── Cargo.toml
│ ├── README.md
│ ├── examples
│ └── pure_glow.rs
│ └── src
│ ├── lib.rs
│ ├── misc_util.rs
│ ├── painter.rs
│ ├── shader
│ ├── fragment.glsl
│ └── vertex.glsl
│ ├── shader_version.rs
│ ├── vao.rs
│ └── winit.rs
├── deny.toml
├── rust-toolchain
└── scripts
├── build_demo_web.sh
├── cargo_deny.sh
├── check.sh
├── clippy_wasm.sh
├── clippy_wasm
└── clippy.toml
├── docs.sh
├── find_bloat.sh
├── generate_changelog.py
├── generate_example_screenshots.sh
├── setup_web.sh
├── start_server.sh
├── wasm_bindgen_check.sh
└── wasm_size.sh
/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | # clipboard api is still unstable, so web-sys requires the below flag to be passed for copy (ctrl + c) to work
2 | # https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html
3 | # check status at https://developer.mozilla.org/en-US/docs/Web/API/Clipboard#browser_compatibility
4 | # we don't use `[build]` because of rust analyzer's build cache invalidation https://github.com/emilk/eframe_template/issues/93
5 | [target.wasm32-unknown-unknown]
6 | rustflags = ["--cfg=web_sys_unstable_apis"]
7 |
--------------------------------------------------------------------------------
/.changes/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "gitSiteUrl": "https://github.com/tauri-apps/egui/",
3 | "pkgManagers": {
4 | "rust": {
5 | "version": true,
6 | "getPublishedVersion": "cargo search ${ pkg.pkg } --limit 1 | sed -nE 's/^[^\"]*\"//; s/\".*//1p' -",
7 | "prepublish": [
8 | "sudo apt-get update",
9 | "sudo apt-get install -y libgtk-3-dev",
10 | "cargo install cargo-audit --features=fix",
11 | {
12 | "command": "cargo generate-lockfile",
13 | "dryRunCommand": true,
14 | "runFromRoot": true,
15 | "pipe": true
16 | },
17 | {
18 | "command": "echo '\nCargo Audit
\n\n```'",
19 | "dryRunCommand": true,
20 | "pipe": true
21 | },
22 | {
23 | "command": "cargo audit ${ process.env.CARGO_AUDIT_OPTIONS || '' }",
24 | "dryRunCommand": true,
25 | "runFromRoot": true,
26 | "pipe": true
27 | },
28 | {
29 | "command": "echo '```\n\n \n'",
30 | "dryRunCommand": true,
31 | "pipe": true
32 | }
33 | ],
34 | "publish": [
35 | {
36 | "command": "cargo package --allow-dirty",
37 | "dryRunCommand": true
38 | },
39 | {
40 | "command": "echo '\nCargo Publish
\n\n```'",
41 | "dryRunCommand": true,
42 | "pipe": true
43 | },
44 | {
45 | "command": "echo \"\\`\\`\\`\"",
46 | "dryRunCommand": true,
47 | "pipe": true
48 | },
49 | {
50 | "command": "cargo publish --no-verify",
51 | "dryRunCommand": "cargo publish --no-verify --dry-run --allow-dirty",
52 | "pipe": true
53 | },
54 | {
55 | "command": "echo '```\n\n \n'",
56 | "dryRunCommand": true,
57 | "pipe": true
58 | }
59 | ],
60 | "postpublish": [
61 | "git tag ${ pkg.pkg }-v${ pkgFile.versionMajor } -f",
62 | "git tag ${ pkg.pkg }-v${ pkgFile.versionMajor }.${ pkgFile.versionMinor } -f",
63 | "git push --tags -f"
64 | ],
65 | "assets": [
66 | {
67 | "path": "${ pkg.path }/${ pkg.pkg }-${ pkgFile.version }.crate",
68 | "name": "${ pkg.pkg }-${ pkgFile.version }.crate"
69 | }
70 | ]
71 | }
72 | },
73 | "packages": {
74 | "egui-tao": {
75 | "path": "./crates/egui-winit",
76 | "manager": "rust"
77 | },
78 | "egui_glow_tao": {
79 | "path": "./crates/egui_glow",
80 | "manager": "rust"
81 | },
82 | "eframe_tao": {
83 | "path": "./crates/eframe",
84 | "manager": "rust"
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/.changes/readme.md:
--------------------------------------------------------------------------------
1 | # Changes
2 |
3 | ##### via https://github.com/jbolda/covector
4 |
5 | As you create PRs and make changes that require a version bump, please add a new markdown file in this folder. You do not note the version _number_, but rather the type of bump that you expect: major, minor, or patch. The filename is not important, as long as it is a `.md`, but we recommend that it represents the overall change for organizational purposes.
6 |
7 | When you select the version bump required, you do _not_ need to consider dependencies. Only note the package with the actual change, and any packages that depend on that package will be bumped automatically in the process.
8 |
9 | Use the following format:
10 |
11 | ```md
12 | ---
13 | "package-a": patch
14 | "package-b": minor
15 | ---
16 |
17 | Change summary goes here
18 |
19 | ```
20 |
21 | Summaries do not have a specific character limit, but are text only. These summaries are used within the (future implementation of) changelogs. They will give context to the change and also point back to the original PR if more details and context are needed.
22 |
23 | Changes will be designated as a `major`, `minor` or `patch` as further described in [semver](https://semver.org/).
24 |
25 | Given a version number MAJOR.MINOR.PATCH, increment the:
26 |
27 | - MAJOR version when you make incompatible API changes,
28 | - MINOR version when you add functionality in a backwards compatible manner, and
29 | - PATCH version when you make backwards compatible bug fixes.
30 |
31 | Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format, but will be discussed prior to usage (as extra steps will be necessary in consideration of merging and publishing).
32 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 |
21 |
22 | **Describe the bug**
23 |
24 |
25 | **To Reproduce**
26 | Steps to reproduce the behavior:
27 | 1.
28 | 2.
29 | 3.
30 | 4.
31 |
32 | **Expected behavior**
33 |
34 |
35 | **Screenshots**
36 |
37 |
38 | **Desktop (please complete the following information):**
39 | - OS:
40 | - Browser
41 | - Version
42 |
43 | **Smartphone (please complete the following information):**
44 | - Device:
45 | - OS:
46 | - Browser
47 | - Version
48 |
49 | **Additional context**
50 |
51 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: feature-request
6 | assignees: ''
7 |
8 | ---
9 |
10 |
13 |
14 |
15 | **Is your feature request related to a problem? Please describe.**
16 |
17 |
18 | **Describe the solution you'd like**
19 |
20 |
21 | **Describe alternatives you've considered**
22 |
23 |
24 | **Additional context**
25 |
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/other.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Other
3 | about: For issues that are neither bugs or feature requests
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | If you are asking a question, use [the egui discussions forum](https://github.com/emilk/egui/discussions/categories/q-a) instead!
11 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
14 |
15 | Closes .
16 |
--------------------------------------------------------------------------------
/.github/workflows/covector-status.yml:
--------------------------------------------------------------------------------
1 | name: covector status
2 | on: [pull_request]
3 |
4 | jobs:
5 | covector:
6 | runs-on: ubuntu-latest
7 |
8 | steps:
9 | - uses: actions/checkout@v2
10 | with:
11 | fetch-depth: 0 # required for use of git history
12 | - name: covector status
13 | uses: jbolda/covector/packages/action@covector-v0.7
14 | id: covector
15 | with:
16 | command: "status"
17 |
--------------------------------------------------------------------------------
/.github/workflows/covector-version-or-publish.yml:
--------------------------------------------------------------------------------
1 | name: version or publish
2 |
3 | on:
4 | push:
5 | branches:
6 | - '0.21'
7 |
8 | jobs:
9 | version-or-publish:
10 | runs-on: ubuntu-latest
11 | timeout-minutes: 65
12 | outputs:
13 | change: ${{ steps.covector.outputs.change }}
14 | commandRan: ${{ steps.covector.outputs.commandRan }}
15 | successfulPublish: ${{ steps.covector.outputs.successfulPublish }}
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 | with:
20 | fetch-depth: 0
21 | - name: cargo login
22 | run: cargo login ${{ secrets.ORG_CRATES_IO_TOKEN }}
23 | - name: git config
24 | run: |
25 | git config --global user.name "${{ github.event.pusher.name }}"
26 | git config --global user.email "${{ github.event.pusher.email }}"
27 | - name: covector version or publish (publish when no change files present)
28 | uses: jbolda/covector/packages/action@covector-v0
29 | id: covector
30 | env:
31 | CARGO_AUDIT_OPTIONS: ${{ secrets.CARGO_AUDIT_OPTIONS }}
32 | with:
33 | token: ${{ secrets.GITHUB_TOKEN }}
34 | command: "version-or-publish"
35 | createRelease: true
36 | - name: Create Pull Request With Versions Bumped
37 | id: cpr
38 | uses: tauri-apps/create-pull-request@v3
39 | if: steps.covector.outputs.commandRan == 'version'
40 | with:
41 | token: ${{ secrets.GITHUB_TOKEN }}
42 | title: "Publish New Versions"
43 | commit-message: "publish new versions"
44 | labels: "version updates"
45 | branch: "release"
46 | body: ${{ steps.covector.outputs.change }}
47 |
--------------------------------------------------------------------------------
/.github/workflows/rust.yml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 |
3 | name: CI
4 |
5 | env:
6 | # web_sys_unstable_apis is required to enable the web_sys clipboard API which eframe web uses,
7 | # as well as by the wasm32-backend of the wgpu crate.
8 | # https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Clipboard.html
9 | # https://rustwasm.github.io/docs/wasm-bindgen/web-sys/unstable-apis.html
10 | RUSTFLAGS: --cfg=web_sys_unstable_apis -D warnings
11 | RUSTDOCFLAGS: -D warnings
12 |
13 | jobs:
14 | tests:
15 | name: Tests
16 | strategy:
17 | fail-fast: false
18 | matrix:
19 | rust_version: [1.65.0, stable, nightly]
20 | platform:
21 | - { target: x86_64-pc-windows-msvc, os: windows-latest }
22 | - { target: i686-pc-windows-msvc, os: windows-latest }
23 | - {
24 | target: x86_64-pc-windows-gnu,
25 | os: windows-latest,
26 | host: -x86_64-pc-windows-gnu,
27 | }
28 | # - { target: i686-unknown-linux-gnu, os: ubuntu-latest }
29 | - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest }
30 | - { target: x86_64-apple-darwin, os: macos-latest }
31 |
32 | env:
33 | RUST_BACKTRACE: 1
34 | CARGO_INCREMENTAL: 0
35 | RUSTFLAGS: "-C debuginfo=0"
36 | OPTIONS: ${{ matrix.platform.options }}
37 | CMD: ${{ matrix.platform.cmd }}
38 | FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
39 | RUSTDOCFLAGS: -D warnings
40 |
41 | runs-on: ${{ matrix.platform.os }}
42 | steps:
43 | - uses: actions/checkout@v1
44 | # Used to cache cargo-web
45 | - name: Cache cargo folder
46 | uses: actions/cache@v1
47 | with:
48 | path: ~/.cargo
49 | key: ${{ matrix.platform.target }}-cargo-${{ matrix.rust_version }}
50 |
51 | - uses: hecrj/setup-rust-action@v1
52 | with:
53 | rust-version: ${{ matrix.rust_version }}${{ matrix.platform.host }}
54 | targets: ${{ matrix.platform.target }}
55 | components: clippy
56 |
57 | # We need those for examples.
58 | - name: Install GCC Multilib
59 | if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
60 | run: sudo apt-get update && sudo apt-get install gcc-multilib
61 |
62 | - name: Install Gtk (ubuntu only)
63 | if: matrix.platform.os == 'ubuntu-latest'
64 | run: |
65 | sudo apt-get update
66 | sudo apt-get install -y libgtk-3-dev
67 |
68 | - name: Install Core (windows only)
69 | if: matrix.platform.os == 'windows-latest'
70 | run: |
71 | rustup target add ${{ matrix.platform.target }}
72 |
73 | - name: Install cargo-apk
74 | if: contains(matrix.platform.target, 'android')
75 | run: cargo +stable install cargo-apk
76 |
77 | - name: Build tests (eframe_tao)
78 | shell: bash
79 | run: cargo $CMD test -p eframe_tao --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
80 | - name: Build tests (egui_glow_tao)
81 | shell: bash
82 | run: cargo $CMD test -p egui_glow_tao --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
83 | - name: Build tests (egui-tao)
84 | shell: bash
85 | run: cargo $CMD test -p egui-tao --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
86 | - name: Run tests
87 | shell: bash
88 | if: (
89 | !contains(matrix.platform.target, 'android') &&
90 | !contains(matrix.platform.target, 'ios') &&
91 | !contains(matrix.platform.target, 'wasm32'))
92 | run: cargo test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
93 |
94 | - name: Check documentation
95 | shell: bash
96 | run: cargo doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES --document-private-items
97 |
98 | # - name: Lint with clippy
99 | # shell: bash
100 | # if: (matrix.rust_version == '1.65.0') && !contains(matrix.platform.options, '--no-default-features')
101 | # run: cargo clippy --workspace --all-targets --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES -- -Dwarnings
102 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | **/target
3 | **/target_ra
4 | **/target_wasm
5 | /.*.json
6 | /.vscode
7 | /media/*
8 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.insertFinalNewline": true,
3 | "editor.formatOnSave": true,
4 | "files.trimTrailingWhitespace": true,
5 | "editor.semanticTokenColorCustomizations": {
6 | "rules": {
7 | "*.unsafe:rust": "#eb5046"
8 | }
9 | },
10 | "files.exclude": {
11 | "target_ra/**": true,
12 | "target_wasm/**": true,
13 | "target/**": true,
14 | },
15 | // Tell Rust Analyzer to use its own target directory, so we don't need to wait for it to finish wen we want to `cargo run`
16 | "rust-analyzer.check.overrideCommand": [
17 | "cargo",
18 | "cranky",
19 | "--target-dir=target_ra",
20 | "--workspace",
21 | "--message-format=json",
22 | "--all-targets",
23 | ],
24 | "rust-analyzer.cargo.buildScripts.overrideCommand": [
25 | "cargo",
26 | "check",
27 | "--quiet",
28 | "--target-dir=target_ra",
29 | "--workspace",
30 | "--message-format=json",
31 | "--all-targets",
32 | ],
33 | }
34 |
--------------------------------------------------------------------------------
/ARCHITECTURE.md:
--------------------------------------------------------------------------------
1 | # Architecture
2 | This document describes how the crates that make up egui are all connected.
3 |
4 | Also see [`CONTRIBUTING.md`](CONTRIBUTING.md) for what to do before opening a PR.
5 |
6 |
7 | ## Crate overview
8 | The crates in this repository are: `egui, emath, epaint, egui_extras, egui-winit, egui_glium, egui_glow, egui_demo_lib, egui_demo_app`.
9 |
10 | ### `egui`: The main GUI library.
11 | Example code: `if ui.button("Click me").clicked() { … }`
12 | This is the crate where the bulk of the code is at. `egui` depends only on `emath` and `epaint`.
13 |
14 | ### `emath`: minimal 2D math library
15 | Examples: `Vec2, Pos2, Rect, lerp, remap`
16 |
17 | ### `epaint`
18 | 2d shapes and text that can be turned into textured triangles.
19 |
20 | Example: `Shape::Circle { center, radius, fill, stroke }`
21 |
22 | Depends on `emath`.
23 |
24 | ### `egui_extras`
25 | This adds additional features on top of `egui`.
26 |
27 | ### `egui-winit`
28 | This crates provides bindings between [`egui`](https://github.com/emilk/egui) and [winit](https://crates.io/crates/winit).
29 |
30 | The library translates winit events to egui, handled copy/paste, updates the cursor, open links clicked in egui, etc.
31 |
32 | ### `egui_glium`
33 | Puts an egui app inside a native window on your laptop. Paints the triangles that egui outputs using [glium](https://github.com/glium/glium).
34 |
35 | ### `egui_glow`
36 | Puts an egui app inside a native window on your laptop. Paints the triangles that egui outputs using [glow](https://github.com/grovesNL/glow).
37 |
38 | ### `eframe`
39 | `eframe` is the official `egui` framework, built so you can compile the same app for either web or native.
40 |
41 | The demo that you can see at is using `eframe` to host the `egui`. The demo code is found in:
42 |
43 | ### `egui_demo_lib`
44 | Depends on `egui`.
45 | This contains a bunch of uses of `egui` and looks like the ui code you would write for an `egui` app.
46 |
47 | ### `egui_demo_app`
48 | Thin wrapper around `egui_demo_lib` so we can compile it to a web site or a native app executable.
49 | Depends on `egui_demo_lib` + `eframe`.
50 |
51 | ### Other integrations
52 |
53 | There are also many great integrations for game engines such as `bevy` and `miniquad` which you can find at .
54 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, caste, color, religion, or sexual
10 | identity and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the overall
26 | community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or advances of
31 | any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email address,
35 | without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | [the egui discord](https://discord.gg/JFcEma9bJq).
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series of
86 | actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or permanent
93 | ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within the
113 | community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.1, available at
119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120 |
121 | Community Impact Guidelines were inspired by
122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123 |
124 | For answers to common questions about this code of conduct, see the FAQ at
125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126 | [https://www.contributor-covenant.org/translations][translations].
127 |
128 | [homepage]: https://www.contributor-covenant.org
129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130 | [Mozilla CoC]: https://github.com/mozilla/diversity
131 | [FAQ]: https://www.contributor-covenant.org/faq
132 | [translations]: https://www.contributor-covenant.org/translations
133 |
134 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing guidelines
2 |
3 | ## Introduction
4 |
5 | `egui` has been an on-and-off weekend project of mine since late 2018. I am grateful to any help I can get, but bare in mind that sometimes I can be slow to respond because I am busy with other things!
6 |
7 | / Emil
8 |
9 |
10 | ## Discussion
11 |
12 | You can ask questions, share screenshots and more at [GitHub Discussions](https://github.com/emilk/egui/discussions).
13 |
14 | There is an `egui` discord at .
15 |
16 |
17 | ## Filing an issue
18 |
19 | [Issues](https://github.com/emilk/egui/issues) are for bug reports and feature requests. Issues are not for asking questions (use [Discussions](https://github.com/emilk/egui/discussions) or [Discord](https://discord.gg/vbuv9Xan65) for that).
20 |
21 | Always make sure there is not already a similar issue to the one you are creating.
22 |
23 | If you are filing a bug, please provide a way to reproduce it.
24 |
25 |
26 | ## Making a PR
27 |
28 | First file an issue (or find an existing one) and announce that you plan to work on something. That way we will avoid having several people doing double work. Please ask for feedback before you start working on something non-trivial!
29 |
30 | Browse through [`ARCHITECTURE.md`](ARCHITECTURE.md) to get a sense of how all pieces connects.
31 |
32 | You can test your code locally by running `./scripts/check.sh`.
33 |
34 | When you have something that works, open a draft PR. You may get some helpful feedback early!
35 | When you feel the PR is ready to go, do a self-review of the code, and then open it for review.
36 |
37 | Please keep pull requests small and focused.
38 |
39 | Don't worry about having many small commits in the PR - they will be squashed to one commit once merged.
40 |
41 | Do not include the `.js` and `.wasm` build artifacts generated for building for web.
42 | `git` is not great at storing large files like these, so we only commit a new web demo after a new egui release.
43 |
44 |
45 | ## Creating an integration for egui
46 |
47 | If you make an integration for `egui` for some engine or renderer, please share it with the world!
48 | I will add a link to it from the `egui` README.md so others can easily find it.
49 |
50 | Read the section on integrations at .
51 |
52 |
53 | ## Testing the web viewer
54 | * Install some tools with `scripts/setup_web.sh`
55 | * Build with `scripts/build_demo_web.sh`
56 | * Host with `scripts/start_server.sh`
57 | * Open
58 |
59 |
60 | ## Code Conventions
61 | Conventions unless otherwise specified:
62 |
63 | * angles are in radians
64 | * `Vec2::X` is right and `Vec2::Y` is down.
65 | * `Pos2::ZERO` is left top.
66 |
67 | While using an immediate mode gui is simple, implementing one is a lot more tricky. There are many subtle corner-case you need to think through. The `egui` source code is a bit messy, partially because it is still evolving.
68 |
69 | * Read some code before writing your own.
70 | * Follow the `egui` code style.
71 | * Add blank lines around all `fn`, `struct`, `enum`, etc.
72 | * `// Comment like this.` and not `//like this`.
73 | * Use `TODO` instead of `FIXME`.
74 | * Add your github handle to the `TODO`:s you write, e.g: `TODO(emilk): clean this up`.
75 | * Write idiomatic rust.
76 | * Avoid `unsafe`.
77 | * Avoid code that can cause panics.
78 | * Use good names for everything.
79 | * Add docstrings to types, `struct` fields and all `pub fn`.
80 | * Add some example code (doc-tests).
81 | * Before making a function longer, consider adding a helper function.
82 | * If you are only using it in one function, put the `use` statement in that function. This improves locality, making it easier to read and move the code.
83 | * When importing a `trait` to use it's trait methods, do this: `use Trait as _;`. That lets the reader know why you imported it, even though it seems unused.
84 | * Follow the [Rust API Guidelines](https://rust-lang.github.io/api-guidelines/).
85 | * Break the above rules when it makes sense.
86 |
87 |
88 | ### Good:
89 | ``` rust
90 | /// The name of the thing.
91 | fn name(&self) -> &str {
92 | &self.name
93 | }
94 |
95 | fn foo(&self) {
96 | // TODO(emilk): implement
97 | }
98 | ```
99 |
100 | ### Bad:
101 | ``` rust
102 | //some function
103 | fn get_name(&self) -> &str {
104 | &self.name
105 | }
106 | fn foo(&self) {
107 | //FIXME: implement
108 | }
109 | ```
110 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | resolver = "2"
3 | members = [
4 | "crates/eframe",
5 | "crates/egui_demo_app",
6 | "crates/egui_glow",
7 | "crates/egui-winit",
8 | ]
9 |
10 | [profile.release]
11 | # lto = true # VERY slightly smaller wasm
12 | # opt-level = 's' # 10-20% smaller wasm compared to `opt-level = 3`
13 | # opt-level = 1 # very slow and big wasm. Don't do this.
14 | opt-level = 2 # fast and small wasm, basically same as `opt-level = 's'`
15 | # opt-level = 3 # unnecessarily large wasm for no performance gain
16 |
17 | # debug = true # include debug symbols, useful when profiling wasm
18 |
19 |
20 | [profile.dev]
21 | # Can't leave this on by default, because it breaks the Windows build. Related: https://github.com/rust-lang/cargo/issues/4897
22 | # split-debuginfo = "unpacked" # faster debug builds on mac
23 | # opt-level = 1 # Make debug builds run faster
24 |
25 | # Optimize all dependencies even in debug builds (does not affect workspace packages):
26 | [profile.dev.package."*"]
27 | opt-level = 2
28 |
29 | [workspace.dependencies]
30 | thiserror = "1.0.37"
31 |
--------------------------------------------------------------------------------
/Cranky.toml:
--------------------------------------------------------------------------------
1 | # https://github.com/ericseppanen/cargo-cranky
2 | # cargo install cargo-cranky && cargo cranky
3 |
4 | deny = ["unsafe_code"]
5 |
6 | warn = [
7 | "clippy::all",
8 | "clippy::await_holding_lock",
9 | "clippy::bool_to_int_with_if",
10 | "clippy::char_lit_as_u8",
11 | "clippy::checked_conversions",
12 | "clippy::cloned_instead_of_copied",
13 | "clippy::dbg_macro",
14 | "clippy::debug_assert_with_mut_call",
15 | "clippy::derive_partial_eq_without_eq",
16 | "clippy::disallowed_methods",
17 | "clippy::disallowed_script_idents",
18 | "clippy::doc_link_with_quotes",
19 | "clippy::doc_markdown",
20 | "clippy::empty_enum",
21 | "clippy::enum_glob_use",
22 | "clippy::equatable_if_let",
23 | "clippy::exit",
24 | "clippy::expl_impl_clone_on_copy",
25 | "clippy::explicit_deref_methods",
26 | "clippy::explicit_into_iter_loop",
27 | "clippy::explicit_iter_loop",
28 | "clippy::fallible_impl_from",
29 | "clippy::filter_map_next",
30 | "clippy::flat_map_option",
31 | "clippy::float_cmp_const",
32 | "clippy::fn_params_excessive_bools",
33 | "clippy::fn_to_numeric_cast_any",
34 | "clippy::from_iter_instead_of_collect",
35 | "clippy::if_let_mutex",
36 | "clippy::implicit_clone",
37 | "clippy::imprecise_flops",
38 | "clippy::index_refutable_slice",
39 | "clippy::inefficient_to_string",
40 | "clippy::invalid_upcast_comparisons",
41 | "clippy::iter_not_returning_iterator",
42 | "clippy::iter_on_empty_collections",
43 | "clippy::iter_on_single_items",
44 | "clippy::large_digit_groups",
45 | "clippy::large_stack_arrays",
46 | "clippy::large_types_passed_by_value",
47 | "clippy::let_unit_value",
48 | "clippy::linkedlist",
49 | "clippy::lossy_float_literal",
50 | "clippy::macro_use_imports",
51 | "clippy::manual_assert",
52 | "clippy::manual_instant_elapsed",
53 | "clippy::manual_ok_or",
54 | "clippy::manual_string_new",
55 | "clippy::map_err_ignore",
56 | "clippy::map_flatten",
57 | "clippy::map_unwrap_or",
58 | "clippy::match_on_vec_items",
59 | "clippy::match_same_arms",
60 | "clippy::match_wild_err_arm",
61 | "clippy::match_wildcard_for_single_variants",
62 | "clippy::mem_forget",
63 | "clippy::mismatched_target_os",
64 | "clippy::mismatching_type_param_order",
65 | "clippy::missing_enforced_import_renames",
66 | "clippy::missing_errors_doc",
67 | "clippy::missing_safety_doc",
68 | "clippy::mut_mut",
69 | "clippy::mutex_integer",
70 | "clippy::needless_borrow",
71 | "clippy::needless_continue",
72 | "clippy::needless_for_each",
73 | "clippy::needless_pass_by_value",
74 | "clippy::negative_feature_names",
75 | "clippy::nonstandard_macro_braces",
76 | "clippy::option_option",
77 | "clippy::path_buf_push_overwrite",
78 | "clippy::ptr_as_ptr",
79 | "clippy::rc_mutex",
80 | "clippy::ref_option_ref",
81 | "clippy::rest_pat_in_fully_bound_structs",
82 | "clippy::same_functions_in_if_condition",
83 | "clippy::semicolon_if_nothing_returned",
84 | "clippy::single_match_else",
85 | "clippy::str_to_string",
86 | "clippy::string_add_assign",
87 | "clippy::string_add",
88 | "clippy::string_lit_as_bytes",
89 | "clippy::string_to_string",
90 | "clippy::todo",
91 | "clippy::trailing_empty_array",
92 | "clippy::trait_duplication_in_bounds",
93 | "clippy::unimplemented",
94 | "clippy::unnecessary_wraps",
95 | "clippy::unnested_or_patterns",
96 | "clippy::unused_peekable",
97 | "clippy::unused_rounding",
98 | "clippy::unused_self",
99 | "clippy::useless_transmute",
100 | "clippy::verbose_file_reads",
101 | "clippy::zero_sized_map_values",
102 | "elided_lifetimes_in_paths",
103 | "future_incompatible",
104 | "nonstandard_style",
105 | "rust_2018_idioms",
106 | "rust_2021_prelude_collisions",
107 | "rustdoc::missing_crate_level_docs",
108 | "semicolon_in_expressions_from_macros",
109 | "trivial_numeric_casts",
110 | "unused_extern_crates",
111 | "unused_import_braces",
112 | "unused_lifetimes",
113 | ]
114 |
115 | allow = [
116 | "clippy::manual_range_contains", # This one is just annoying
117 |
118 | # Some of these we should try to put in "warn":
119 | "clippy::type_complexity",
120 | "clippy::undocumented_unsafe_blocks",
121 | "trivial_casts",
122 | "unsafe_op_in_unsafe_fn", # `unsafe_op_in_unsafe_fn` may become the default in future Rust versions: https://github.com/rust-lang/rust/issues/71668
123 | "unused_qualifications",
124 | ]
125 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018-2021 Emil Ernerfeldt
2 |
3 | Permission is hereby granted, free of charge, to any
4 | person obtaining a copy of this software and associated
5 | documentation files (the "Software"), to deal in the
6 | Software without restriction, including without
7 | limitation the rights to use, copy, modify, merge,
8 | publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software
10 | is furnished to do so, subject to the following
11 | conditions:
12 |
13 | The above copyright notice and this permission notice
14 | shall be included in all copies or substantial portions
15 | of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 | DEALINGS IN THE SOFTWARE.
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # egui
2 |
3 | > egui (pronounced "e-gooey") is a simple, fast, and highly portable immediate mode GUI library for Rust.
4 | >
5 | > egui aims to be the easiest-to-use Rust GUI library, and the simplest way to make a web app in Rust.
6 |
7 | [](https://crates.io/crates/egui)
8 | [](https://docs.rs/egui)
9 |
10 | ```
11 | [dependencies]
12 | egui = "0.22.0"
13 | ```
14 |
15 | This repository provides binding for egui to use tao instead. Currently only `glow` backend is supported.
16 |
17 | For more information on how to use egui, please check out [egui repository](https://github.com/emilk/egui) for both [simple examples](https://github.com/emilk/egui/tree/master/examples) and [detailed documents](https://docs.rs/egui).
18 |
19 | ## Who is egui for?
20 |
21 | Quoting from egui repository:
22 |
23 | > [...] if you are writing something interactive in Rust that needs a simple GUI, egui may be for you.
24 |
25 | ## Demo
26 |
27 | Demo app uses [`eframe_tao`](https://github.com/tauri-apps/egui/tree/master/crates/eframe).
28 |
29 | To test the demo app locally, run `cargo run --release -p egui_demo_app`.
30 |
31 | The native backend is [`egui_glow_tao`](https://github.com/tauri-apps/egui/tree/master/crates/egui_glow) (using [`glow`](https://crates.io/crates/glow)) and should work out-of-the-box on Mac and Windows, but on Linux you need to first run:
32 |
33 | `sudo apt-get install -y libclang-dev libgtk-3-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev`
34 |
35 | On Fedora Rawhide you need to run:
36 |
37 | `dnf install clang clang-devel clang-tools-extra libxkbcommon-devel pkg-config openssl-devel libxcb-devel gtk3-devel atk fontconfig-devel`
38 |
39 | **NOTE**: This is just for the demo app - egui itself is completely platform agnostic!
40 |
--------------------------------------------------------------------------------
/bacon.toml:
--------------------------------------------------------------------------------
1 | # This is a configuration file for the bacon tool
2 | # More info at https://github.com/Canop/bacon
3 |
4 | default_job = "cranky"
5 |
6 | [jobs]
7 |
8 | [jobs.cranky]
9 | command = [
10 | "cargo",
11 | "cranky",
12 | "--all-targets",
13 | "--all-features",
14 | "--color=always",
15 | ]
16 | need_stdout = false
17 | watch = ["tests", "benches", "examples"]
18 |
19 | [jobs.wasm]
20 | command = [
21 | "cargo",
22 | "cranky",
23 | "-p=egui_demo_app",
24 | "--lib",
25 | "--target=wasm32-unknown-unknown",
26 | "--target-dir=target_wasm",
27 | "--all-features",
28 | "--color=always",
29 | ]
30 | need_stdout = false
31 | watch = ["tests", "benches", "examples"]
32 |
33 | [jobs.test]
34 | command = ["cargo", "test", "--color=always"]
35 | need_stdout = true
36 | watch = ["tests"]
37 |
38 | [jobs.doc]
39 | command = ["cargo", "doc", "--color=always", "--all-features", "--no-deps"]
40 | need_stdout = false
41 |
42 | # if the doc compiles, then it opens in your browser and bacon switches
43 | # to the previous job
44 | [jobs.doc-open]
45 | command = [
46 | "cargo",
47 | "doc",
48 | "--color=always",
49 | "--all-features",
50 | "--no-deps",
51 | "--open",
52 | ]
53 | need_stdout = false
54 | on_success = "back" # so that we don't open the browser at each change
55 |
56 | # You can run your application and have the result displayed in bacon,
57 | # *if* it makes sense for this crate. You can run an example the same
58 | # way. Don't forget the `--color always` part or the errors won't be
59 | # properly parsed.
60 | [jobs.run]
61 | command = ["cargo", "run", "--color=always"]
62 | need_stdout = true
63 |
64 | # You may define here keybindings that would be specific to
65 | # a project, for example a shortcut to launch a specific job.
66 | # Shortcuts to internal functions (scrolling, toggling, etc.)
67 | # should go in your personal prefs.toml file instead.
68 | [keybindings]
69 | i = "job:initial"
70 | c = "job:cranky"
71 | w = "job:wasm"
72 | d = "job:doc-open"
73 | t = "job:test"
74 | r = "job:run"
75 |
--------------------------------------------------------------------------------
/clippy.toml:
--------------------------------------------------------------------------------
1 | # There is also a scripts/clippy_wasm/clippy.toml which forbids some mthods that are not available in wasm.
2 |
3 | msrv = "1.65"
4 |
5 | # Allow-list of words for markdown in dosctrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown
6 | doc-valid-idents = [
7 | # You must also update the same list in the root `clippy.toml`!
8 | "AccessKit",
9 | "..",
10 | ]
11 |
--------------------------------------------------------------------------------
/crates/eframe/Cargo.toml:
--------------------------------------------------------------------------------
1 | lib = { }
2 |
3 | [package]
4 | name = "eframe_tao"
5 | version = "0.23.0"
6 | authors = [ "Emil Ernerfeldt " ]
7 | description = "egui framework - write GUI apps that compiles to web and/or natively"
8 | edition = "2021"
9 | rust-version = "1.65"
10 | homepage = "https://github.com/emilk/egui/tree/master/crates/eframe"
11 | license = "MIT OR Apache-2.0"
12 | readme = "README.md"
13 | repository = "https://github.com/emilk/egui/tree/master/crates/eframe"
14 | categories = [ "gui", "game-development" ]
15 | keywords = [ "egui", "gui", "gamedev" ]
16 | include = [
17 | "../LICENSE-APACHE",
18 | "../LICENSE-MIT",
19 | "**/*.rs",
20 | "Cargo.toml",
21 | "data/icon.png"
22 | ]
23 |
24 | [package.metadata.docs.rs]
25 | all-features = true
26 | targets = [ "x86_64-unknown-linux-gnu", "wasm32-unknown-unknown" ]
27 |
28 | [features]
29 | default = [ "default_fonts", "glow" ]
30 | default_fonts = [ "egui/default_fonts" ]
31 | glow = [
32 | "dep:glow",
33 | "dep:egui_glow",
34 | "dep:glutin",
35 | "dep:glutin-winit"
36 | ]
37 | persistence = [
38 | "directories-next",
39 | "egui-winit/serde",
40 | "egui/persistence",
41 | "ron",
42 | "serde"
43 | ]
44 | puffin = [ "dep:puffin", "egui_glow?/puffin" ]
45 | web_screen_reader = [ "tts" ]
46 | __screenshot = [ ]
47 |
48 | [dependencies]
49 | egui = { version = "0.22.0", default-features = false, features = [ "bytemuck", "log" ] }
50 | log = { version = "0.4", features = [ "std" ] }
51 | document-features = { version = "0.2", optional = true }
52 | egui_glow = { package = "egui_glow_tao", version = "0.23.0", path = "../egui_glow", optional = true, default-features = false }
53 | glow = { version = "0.12", optional = true }
54 | ron = { version = "0.8", optional = true, features = [ "integer128" ] }
55 | serde = { version = "1", optional = true, features = [ "derive" ] }
56 |
57 | [dependencies.thiserror]
58 | workspace = true
59 |
60 | [target."cfg(not(target_arch = \"wasm32\"))".dependencies]
61 | egui-winit = { package = "egui-tao", version = "0.23.0", path = "../egui-winit", default-features = false, features = [ "clipboard", "links" ] }
62 | image = { version = "0.24", default-features = false, features = [ "png" ] }
63 | raw-window-handle = { version = "0.5.0" }
64 | winit = { package = "tao", version = "0.19.0" }
65 | directories-next = { version = "2", optional = true }
66 | pollster = { version = "0.3", optional = true }
67 | glutin = { version = "0.30", optional = true }
68 | # glutin-winit = { package = "glutin_tao", version = "0.33.0", optional = true }
69 | glutin-winit = { package = "glutin_tao", version = "0.33.0", git = "https://github.com/tauri-apps/glutin", branch = "0.31", optional = true }
70 | puffin = { version = "0.15", optional = true }
71 |
72 | [target."cfg(any(target_os = \"macos\"))".dependencies]
73 | cocoa = "0.24.1"
74 | objc = "0.2.7"
75 |
76 | [target."cfg(any(target_os = \"windows\"))".dependencies]
77 | winapi = "0.3.9"
78 |
79 | [target."cfg(target_arch = \"wasm32\")".dependencies]
80 | bytemuck = "1.7"
81 | js-sys = "0.3"
82 | percent-encoding = "2.1"
83 | wasm-bindgen = "0.2.86"
84 | wasm-bindgen-futures = "0.4"
85 | web-sys = { version = "0.3.58", features = [
86 | "BinaryType",
87 | "Blob",
88 | "Clipboard",
89 | "ClipboardEvent",
90 | "CompositionEvent",
91 | "console",
92 | "CssStyleDeclaration",
93 | "DataTransfer",
94 | "DataTransferItem",
95 | "DataTransferItemList",
96 | "Document",
97 | "DomRect",
98 | "DragEvent",
99 | "Element",
100 | "Event",
101 | "EventListener",
102 | "EventTarget",
103 | "ExtSRgb",
104 | "File",
105 | "FileList",
106 | "FocusEvent",
107 | "HtmlCanvasElement",
108 | "HtmlElement",
109 | "HtmlInputElement",
110 | "InputEvent",
111 | "KeyboardEvent",
112 | "Location",
113 | "MediaQueryList",
114 | "MediaQueryListEvent",
115 | "MouseEvent",
116 | "Navigator",
117 | "Performance",
118 | "Storage",
119 | "Touch",
120 | "TouchEvent",
121 | "TouchList",
122 | "WebGl2RenderingContext",
123 | "WebglDebugRendererInfo",
124 | "WebGlRenderingContext",
125 | "WheelEvent",
126 | "Window"
127 | ] }
128 | raw-window-handle = { version = "0.5.2", optional = true }
129 | tts = { version = "0.25", optional = true, default-features = false }
130 |
--------------------------------------------------------------------------------
/crates/eframe/README.md:
--------------------------------------------------------------------------------
1 | # eframe_tao: the [`egui`](https://github.com/emilk/egui) framework for tao
2 |
3 | [](https://crates.io/crates/eframe_tao)
4 | [](https://docs.rs/eframe_tao)
5 | 
6 | 
7 |
8 | `eframe_tao` is a modification of `eframe` to utilized `tao` instead of `winit`.
9 |
10 | `eframe` is the official framework library for writing apps using [`egui`](https://github.com/emilk/egui). The app can be compiled both to run natively (cross platform) or be compiled to a web app (using WASM).
11 |
12 | To get started, see the [examples](https://github.com/emilk/egui/tree/master/examples).
13 | To learn how to set up `eframe` for web and native, go to and follow the instructions there!
14 |
15 | There is also a tutorial video at .
16 |
17 | For how to use `egui`, see [the egui docs](https://docs.rs/egui).
18 |
19 | ---
20 |
21 | `eframe` uses [`egui_glow_tao`](https://github.com/tauri-apps/egui/tree/master/crates/egui_glow) for rendering, and on native it uses [`egui-tao`](https://github.com/tauri-apps/egui/tree/master/crates/egui-winit).
22 |
23 | To use on Linux, first run:
24 |
25 | ```
26 | sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev
27 | ```
28 |
29 | You need to either use `edition = "2021"`, or set `resolver = "2"` in the `[workspace]` section of your to-level `Cargo.toml`. See [this link](https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html) for more info.
30 |
31 | ## Alternatives
32 |
33 | You can also use `egui_glow` and [`winit`](https://github.com/rust-windowing/winit) to build your own app as demonstrated in .
34 |
35 | ## Problems with running egui on the web
36 |
37 | `eframe` uses WebGL (via [`glow`](https://crates.io/crates/glow)) and WASM, and almost nothing else from the web tech stack. This has some benefits, but also produces some challenges and serious downsides.
38 |
39 | - Rendering: Getting pixel-perfect rendering right on the web is very difficult.
40 | - Search: you cannot search an egui web page like you would a normal web page.
41 | - Bringing up an on-screen keyboard on mobile: there is no JS function to do this, so `eframe` fakes it by adding some invisible DOM elements. It doesn't always work.
42 | - Mobile text editing is not as good as for a normal web app.
43 | - Accessibility: There is an experimental screen reader for `eframe`, but it has to be enabled explicitly. There is no JS function to ask "Does the user want a screen reader?" (and there should probably not be such a function, due to user tracking/integrity concerns).
44 | - No integration with browser settings for colors and fonts.
45 |
46 | In many ways, `eframe` is trying to make the browser do something it wasn't designed to do (though there are many things browser vendors could do to improve how well libraries like egui work).
47 |
48 | The suggested use for `eframe` are for web apps where performance and responsiveness are more important than accessibility and mobile text editing.
49 |
50 | ## Companion crates
51 |
52 | Not all rust crates work when compiled to WASM, but here are some useful crates have been designed to work well both natively and as WASM:
53 |
54 | - Audio: [`cpal`](https://github.com/RustAudio/cpal).
55 | - HTTP client: [`ehttp`](https://github.com/emilk/ehttp) and [`reqwest`](https://github.com/seanmonstar/reqwest).
56 | - Time: [`chrono`](https://github.com/chronotope/chrono).
57 | - WebSockets: [`ewebsock`](https://github.com/rerun-io/ewebsock).
58 |
59 | ## Name
60 |
61 | The _frame_ in `eframe` stands both for the frame in which your `egui` app resides and also for "framework" (`frame` is a framework, `egui` is a library).
62 |
--------------------------------------------------------------------------------
/crates/eframe/data/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/egui/97efd554bf12e948ac8e51240fd6f177b3d87bb3/crates/eframe/data/icon.png
--------------------------------------------------------------------------------
/crates/eframe/src/epi/icon_data.rs:
--------------------------------------------------------------------------------
1 | /// Image data for an application icon.
2 | ///
3 | /// Use a square image, e.g. 256x256 pixels.
4 | /// You can use a transparent background.
5 | #[derive(Clone)]
6 | pub struct IconData {
7 | /// RGBA pixels, with separate/unmultiplied alpha.
8 | pub rgba: Vec,
9 |
10 | /// Image width. This should be a multiple of 4.
11 | pub width: u32,
12 |
13 | /// Image height. This should be a multiple of 4.
14 | pub height: u32,
15 | }
16 |
17 | impl IconData {
18 | /// Convert into [`image::RgbaImage`]
19 | ///
20 | /// # Errors
21 | /// If this is not a valid png.
22 | pub fn try_from_png_bytes(png_bytes: &[u8]) -> Result {
23 | crate::profile_function!();
24 | let image = image::load_from_memory(png_bytes)?;
25 | Ok(Self::from_image(image))
26 | }
27 |
28 | fn from_image(image: image::DynamicImage) -> Self {
29 | let image = image.into_rgba8();
30 | Self {
31 | width: image.width(),
32 | height: image.height(),
33 | rgba: image.into_raw(),
34 | }
35 | }
36 |
37 | /// Convert into [`image::RgbaImage`]
38 | ///
39 | /// # Errors
40 | /// If `width*height != 4 * rgba.len()`, or if the image is too big.
41 | pub fn to_image(&self) -> Result {
42 | crate::profile_function!();
43 | let Self {
44 | rgba,
45 | width,
46 | height,
47 | } = self.clone();
48 | image::RgbaImage::from_raw(width, height, rgba).ok_or_else(|| "Invalid IconData".to_owned())
49 | }
50 |
51 | /// Encode as PNG.
52 | ///
53 | /// # Errors
54 | /// The image is invalid, or the PNG encoder failed.
55 | pub fn to_png_bytes(&self) -> Result, String> {
56 | crate::profile_function!();
57 | let image = self.to_image()?;
58 | let mut png_bytes: Vec = Vec::new();
59 | image
60 | .write_to(
61 | &mut std::io::Cursor::new(&mut png_bytes),
62 | image::ImageOutputFormat::Png,
63 | )
64 | .map_err(|err| err.to_string())?;
65 | Ok(png_bytes)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/crates/eframe/src/native/file_storage.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | collections::HashMap,
3 | path::{Path, PathBuf},
4 | };
5 |
6 | // ----------------------------------------------------------------------------
7 |
8 | /// A key-value store backed by a [RON](https://github.com/ron-rs/ron) file on disk.
9 | /// Used to restore egui state, glium window position/size and app state.
10 | pub struct FileStorage {
11 | ron_filepath: PathBuf,
12 | kv: HashMap,
13 | dirty: bool,
14 | last_save_join_handle: Option>,
15 | }
16 |
17 | impl Drop for FileStorage {
18 | fn drop(&mut self) {
19 | if let Some(join_handle) = self.last_save_join_handle.take() {
20 | join_handle.join().ok();
21 | }
22 | }
23 | }
24 |
25 | impl FileStorage {
26 | /// Store the state in this .ron file.
27 | pub fn from_ron_filepath(ron_filepath: impl Into) -> Self {
28 | let ron_filepath: PathBuf = ron_filepath.into();
29 | log::debug!("Loading app state from {:?}…", ron_filepath);
30 | Self {
31 | kv: read_ron(&ron_filepath).unwrap_or_default(),
32 | ron_filepath,
33 | dirty: false,
34 | last_save_join_handle: None,
35 | }
36 | }
37 |
38 | /// Find a good place to put the files that the OS likes.
39 | pub fn from_app_name(app_name: &str) -> Option {
40 | if let Some(proj_dirs) = directories_next::ProjectDirs::from("", "", app_name) {
41 | let data_dir = proj_dirs.data_dir().to_path_buf();
42 | if let Err(err) = std::fs::create_dir_all(&data_dir) {
43 | log::warn!(
44 | "Saving disabled: Failed to create app path at {:?}: {}",
45 | data_dir,
46 | err
47 | );
48 | None
49 | } else {
50 | Some(Self::from_ron_filepath(data_dir.join("app.ron")))
51 | }
52 | } else {
53 | log::warn!("Saving disabled: Failed to find path to data_dir.");
54 | None
55 | }
56 | }
57 | }
58 |
59 | impl crate::Storage for FileStorage {
60 | fn get_string(&self, key: &str) -> Option {
61 | self.kv.get(key).cloned()
62 | }
63 |
64 | fn set_string(&mut self, key: &str, value: String) {
65 | if self.kv.get(key) != Some(&value) {
66 | self.kv.insert(key.to_owned(), value);
67 | self.dirty = true;
68 | }
69 | }
70 |
71 | fn flush(&mut self) {
72 | if self.dirty {
73 | self.dirty = false;
74 |
75 | let file_path = self.ron_filepath.clone();
76 | let kv = self.kv.clone();
77 |
78 | if let Some(join_handle) = self.last_save_join_handle.take() {
79 | // wait for previous save to complete.
80 | join_handle.join().ok();
81 | }
82 |
83 | let join_handle = std::thread::spawn(move || {
84 | let file = std::fs::File::create(&file_path).unwrap();
85 | let config = Default::default();
86 | ron::ser::to_writer_pretty(file, &kv, config).unwrap();
87 | log::trace!("Persisted to {:?}", file_path);
88 | });
89 |
90 | self.last_save_join_handle = Some(join_handle);
91 | }
92 | }
93 | }
94 |
95 | // ----------------------------------------------------------------------------
96 |
97 | fn read_ron(ron_path: impl AsRef) -> Option
98 | where
99 | T: serde::de::DeserializeOwned,
100 | {
101 | match std::fs::File::open(ron_path) {
102 | Ok(file) => {
103 | let reader = std::io::BufReader::new(file);
104 | match ron::de::from_reader(reader) {
105 | Ok(value) => Some(value),
106 | Err(err) => {
107 | log::warn!("Failed to parse RON: {}", err);
108 | None
109 | }
110 | }
111 | }
112 | Err(_err) => {
113 | // File probably doesn't exist. That's fine.
114 | None
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/crates/eframe/src/native/mod.rs:
--------------------------------------------------------------------------------
1 | mod app_icon;
2 | pub mod epi_integration;
3 | pub mod run;
4 |
5 | /// File storage which can be used by native backends.
6 | #[cfg(feature = "persistence")]
7 | pub mod file_storage;
8 |
--------------------------------------------------------------------------------
/crates/eframe/src/web/backend.rs:
--------------------------------------------------------------------------------
1 | use std::collections::BTreeMap;
2 |
3 | use egui::mutex::Mutex;
4 |
5 | use crate::epi;
6 |
7 | use super::percent_decode;
8 |
9 | // ----------------------------------------------------------------------------
10 |
11 | /// Data gathered between frames.
12 | #[derive(Default)]
13 | pub struct WebInput {
14 | /// Required because we don't get a position on touched
15 | pub latest_touch_pos: Option,
16 |
17 | /// Required to maintain a stable touch position for multi-touch gestures.
18 | pub latest_touch_pos_id: Option,
19 |
20 | pub raw: egui::RawInput,
21 | }
22 |
23 | impl WebInput {
24 | pub fn new_frame(&mut self, canvas_size: egui::Vec2) -> egui::RawInput {
25 | egui::RawInput {
26 | screen_rect: Some(egui::Rect::from_min_size(Default::default(), canvas_size)),
27 | pixels_per_point: Some(super::native_pixels_per_point()), // We ALWAYS use the native pixels-per-point
28 | time: Some(super::now_sec()),
29 | ..self.raw.take()
30 | }
31 | }
32 |
33 | pub fn on_web_page_focus_change(&mut self, focused: bool) {
34 | self.raw.modifiers = egui::Modifiers::default();
35 | self.raw.focused = focused;
36 | self.raw.events.push(egui::Event::WindowFocused(focused));
37 | self.latest_touch_pos = None;
38 | self.latest_touch_pos_id = None;
39 | }
40 | }
41 |
42 | // ----------------------------------------------------------------------------
43 |
44 | use std::sync::atomic::Ordering::SeqCst;
45 |
46 | /// Stores when to do the next repaint.
47 | pub struct NeedRepaint(Mutex);
48 |
49 | impl Default for NeedRepaint {
50 | fn default() -> Self {
51 | Self(Mutex::new(f64::NEG_INFINITY)) // start with a repaint
52 | }
53 | }
54 |
55 | impl NeedRepaint {
56 | /// Returns the time (in [`now_sec`] scale) when
57 | /// we should next repaint.
58 | pub fn when_to_repaint(&self) -> f64 {
59 | *self.0.lock()
60 | }
61 |
62 | /// Unschedule repainting.
63 | pub fn clear(&self) {
64 | *self.0.lock() = f64::INFINITY;
65 | }
66 |
67 | pub fn repaint_after(&self, num_seconds: f64) {
68 | let mut repaint_time = self.0.lock();
69 | *repaint_time = repaint_time.min(super::now_sec() + num_seconds);
70 | }
71 |
72 | pub fn repaint_asap(&self) {
73 | *self.0.lock() = f64::NEG_INFINITY;
74 | }
75 | }
76 |
77 | pub struct IsDestroyed(std::sync::atomic::AtomicBool);
78 |
79 | impl Default for IsDestroyed {
80 | fn default() -> Self {
81 | Self(false.into())
82 | }
83 | }
84 |
85 | impl IsDestroyed {
86 | pub fn fetch(&self) -> bool {
87 | self.0.load(SeqCst)
88 | }
89 |
90 | pub fn set_true(&self) {
91 | self.0.store(true, SeqCst);
92 | }
93 | }
94 |
95 | // ----------------------------------------------------------------------------
96 |
97 | pub fn user_agent() -> Option {
98 | web_sys::window()?.navigator().user_agent().ok()
99 | }
100 |
101 | pub fn web_location() -> epi::Location {
102 | let location = web_sys::window().unwrap().location();
103 |
104 | let hash = percent_decode(&location.hash().unwrap_or_default());
105 |
106 | let query = location
107 | .search()
108 | .unwrap_or_default()
109 | .strip_prefix('?')
110 | .map(percent_decode)
111 | .unwrap_or_default();
112 |
113 | let query_map = parse_query_map(&query)
114 | .iter()
115 | .map(|(k, v)| ((*k).to_owned(), (*v).to_owned()))
116 | .collect();
117 |
118 | epi::Location {
119 | url: percent_decode(&location.href().unwrap_or_default()),
120 | protocol: percent_decode(&location.protocol().unwrap_or_default()),
121 | host: percent_decode(&location.host().unwrap_or_default()),
122 | hostname: percent_decode(&location.hostname().unwrap_or_default()),
123 | port: percent_decode(&location.port().unwrap_or_default()),
124 | hash,
125 | query,
126 | query_map,
127 | origin: percent_decode(&location.origin().unwrap_or_default()),
128 | }
129 | }
130 |
131 | fn parse_query_map(query: &str) -> BTreeMap<&str, &str> {
132 | query
133 | .split('&')
134 | .filter_map(|pair| {
135 | if pair.is_empty() {
136 | None
137 | } else {
138 | Some(if let Some((key, value)) = pair.split_once('=') {
139 | (key, value)
140 | } else {
141 | (pair, "")
142 | })
143 | }
144 | })
145 | .collect()
146 | }
147 |
148 | #[test]
149 | fn test_parse_query() {
150 | assert_eq!(parse_query_map(""), BTreeMap::default());
151 | assert_eq!(parse_query_map("foo"), BTreeMap::from_iter([("foo", "")]));
152 | assert_eq!(
153 | parse_query_map("foo=bar"),
154 | BTreeMap::from_iter([("foo", "bar")])
155 | );
156 | assert_eq!(
157 | parse_query_map("foo=bar&baz=42"),
158 | BTreeMap::from_iter([("foo", "bar"), ("baz", "42")])
159 | );
160 | assert_eq!(
161 | parse_query_map("foo&baz=42"),
162 | BTreeMap::from_iter([("foo", ""), ("baz", "42")])
163 | );
164 | assert_eq!(
165 | parse_query_map("foo&baz&&"),
166 | BTreeMap::from_iter([("foo", ""), ("baz", "")])
167 | );
168 | }
169 |
--------------------------------------------------------------------------------
/crates/eframe/src/web/input.rs:
--------------------------------------------------------------------------------
1 | use super::{canvas_element, canvas_origin, AppRunner};
2 |
3 | pub fn pos_from_mouse_event(canvas_id: &str, event: &web_sys::MouseEvent) -> egui::Pos2 {
4 | let canvas = canvas_element(canvas_id).unwrap();
5 | let rect = canvas.get_bounding_client_rect();
6 | egui::Pos2 {
7 | x: event.client_x() as f32 - rect.left() as f32,
8 | y: event.client_y() as f32 - rect.top() as f32,
9 | }
10 | }
11 |
12 | pub fn button_from_mouse_event(event: &web_sys::MouseEvent) -> Option {
13 | match event.button() {
14 | 0 => Some(egui::PointerButton::Primary),
15 | 1 => Some(egui::PointerButton::Middle),
16 | 2 => Some(egui::PointerButton::Secondary),
17 | 3 => Some(egui::PointerButton::Extra1),
18 | 4 => Some(egui::PointerButton::Extra2),
19 | _ => None,
20 | }
21 | }
22 |
23 | /// A single touch is translated to a pointer movement. When a second touch is added, the pointer
24 | /// should not jump to a different position. Therefore, we do not calculate the average position
25 | /// of all touches, but we keep using the same touch as long as it is available.
26 | ///
27 | /// `touch_id_for_pos` is the [`TouchId`](egui::TouchId) of the [`Touch`](web_sys::Touch) we previously used to determine the
28 | /// pointer position.
29 | pub fn pos_from_touch_event(
30 | canvas_id: &str,
31 | event: &web_sys::TouchEvent,
32 | touch_id_for_pos: &mut Option,
33 | ) -> egui::Pos2 {
34 | let touch_for_pos = if let Some(touch_id_for_pos) = touch_id_for_pos {
35 | // search for the touch we previously used for the position
36 | // (unfortunately, `event.touches()` is not a rust collection):
37 | (0..event.touches().length())
38 | .into_iter()
39 | .map(|i| event.touches().get(i).unwrap())
40 | .find(|touch| egui::TouchId::from(touch.identifier()) == *touch_id_for_pos)
41 | } else {
42 | None
43 | };
44 | // Use the touch found above or pick the first, or return a default position if there is no
45 | // touch at all. (The latter is not expected as the current method is only called when there is
46 | // at least one touch.)
47 | touch_for_pos
48 | .or_else(|| event.touches().get(0))
49 | .map_or(Default::default(), |touch| {
50 | *touch_id_for_pos = Some(egui::TouchId::from(touch.identifier()));
51 | pos_from_touch(canvas_origin(canvas_id), &touch)
52 | })
53 | }
54 |
55 | fn pos_from_touch(canvas_origin: egui::Pos2, touch: &web_sys::Touch) -> egui::Pos2 {
56 | egui::Pos2 {
57 | x: touch.page_x() as f32 - canvas_origin.x,
58 | y: touch.page_y() as f32 - canvas_origin.y,
59 | }
60 | }
61 |
62 | pub fn push_touches(runner: &mut AppRunner, phase: egui::TouchPhase, event: &web_sys::TouchEvent) {
63 | let canvas_origin = canvas_origin(runner.canvas_id());
64 | for touch_idx in 0..event.changed_touches().length() {
65 | if let Some(touch) = event.changed_touches().item(touch_idx) {
66 | runner.input.raw.events.push(egui::Event::Touch {
67 | device_id: egui::TouchDeviceId(0),
68 | id: egui::TouchId::from(touch.identifier()),
69 | phase,
70 | pos: pos_from_touch(canvas_origin, &touch),
71 | force: touch.force(),
72 | });
73 | }
74 | }
75 | }
76 |
77 | /// Web sends all keys as strings, so it is up to us to figure out if it is
78 | /// a real text input or the name of a key.
79 | pub fn should_ignore_key(key: &str) -> bool {
80 | let is_function_key = key.starts_with('F') && key.len() > 1;
81 | is_function_key
82 | || matches!(
83 | key,
84 | "Alt"
85 | | "ArrowDown"
86 | | "ArrowLeft"
87 | | "ArrowRight"
88 | | "ArrowUp"
89 | | "Backspace"
90 | | "CapsLock"
91 | | "ContextMenu"
92 | | "Control"
93 | | "Delete"
94 | | "End"
95 | | "Enter"
96 | | "Esc"
97 | | "Escape"
98 | | "GroupNext" // https://github.com/emilk/egui/issues/510
99 | | "Help"
100 | | "Home"
101 | | "Insert"
102 | | "Meta"
103 | | "NumLock"
104 | | "PageDown"
105 | | "PageUp"
106 | | "Pause"
107 | | "ScrollLock"
108 | | "Shift"
109 | | "Tab"
110 | )
111 | }
112 |
113 | /// Web sends all all keys as strings, so it is up to us to figure out if it is
114 | /// a real text input or the name of a key.
115 | pub fn translate_key(key: &str) -> Option {
116 | use egui::Key;
117 |
118 | match key {
119 | "ArrowDown" => Some(Key::ArrowDown),
120 | "ArrowLeft" => Some(Key::ArrowLeft),
121 | "ArrowRight" => Some(Key::ArrowRight),
122 | "ArrowUp" => Some(Key::ArrowUp),
123 |
124 | "Esc" | "Escape" => Some(Key::Escape),
125 | "Tab" => Some(Key::Tab),
126 | "Backspace" => Some(Key::Backspace),
127 | "Enter" => Some(Key::Enter),
128 | "Space" | " " => Some(Key::Space),
129 |
130 | "Help" | "Insert" => Some(Key::Insert),
131 | "Delete" => Some(Key::Delete),
132 | "Home" => Some(Key::Home),
133 | "End" => Some(Key::End),
134 | "PageUp" => Some(Key::PageUp),
135 | "PageDown" => Some(Key::PageDown),
136 |
137 | "-" => Some(Key::Minus),
138 | "+" | "=" => Some(Key::PlusEquals),
139 |
140 | "0" => Some(Key::Num0),
141 | "1" => Some(Key::Num1),
142 | "2" => Some(Key::Num2),
143 | "3" => Some(Key::Num3),
144 | "4" => Some(Key::Num4),
145 | "5" => Some(Key::Num5),
146 | "6" => Some(Key::Num6),
147 | "7" => Some(Key::Num7),
148 | "8" => Some(Key::Num8),
149 | "9" => Some(Key::Num9),
150 |
151 | "a" | "A" => Some(Key::A),
152 | "b" | "B" => Some(Key::B),
153 | "c" | "C" => Some(Key::C),
154 | "d" | "D" => Some(Key::D),
155 | "e" | "E" => Some(Key::E),
156 | "f" | "F" => Some(Key::F),
157 | "g" | "G" => Some(Key::G),
158 | "h" | "H" => Some(Key::H),
159 | "i" | "I" => Some(Key::I),
160 | "j" | "J" => Some(Key::J),
161 | "k" | "K" => Some(Key::K),
162 | "l" | "L" => Some(Key::L),
163 | "m" | "M" => Some(Key::M),
164 | "n" | "N" => Some(Key::N),
165 | "o" | "O" => Some(Key::O),
166 | "p" | "P" => Some(Key::P),
167 | "q" | "Q" => Some(Key::Q),
168 | "r" | "R" => Some(Key::R),
169 | "s" | "S" => Some(Key::S),
170 | "t" | "T" => Some(Key::T),
171 | "u" | "U" => Some(Key::U),
172 | "v" | "V" => Some(Key::V),
173 | "w" | "W" => Some(Key::W),
174 | "x" | "X" => Some(Key::X),
175 | "y" | "Y" => Some(Key::Y),
176 | "z" | "Z" => Some(Key::Z),
177 |
178 | "F1" => Some(Key::F1),
179 | "F2" => Some(Key::F2),
180 | "F3" => Some(Key::F3),
181 | "F4" => Some(Key::F4),
182 | "F5" => Some(Key::F5),
183 | "F6" => Some(Key::F6),
184 | "F7" => Some(Key::F7),
185 | "F8" => Some(Key::F8),
186 | "F9" => Some(Key::F9),
187 | "F10" => Some(Key::F10),
188 | "F11" => Some(Key::F11),
189 | "F12" => Some(Key::F12),
190 | "F13" => Some(Key::F13),
191 | "F14" => Some(Key::F14),
192 | "F15" => Some(Key::F15),
193 | "F16" => Some(Key::F16),
194 | "F17" => Some(Key::F17),
195 | "F18" => Some(Key::F18),
196 | "F19" => Some(Key::F19),
197 | "F20" => Some(Key::F20),
198 |
199 | _ => None,
200 | }
201 | }
202 |
203 | pub fn modifiers_from_event(event: &web_sys::KeyboardEvent) -> egui::Modifiers {
204 | egui::Modifiers {
205 | alt: event.alt_key(),
206 | ctrl: event.ctrl_key(),
207 | shift: event.shift_key(),
208 |
209 | // Ideally we should know if we are running or mac or not,
210 | // but this works good enough for now.
211 | mac_cmd: event.meta_key(),
212 |
213 | // Ideally we should know if we are running or mac or not,
214 | // but this works good enough for now.
215 | command: event.ctrl_key() || event.meta_key(),
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/crates/eframe/src/web/mod.rs:
--------------------------------------------------------------------------------
1 | //! [`egui`] bindings for web apps (compiling to WASM).
2 |
3 | #![allow(clippy::missing_errors_doc)] // So many `-> Result<_, JsValue>`
4 |
5 | mod app_runner;
6 | pub mod backend;
7 | mod events;
8 | mod input;
9 | mod panic_handler;
10 | pub mod screen_reader;
11 | pub mod storage;
12 | mod text_agent;
13 | mod web_logger;
14 | mod web_runner;
15 |
16 | pub(crate) use app_runner::AppRunner;
17 | pub use panic_handler::{PanicHandler, PanicSummary};
18 | pub use web_logger::WebLogger;
19 | pub use web_runner::WebRunner;
20 |
21 | #[cfg(not(any(feature = "glow", feature = "wgpu")))]
22 | compile_error!("You must enable either the 'glow' or 'wgpu' feature");
23 |
24 | mod web_painter;
25 |
26 | #[cfg(feature = "glow")]
27 | mod web_painter_glow;
28 | #[cfg(feature = "glow")]
29 | pub(crate) type ActiveWebPainter = web_painter_glow::WebPainterGlow;
30 |
31 | #[cfg(feature = "wgpu")]
32 | mod web_painter_wgpu;
33 | #[cfg(all(feature = "wgpu", not(feature = "glow")))]
34 | pub(crate) type ActiveWebPainter = web_painter_wgpu::WebPainterWgpu;
35 |
36 | pub use backend::*;
37 | pub use events::*;
38 | pub use storage::*;
39 |
40 | use egui::Vec2;
41 | use wasm_bindgen::prelude::*;
42 | use web_sys::MediaQueryList;
43 |
44 | use input::*;
45 |
46 | use crate::Theme;
47 |
48 | // ----------------------------------------------------------------------------
49 |
50 | /// Current time in seconds (since undefined point in time).
51 | ///
52 | /// Monotonically increasing.
53 | pub fn now_sec() -> f64 {
54 | web_sys::window()
55 | .expect("should have a Window")
56 | .performance()
57 | .expect("should have a Performance")
58 | .now()
59 | / 1000.0
60 | }
61 |
62 | #[allow(dead_code)]
63 | pub fn screen_size_in_native_points() -> Option {
64 | let window = web_sys::window()?;
65 | Some(egui::vec2(
66 | window.inner_width().ok()?.as_f64()? as f32,
67 | window.inner_height().ok()?.as_f64()? as f32,
68 | ))
69 | }
70 |
71 | pub fn native_pixels_per_point() -> f32 {
72 | let pixels_per_point = web_sys::window().unwrap().device_pixel_ratio() as f32;
73 | if pixels_per_point > 0.0 && pixels_per_point.is_finite() {
74 | pixels_per_point
75 | } else {
76 | 1.0
77 | }
78 | }
79 |
80 | pub fn system_theme() -> Option {
81 | let dark_mode = prefers_color_scheme_dark(&web_sys::window()?)
82 | .ok()??
83 | .matches();
84 | Some(theme_from_dark_mode(dark_mode))
85 | }
86 |
87 | fn prefers_color_scheme_dark(window: &web_sys::Window) -> Result