Remove unused files and update Readme

This commit is contained in:
DK Liao
2023-06-13 21:08:04 +08:00
parent e4e2599456
commit 36a122610d
99 changed files with 77 additions and 5431 deletions

View File

@@ -197,25 +197,25 @@ jobs:
arguments: ${{ matrix.flags }} --target ${{ matrix.target }}
# ---------------------------------------------------------------------------
# Android won't work, don't even try
# android:
# name: android
# runs-on: ubuntu-22.04
# steps:
# - uses: actions/checkout@v2
android:
name: android
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
# - uses: actions-rs/toolchain@v1
# with:
# profile: minimal
# toolchain: 1.65.0
# target: aarch64-linux-android
# override: true
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.65.0
target: aarch64-linux-android
override: true
# - name: Set up cargo cache
# uses: Swatinem/rust-cache@v2
- name: Set up cargo cache
uses: Swatinem/rust-cache@v2
- run: cargo check --features wgpu,android-native-activity --target aarch64-linux-android
working-directory: crates/eframe
# - run: cargo check --features wgpu,android-native-activity --target aarch64-linux-android
# working-directory: crates/eframe
# ---------------------------------------------------------------------------

947
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,8 +5,6 @@ members = [
"crates/egui_demo_app",
"crates/egui_glow",
"crates/egui-winit",
"examples/*",
]
[profile.release]

411
README.md
View File

@@ -1,62 +1,26 @@
# 🖌 egui: an easy-to-use GUI in pure Rust
# egui
[<img alt="github" src="https://img.shields.io/badge/github-emilk/egui-8da0cb?logo=github" height="20">](https://github.com/emilk/egui)
[![Latest version](https://img.shields.io/crates/v/egui.svg)](https://crates.io/crates/egui)
[![Documentation](https://docs.rs/egui/badge.svg)](https://docs.rs/egui)
[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
[![Build Status](https://github.com/emilk/egui/workflows/CI/badge.svg)](https://github.com/emilk/egui/actions?workflow=CI)
[![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/emilk/egui/blob/master/LICENSE-MIT)
[![Apache](https://img.shields.io/badge/license-Apache-blue.svg)](https://github.com/emilk/egui/blob/master/LICENSE-APACHE)
[![Discord](https://img.shields.io/discord/900275882684477440?label=egui%20discord)](https://discord.gg/JFcEma9bJq)
> egui (pronounced "e-gooey") is a simple, fast, and highly portable immediate mode GUI library for Rust.
>
> egui aims to be the easiest-to-use Rust GUI library, and the simplest way to make a web app in Rust.
👉 [Click to run the web demo](https://www.egui.rs/#demo) 👈
[![](https://img.shields.io/crates/v/egui.svg)](https://crates.io/crates/egui)
[![Docs.rs](https://docs.rs/egui/badge.svg)](https://docs.rs/egui)
egui (pronounced "e-gooey") is a simple, fast, and highly portable immediate mode GUI library for Rust. egui runs on the web, natively, and [in your favorite game engine](#integrations) (or will soon).
egui aims to be the easiest-to-use Rust GUI library, and the simplest way to make a web app in Rust.
egui can be used anywhere you can draw textured triangles, which means you can easily integrate it into your game engine of choice.
Sections:
* [Example](#example)
* [Quick start](#quick-start)
* [Demo](#demo)
* [Goals](#goals)
* [Who is egui for?](#who-is-egui-for)
* [State / features](#state)
* [Integrations](#integrations)
* [Why immediate mode](#why-immediate-mode)
* [FAQ](#faq)
* [Other](#other)
* [Credits](#credits)
([egui 的中文翻译文档 / chinese translation](https://github.com/Re-Ch-Love/egui-doc-cn/blob/main/README_zh-hans.md))
## Example
``` rust
ui.heading("My egui Application");
ui.horizontal(|ui| {
ui.label("Your name: ");
ui.text_edit_singleline(&mut name);
});
ui.add(egui::Slider::new(&mut age, 0..=120).text("age"));
if ui.button("Click each year").clicked() {
age += 1;
}
ui.label(format!("Hello '{name}', age {age}"));
```
[dependencies]
egui = "0.22.0"
```
<img src="media/demo.gif">
This repository provides binding for egui to use tao instead. Currently only `glow` is supported.
## Quick start
For more information on how to use egui, please check out [egui repository](https://github.com/emilk/egui) for both simple examples and detailed document.
There are simple examples in [the `examples/` folder](https://github.com/emilk/egui/blob/master/examples/). If you want to write a web app, then go to <https://github.com/emilk/eframe_template/> and follow the instructions. The official docs are at <https://docs.rs/egui>. For inspiration and more examples, check out the [the egui web demo](https://www.egui.rs/#demo) and follow the links in it to its source code.
## Who is egui for?
If you want to integrate egui into an existing engine, go to the [Integrations](#integrations) section.
Quoting from egui repository:
If you have questions, use [GitHub Discussions](https://github.com/emilk/egui/discussions). There is also [an egui discord server](https://discord.gg/JFcEma9bJq). If you want to contribute to egui, please read the [Contributing Guidelines](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md).
> [...] if you are writing something interactive in Rust that needs a simple GUI, egui may be for you.
## Demo
@@ -73,350 +37,3 @@ On Fedora Rawhide you need to run:
`dnf install clang clang-devel clang-tools-extra libxkbcommon-devel pkg-config openssl-devel libxcb-devel gtk3-devel atk fontconfig-devel`
**NOTE**: This is just for the demo app - egui itself is completely platform agnostic!
## Goals
* The easiest to use GUI library
* Responsive: target 60 Hz in debug build
* Friendly: difficult to make mistakes, and shouldn't panic
* Portable: the same code works on the web and as a native app
* Easy to integrate into any environment
* A simple 2D graphics API for custom painting ([`epaint`](https://docs.rs/epaint)).
* No callbacks
* Pure immediate mode
* Extensible: [easy to write your own widgets for egui](https://github.com/emilk/egui/blob/master/crates/egui_demo_lib/src/demo/toggle_switch.rs)
* Modular: You should be able to use small parts of egui and combine them in new ways
* Safe: there is no `unsafe` code in egui
* Minimal dependencies: [`ab_glyph`](https://crates.io/crates/ab_glyph) [`ahash`](https://crates.io/crates/ahash) [`nohash-hasher`](https://crates.io/crates/nohash-hasher) [`parking_lot`](https://crates.io/crates/parking_lot)
egui is *not* a framework. egui is a library you call into, not an environment you program for.
**NOTE**: egui does not claim to have reached all these goals yet! egui is still work in progress.
### Non-goals
* Become the most powerful GUI library
* Native looking interface
* Advanced and flexible layouts (that's fundamentally incompatible with immediate mode)
## Who is egui for?
egui aims to be the best choice when you want a simple way to create a GUI, or you want to add a GUI to a game engine.
If you are not using Rust, egui is not for you. If you want a GUI that looks native, egui is not for you. If you want something that doesn't break when you upgrade it, egui isn't for you (yet).
But if you are writing something interactive in Rust that needs a simple GUI, egui may be for you.
### egui vs Dear ImGui
The obvious alternative to egui is [`imgui-rs`](https://github.com/Gekkio/imgui-rs), the Rust wrapper around the C++ library [Dear ImGui](https://github.com/ocornut/imgui). Dear ImGui is a great library (and the main inspiration for egui), with a lot more features and polish. However, egui provides some benefits for Rust users:
* egui is pure Rust
* egui is easily compiled to WASM
* egui lets you use native Rust string types (`imgui-rs` forces you to use annoying macros and wrappers for zero-terminated strings)
* [Writing your own widgets in egui is simple](https://github.com/emilk/egui/blob/master/crates/egui_demo_lib/src/demo/toggle_switch.rs)
egui also tries to improve your experience in other small ways:
* Windows are automatically sized based on their contents
* Windows are automatically positioned to not overlap with each other
* Some subtle animations make egui come alive
So in summary:
* egui: pure Rust, new, exciting, work in progress
* Dear ImGui: feature rich, well tested, cumbersome Rust integration
## State
egui is in active development. It works well for what it does, but it lacks many features and the interfaces are still in flux. New releases will have breaking changes.
### Features
* Widgets: label, text button, hyperlink, checkbox, radio button, slider, draggable value, text editing, combo box, color picker
* Layouts: horizontal, vertical, columns, automatic wrapping
* Text editing: multiline, copy/paste, undo, emoji supports
* Windows: move, resize, name, minimize and close. Automatically sized and positioned.
* Regions: resizing, vertical scrolling, collapsing headers (sections)
* Rendering: Anti-aliased rendering of lines, circles, text and convex polygons.
* Tooltips on hover
* More
<img src="media/widget_gallery.gif" width="50%">
Light Theme:
<img src="media/light_theme.png" width="50%">
## Integrations
egui is built to be easy to integrate into any existing game engine or platform you are working on.
egui itself doesn't know or care on what OS it is running or how to render things to the screen - that is the job of the egui integration.
An integration needs to do the following each frame:
* **Input**: Gather input (mouse, touches, keyboard, screen size, etc) and give it to egui
* Run the application code
* **Output**: Handle egui output (cursor changes, paste, texture allocations, …)
* **Painting**: Render the triangle mesh egui produces (see [OpenGL example](https://github.com/emilk/egui/blob/master/crates/egui_glow/src/painter.rs))
### Official integrations
These are the official egui integrations:
* [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe) for compiling the same app to web/wasm and desktop/native. Uses `egui-winit` and `egui_glow` or `egui-wgpu`.
* [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow) for rendering egui with [glow](https://github.com/grovesNL/glow) on native and web, and for making native apps.
* [`egui-wgpu`](https://github.com/emilk/egui/tree/master/crates/egui-wgpu) for [wgpu](https://crates.io/crates/wgpu) (WebGPU API).
* [`egui-winit`](https://github.com/emilk/egui/tree/master/crates/egui-winit) for integrating with [winit](https://github.com/rust-windowing/winit).
* [`egui_glium`](https://github.com/emilk/egui/tree/master/crates/egui_glium) for compiling native apps with [Glium](https://github.com/glium/glium) (DEPRECATED - looking for new maintainer).
### 3rd party integrations
* [`amethyst_egui`](https://github.com/jgraef/amethyst_egui) for [the Amethyst game engine](https://amethyst.rs/).
* [`bevy_egui`](https://github.com/mvlabat/bevy_egui) for [the Bevy game engine](https://bevyengine.org/).
* [`egui_glfw_gl`](https://github.com/cohaereo/egui_glfw_gl) for [GLFW](https://crates.io/crates/glfw).
* [`egui-glutin-gl`](https://github.com/h3r2tic/egui-glutin-gl/) for [glutin](https://crates.io/crates/glutin).
* [`egui_sdl2_gl`](https://crates.io/crates/egui_sdl2_gl) for [SDL2](https://crates.io/crates/sdl2).
* [`egui_sdl2_platform`](https://github.com/ComLarsic/egui_sdl2_platform) for [SDL2](https://crates.io/crates/sdl2).
* [`egui_vulkano`](https://github.com/derivator/egui_vulkano) for [Vulkano](https://github.com/vulkano-rs/vulkano).
* [`egui_winit_vulkano`](https://github.com/hakolao/egui_winit_vulkano) for [Vulkano](https://github.com/vulkano-rs/vulkano).
* [`egui-macroquad`](https://github.com/optozorax/egui-macroquad) for [macroquad](https://github.com/not-fl3/macroquad).
* [`egui-miniquad`](https://github.com/not-fl3/egui-miniquad) for [Miniquad](https://github.com/not-fl3/miniquad).
* [`egui_speedy2d`](https://github.com/heretik31/egui_speedy2d) for [Speedy2d](https://github.com/QuantumBadger/Speedy2D).
* [`egui-tetra`](https://crates.io/crates/egui-tetra) for [Tetra](https://crates.io/crates/tetra), a 2D game framework.
* [`egui-winit-ash-integration`](https://github.com/MatchaChoco010/egui-winit-ash-integration) for [winit](https://github.com/rust-windowing/winit) and [ash](https://github.com/MaikKlein/ash).
* [`fltk-egui`](https://crates.io/crates/fltk-egui) for [fltk-rs](https://github.com/fltk-rs/fltk-rs).
* [`ggegui`](https://github.com/NemuiSen/ggegui) for the [ggez](https://ggez.rs/) game framework.
* [`godot-egui`](https://github.com/setzer22/godot-egui) for [godot-rust](https://github.com/godot-rust/godot-rust).
* [`nannou_egui`](https://github.com/nannou-org/nannou/tree/master/nannou_egui) for [nannou](https://nannou.cc).
* [`notan_egui`](https://github.com/Nazariglez/notan/tree/main/crates/notan_egui) for [notan](https://github.com/Nazariglez/notan).
* [`screen-13-egui`](https://github.com/attackgoat/screen-13/tree/master/contrib/screen-13-egui) for [Screen 13](https://github.com/attackgoat/screen-13).
* [`egui_skia`](https://github.com/lucasmerlin/egui_skia) for [skia](https://github.com/rust-skia/rust-skia/tree/master/skia-safe).
* [`smithay-egui`](https://github.com/Smithay/smithay-egui) for [smithay](https://github.com/Smithay/smithay/).
* [`tauri-egui`](https://github.com/tauri-apps/tauri-egui) for [tauri](https://github.com/tauri-apps/tauri).
Missing an integration for the thing you're working on? Create one, it's easy!
### Writing your own egui integration
You need to collect [`egui::RawInput`](https://docs.rs/egui/latest/egui/struct.RawInput.html) and handle [`egui::FullOutput`](https://docs.rs/egui/latest/egui/struct.FullOutput.html). The basic structure is this:
``` rust
let mut egui_ctx = egui::CtxRef::default();
// Game loop:
loop {
// Gather input (mouse, touches, keyboard, screen size, etc):
let raw_input: egui::RawInput = my_integration.gather_input();
let full_output = egui_ctx.run(raw_input, |egui_ctx| {
my_app.ui(egui_ctx); // add panels, windows and widgets to `egui_ctx` here
});
let clipped_primitives = egui_ctx.tessellate(full_output.shapes); // creates triangles to paint
my_integration.paint(&full_output.textures_delta, clipped_primitives);
let platform_output = full_output.platform_output;
my_integration.set_cursor_icon(platform_output.cursor_icon);
if !platform_output.copied_text.is_empty() {
my_integration.set_clipboard_text(platform_output.copied_text);
}
// See `egui::FullOutput` and `egui::PlatformOutput` for more
}
```
For a reference OpenGL backend, see [the `egui_glium` painter](https://github.com/emilk/egui/blob/master/crates/egui_glium/src/painter.rs) or [the `egui_glow` painter](https://github.com/emilk/egui/blob/master/crates/egui_glow/src/painter.rs).
### Debugging your integration
#### Things look jagged
* Turn off backface culling.
#### My text is blurry
* Make sure you set the proper `pixels_per_point` in the input to egui.
* Make sure the texture sampler is not off by half a pixel. Try nearest-neighbor sampler to check.
#### My windows are too transparent or too dark
* egui uses premultiplied alpha, so make sure your blending function is `(ONE, ONE_MINUS_SRC_ALPHA)`.
* Make sure your texture sampler is clamped (`GL_CLAMP_TO_EDGE`).
* egui prefers linear color spaces for all blending so:
* Use an sRGBA-aware texture if available (e.g. `GL_SRGB8_ALPHA8`).
* Otherwise: remember to decode gamma in the fragment shader.
* Decode the gamma of the incoming vertex colors in your vertex shader.
* Turn on sRGBA/linear framebuffer if available (`GL_FRAMEBUFFER_SRGB`).
* Otherwise: gamma-encode the colors before you write them again.
## Why immediate mode
`egui` is an [immediate mode GUI library](https://en.wikipedia.org/wiki/Immediate_mode_GUI), as opposed to a *retained mode* GUI library. The difference between retained mode and immediate mode is best illustrated with the example of a button: In a retained GUI you create a button, add it to some UI and install some on-click handler (callback). The button is retained in the UI, and to change the text on it you need to store some sort of reference to it. By contrast, in immediate mode you show the button and interact with it immediately, and you do so every frame (e.g. 60 times per second). This means there is no need for any on-click handler, nor to store any reference to it. In `egui` this looks like this: `if ui.button("Save file").clicked() { save(file); }`.
A more detailed description of immediate mode can be found [in the `egui` docs](https://docs.rs/egui/latest/egui/#understanding-immediate-mode).
There are advantages and disadvantages to both systems.
The short of it is this: immediate mode GUI libraries are easier to use, but less powerful.
### Advantages of immediate mode
#### Usability
The main advantage of immediate mode is that the application code becomes vastly simpler:
* You never need to have any on-click handlers and callbacks that disrupts your code flow.
* You don't have to worry about a lingering callback calling something that is gone.
* Your GUI code can easily live in a simple function (no need for an object just for the UI).
* You don't have to worry about app state and GUI state being out-of-sync (i.e. the GUI showing something outdated), because the GUI isn't storing any state - it is showing the latest state *immediately*.
In other words, a whole lot of code, complexity and bugs are gone, and you can focus your time on something more interesting than writing GUI code.
### Disadvantages of immediate mode
#### Layout
The main disadvantage of immediate mode is it makes layout more difficult. Say you want to show a small dialog window in the center of the screen. To position the window correctly the GUI library must first know the size of it. To know the size of the window the GUI library must first layout the contents of the window. In retained mode this is easy: the GUI library does the window layout, positions the window, then checks for interaction ("was the OK button clicked?").
In immediate mode you run into a paradox: to know the size of the window, we must do the layout, but the layout code also checks for interaction ("was the OK button clicked?") and so it needs to know the window position *before* showing the window contents. This means we must decide where to show the window *before* we know its size!
This is a fundamental shortcoming of immediate mode GUIs, and any attempt to resolve it comes with its own downsides.
One workaround is to store the size and use it the next frame. This produces a frame-delay for the correct layout, producing occasional flickering the first frame something shows up. `egui` does this for some things such as windows and grid layouts.
You can also call the layout code twice (once to get the size, once to do the interaction), but that is not only more expensive, it's also complex to implement, and in some cases twice is not enough. `egui` never does this.
For "atomic" widgets (e.g. a button) `egui` knows the size before showing it, so centering buttons, labels etc is possible in `egui` without any special workarounds.
#### CPU usage
Since an immediate mode GUI does a full layout each frame, the layout code needs to be quick. If you have a very complex GUI this can tax the CPU. In particular, having a very large UI in a scroll area (with very long scrollback) can be slow, as the content needs to be laid out each frame.
If you design the GUI with this in mind and refrain from huge scroll areas (or only lay out the part that is in view) then the performance hit is generally pretty small. For most cases you can expect `egui` to take up 1-2 ms per frame, but `egui` still has a lot of room for optimization (it's not something I've focused on yet). You can also set up `egui` to only repaint when there is interaction (e.g. mouse movement).
If your GUI is highly interactive, then immediate mode may actually be more performant compared to retained mode. Go to any web page and resize the browser window, and you'll notice that the browser is very slow to do the layout and eats a lot of CPU doing it. Resize a window in `egui` by contrast, and you'll get smooth 60 FPS at no extra CPU cost.
#### IDs
There are some GUI state that you want the GUI library to retain, even in an immediate mode library such as `egui`. This includes position and sizes of windows and how far the user has scrolled in some UI. In these cases you need to provide `egui` with a seed of a unique identifier (unique within the parent UI). For instance: by default `egui` uses the window titles as unique IDs to store window positions. If you want two windows with the same name (or one window with a dynamic name) you must provide some other ID source to `egui` (some unique integer or string).
`egui` also needs to track which widget is being interacted with (e.g. which slider is being dragged). `egui` uses unique id:s for this awell, but in this case the IDs are automatically generated, so there is no need for the user to worry about it. In particular, having two buttons with the same name is no problem (this is in contrast with [`Dear ImGui`](https://github.com/ocornut/imgui)).
Overall, ID handling is a rare inconvenience, and not a big disadvantage.
## FAQ
Also see [GitHub Discussions](https://github.com/emilk/egui/discussions/categories/q-a).
### Can I use `egui` with non-latin characters?
Yes! But you need to install your own font (`.ttf` or `.otf`) using `Context::set_fonts`.
### Can I customize the look of egui?
Yes! You can customize the colors, spacing, fonts and sizes of everything using `Context::set_style`.
Here is an example (from https://github.com/AlexxxRu/TinyPomodoro):
<img src="media/pompodoro-skin.png" width="50%">
### How do I use egui with `async`?
If you call `.await` in your GUI code, the UI will freeze, which is very bad UX. Instead, keep the GUI thread non-blocking and communicate with any concurrent tasks (`async` tasks or other threads) with something like:
* Channels (e.g. [`std::sync::mpsc::channel`](https://doc.rust-lang.org/std/sync/mpsc/fn.channel.html)). Make sure to use [`try_recv`](https://doc.rust-lang.org/std/sync/mpsc/struct.Receiver.html#method.try_recv) so you don't block the gui thread!
* `Arc<Mutex<Value>>` (background thread sets a value; GUI thread reads it)
* [`poll_promise::Promise`](https://docs.rs/poll-promise) (example: [`examples/download_image/`](https://github.com/emilk/egui/blob/master/examples/download_image/))
* [`eventuals::Eventual`](https://docs.rs/eventuals/latest/eventuals/struct.Eventual.html)
* [`tokio::sync::watch::channel`](https://docs.rs/tokio/latest/tokio/sync/watch/fn.channel.html)
### What about accessibility, such as screen readers?
egui includes optional support for [AccessKit](https://accesskit.dev/), which currently implements the native accessibility APIs on Windows and macOS. This feature is enabled by default in eframe. For platforms that AccessKit doesn't yet support, including web, there is an experimental built-in screen reader; in [the web demo](https://www.egui.rs/#demo) you can enable it in the "Backend" tab.
The original discussion of accessibility in egui is at <https://github.com/emilk/egui/issues/167>. Now that AccessKit support is merged, providing a strong foundation for future accessibility work, please open new issues on specific accessibility problems.
### What is the difference between [egui](https://docs.rs/egui) and [eframe](https://github.com/emilk/egui/tree/master/crates/eframe)?
`egui` is a 2D user interface library for laying out and interacting with buttons, sliders, etc.
`egui` has no idea if it is running on the web or natively, and does not know how to collect input or show things on screen.
That is the job of *the integration* or *backend*.
It is common to use `egui` from a game engine (using e.g. [`bevy_egui`](https://docs.rs/bevy_egui)),
but you can also use `egui` stand-alone using `eframe`. `eframe` has integration for web and native, and handles input and rendering.
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).
### How do I render 3D stuff in an egui area?
There are multiple ways to combine egui with 3D. The simplest way is to use a 3D library and have egui sit on top of the 3D view. See for instance [`bevy_egui`](https://github.com/mvlabat/bevy_egui) or [`three-d`](https://github.com/asny/three-d).
If you want to embed 3D into an egui view there are two options.
#### `Shape::Callback`
Example:
* <https://github.com/emilk/egui/blob/master/examples/custom_3d_glow.rs>
`Shape::Callback` will call your code when egui gets painted, to show anything using whatever the background rendering context is. When using [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe) this will be [`glow`](https://github.com/grovesNL/glow). Other integrations will give you other rendering contexts, if they support `Shape::Callback` at all.
#### Render-to-texture
You can also render your 3D scene to a texture and display it using [`ui.image(…)`](https://docs.rs/egui/latest/egui/struct.Ui.html#method.image). You first need to convert the native texture to an [`egui::TextureId`](https://docs.rs/egui/latest/egui/enum.TextureId.html), and how to do this depends on the integration you use.
Examples:
* Using [`egui-miniquad`]( https://github.com/not-fl3/egui-miniquad): https://github.com/not-fl3/egui-miniquad/blob/master/examples/render_to_egui_image.rs
* Using [`egui_glium`](https://github.com/emilk/egui/tree/master/crates/egui_glium): <https://github.com/emilk/egui/blob/master/crates/egui_glium/examples/native_texture.rs>.
## Other
### Conventions and design choices
All coordinates are in screen space coordinates, with (0, 0) in the top left corner
All coordinates are in "points" which may consist of many physical pixels.
All colors have premultiplied alpha.
egui uses the builder pattern for construction widgets. For instance: `ui.add(Label::new("Hello").text_color(RED));` I am not a big fan of the builder pattern (it is quite verbose both in implementation and in use) but until Rust has named, default arguments it is the best we can do. To alleviate some of the verbosity there are common-case helper functions, like `ui.label("Hello");`.
Instead of using matching `begin/end` style function calls (which can be error prone) egui prefers to use `FnOnce` closures passed to a wrapping function. Lambdas are a bit ugly though, so I'd like to find a nicer solution to this. More discussion of this at <https://github.com/emilk/egui/issues/1004#issuecomment-1001650754>.
egui uses a single `RwLock` for short-time locks on each access of `Context` data. This is to leave implementation simple and transactional and allow users to run their UI logic in parallel. Instead of creating mutex guards, egui uses closures passed to a wrapping function, e.g. `ctx.input(|i| i.key_down(Key::A))`. This is to make it less likely that a user would accidentally double-lock the `Context`, which would lead to a deadlock.
### Inspiration
The one and only [Dear ImGui](https://github.com/ocornut/imgui) is a great Immediate Mode GUI for C++ which works with many backends. That library revolutionized how I think about GUI code and turned GUI programming from something I hated to do to something I now enjoy.
### Name
The name of the library and the project is "egui" and pronounced as "e-gooey". Please don't write it as "EGUI".
The library was originally called "Emigui", but was renamed to "egui" in 2020.
## Credits
egui author and maintainer: Emil Ernerfeldt [(@emilk](https://github.com/emilk)).
Notable contributions by:
* [@n2](https://github.com/n2): [Mobile web input and IME support](https://github.com/emilk/egui/pull/253).
* [@optozorax](https://github.com/optozorax): [Arbitrary widget data storage](https://github.com/emilk/egui/pull/257).
* [@quadruple-output](https://github.com/quadruple-output): [Multitouch](https://github.com/emilk/egui/pull/306).
* [@EmbersArc](https://github.com/EmbersArc): [Plots](https://github.com/emilk/egui/pulls?q=+is%3Apr+author%3AEmbersArc).
* [@AsmPrgmC3](https://github.com/AsmPrgmC3): [Proper sRGBA blending for web](https://github.com/emilk/egui/pull/650).
* [@AlexApps99](https://github.com/AlexApps99): [`egui_glow`](https://github.com/emilk/egui/pull/685).
* [@mankinskin](https://github.com/mankinskin): [Context menus](https://github.com/emilk/egui/pull/543).
* [@t18b219k](https://github.com/t18b219k): [Port glow painter to web](https://github.com/emilk/egui/pull/868).
* [@danielkeller](https://github.com/danielkeller): [`Context` refactor](https://github.com/emilk/egui/pull/1050).
* [@MaximOsipenko](https://github.com/MaximOsipenko): [`Context` lock refactor](https://github.com/emilk/egui/pull/2625).
* And [many more](https://github.com/emilk/egui/graphs/contributors?type=a).
egui is licensed under [MIT](LICENSE-MIT) OR [Apache-2.0](LICENSE-APACHE).
* The flattening algorithm for the cubic bezier curve and quadratic bezier curve is from [lyon_geom](https://docs.rs/lyon_geom/latest/lyon_geom/)
Default fonts:
* `emoji-icon-font.ttf`: [Copyright (c) 2014 John Slegers](https://github.com/jslegers/emoji-icon-font) , MIT License
* `Hack-Regular.ttf`: <https://github.com/source-foundry/Hack>, [MIT Licence](https://github.com/source-foundry/Hack/blob/master/LICENSE.md)
* `NotoEmoji-Regular.ttf`: [google.com/get/noto](https://google.com/get/noto), [SIL Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL)
* `Ubuntu-Light.ttf` by [Dalton Maag](http://www.daltonmaag.com/): [Ubuntu font licence](https://ubuntu.com/legal/font-licence)
---
<div align="center">
<img src="media/rerun_io_logo.png" width="50%">
egui development is sponsored by [Rerun](https://www.rerun.io/), a startup doing<br>
visualizations for computer vision and robotics.
</div>

View File

@@ -1,10 +0,0 @@
# https://github.com/crate-ci/typos
# install: cargo install typos-cli
# run: typos
[default.extend-words]
nknown = "nknown" # part of @55nknown username
Prefence = "Prefence" # typo in glutin_winit API
[files]
extend-exclude = ["docs/egui_demo_app.js"] # auto-generated

View File

@@ -54,7 +54,9 @@ persistence = [
##
## Only enabled on native, because of the low resolution (1ms) of time keeping in browsers.
## `eframe` will call `puffin::GlobalProfiler::lock().new_frame()` for you
puffin = ["dep:puffin", "egui_glow?/puffin", "egui-wgpu?/puffin"]
## egui-tao: we currently don't support wgpu so remove egui-wgpu dependency for now
## puffin = ["dep:puffin", "egui_glow?/puffin", "egui-wgpu?/puffin"]
puffin = ["dep:puffin", "egui_glow?/puffin"]
## Enable screen reader support (requires `ctx.options_mut(|o| o.screen_reader = true);`) on web.
##
@@ -67,7 +69,7 @@ __screenshot = []
## Use [`wgpu`](https://docs.rs/wgpu) for painting (via [`egui-wgpu`](https://github.com/emilk/egui/tree/master/crates/egui-wgpu)).
## This overrides the `glow` feature.
wgpu = ["dep:wgpu", "dep:egui-wgpu", "dep:pollster", "dep:raw-window-handle"]
## wgpu = ["dep:wgpu", "dep:egui-wgpu", "dep:pollster", "dep:raw-window-handle"]
[dependencies]
egui = { version = "0.22.0", default-features = false, features = [
@@ -101,9 +103,9 @@ winit = {package = "tao", version = "0.18.0"}
# optional native:
directories-next = { version = "2", optional = true }
egui-wgpu = { version = "0.22.0", optional = true, features = [
"winit",
] } # if wgpu is used, use it with winit
## egui-wgpu = { version = "0.22.0", optional = true, features = [
## "winit",
## ] } # if wgpu is used, use it with winit
pollster = { version = "0.3", optional = true } # needed for wgpu
# we can expose these to user so that they can select which backends they want to enable to avoid compiling useless deps.
@@ -111,7 +113,7 @@ pollster = { version = "0.3", optional = true } # needed for wgpu
glutin = { version = "0.30", optional = true }
glutin-winit = { package = "glutin_tao", version = "0.33.0", optional = true }
puffin = { version = "0.15", optional = true }
wgpu = { version = "0.16.0", optional = true }
## wgpu = { version = "0.16.0", optional = true }
# mac:
[target.'cfg(any(target_os = "macos"))'.dependencies]
@@ -175,7 +177,7 @@ web-sys = { version = "0.3.58", features = [
] }
# optional web:
egui-wgpu = { version = "0.22.0", optional = true } # if wgpu is used, use it without (!) winit
## egui-wgpu = { version = "0.22.0", optional = true } # if wgpu is used, use it without (!) winit
raw-window-handle = { version = "0.5.2", optional = true }
tts = { version = "0.25", optional = true, default-features = false }
wgpu = { version = "0.16.0", optional = true }
## wgpu = { version = "0.16.0", optional = true }

View File

@@ -25,7 +25,7 @@ serde = ["dep:serde", "egui_demo_lib/serde", "egui/serde"]
syntax_highlighting = ["egui_demo_lib/syntax_highlighting"]
glow = ["eframe/glow"]
wgpu = ["eframe/wgpu", "bytemuck"]
## wgpu = ["eframe/wgpu", "bytemuck"]
[dependencies]

View File

@@ -1 +0,0 @@
www.egui.rs

View File

@@ -1,3 +0,0 @@
This folder contains the files required for the egui web demo hosted at <https://www.egui.rs/>.
The reason the folder is called "docs" is because that is the name that GitHub requires in order to host a web page from the `master` branch of a repository.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -1,12 +0,0 @@
<!DOCTYPE html>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<head>
<meta http-equiv="refresh" content="0; url = https://www.egui.rs/#http" />
</head>
<body>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,198 +0,0 @@
<!DOCTYPE html>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<!-- Disable zooming: -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<head>
<title>egui An immediate mode GUI written in Rust</title>
<style>
html {
/* Remove touch delay: */
touch-action: manipulation;
}
body {
/* Light mode background color for what is not covered by the egui canvas,
or where the egui canvas is translucent. */
background: #909090;
}
@media (prefers-color-scheme: dark) {
body {
/* Dark mode background color for what is not covered by the egui canvas,
or where the egui canvas is translucent. */
background: #404040;
}
}
/* Allow canvas to fill entire web page: */
html,
body {
overflow: hidden;
margin: 0 !important;
padding: 0 !important;
height: 100%;
width: 100%;
}
/* Position canvas in center-top: */
canvas {
margin-right: auto;
margin-left: auto;
display: block;
position: absolute;
top: 0%;
left: 50%;
transform: translate(-50%, 0%);
}
.centered {
margin-right: auto;
margin-left: auto;
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #f0f0f0;
font-size: 24px;
font-family: Ubuntu-Light, Helvetica, sans-serif;
text-align: center;
}
/* ---------------------------------------------- */
/* Loading animation from https://loading.io/css/ */
.lds-dual-ring {
display: inline-block;
width: 24px;
height: 24px;
}
.lds-dual-ring:after {
content: " ";
display: block;
width: 24px;
height: 24px;
margin: 0px;
border-radius: 50%;
border: 3px solid #fff;
border-color: #fff transparent #fff transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
@keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<!-- The WASM code will resize the canvas dynamically -->
<canvas id="the_canvas_id"></canvas>
<div class="centered" id="center_text">
<p style="font-size:16px">
Loading…
</p>
<div class="lds-dual-ring"></div>
</div>
<script>
// The `--no-modules`-generated JS from `wasm-bindgen` attempts to use
// `WebAssembly.instantiateStreaming` to instantiate the wasm module,
// but this doesn't work with `file://` urls. This example is frequently
// viewed by simply opening `index.html` in a browser (with a `file://`
// url), so it would fail if we were to call this function!
//
// Work around this for now by deleting the function to ensure that the
// `no_modules.js` script doesn't have access to it. You won't need this
// hack when deploying over HTTP.
delete WebAssembly.instantiateStreaming;
</script>
<!-- this is the JS generated by the `wasm-bindgen` CLI tool -->
<script src="egui_demo_app.js"></script>
<script>
// We'll defer our execution until the wasm is ready to go.
// Here we tell bindgen the path to the wasm file so it can start
// initialization and return to us a promise when it's done.
console.debug("Loading wasm…");
wasm_bindgen("./egui_demo_app_bg.wasm")
.then(on_wasm_loaded)
.catch(on_error);
function on_wasm_loaded() {
console.debug("Wasm loaded. Starting app…");
let handle = new wasm_bindgen.WebHandle();
function check_for_panic() {
if (handle.has_panicked()) {
console.error("The egui app has crashed");
// The demo app already logs the panic message and callstack, but you
// can access them like this if you want to show them in the html:
// console.error(`${handle.panic_message()}`);
// console.error(`${handle.panic_callstack()}`);
document.getElementById("the_canvas_id").remove();
document.getElementById("center_text").innerHTML = `
<p>
The egui app has crashed.
</p>
<p style="font-size:10px" align="left">
${handle.panic_message()}
</p>
<p style="font-size:14px">
See the console for details.
</p>
<p style="font-size:14px">
Reload the page to try again.
</p>`;
} else {
let delay_ms = 1000;
setTimeout(check_for_panic, delay_ms);
}
}
check_for_panic();
handle.start("the_canvas_id").then(on_app_started).catch(on_error);
}
function on_app_started(handle) {
// Call `handle.destroy()` to stop. Uncomment to quick result:
// setTimeout(() => { handle.destroy(); handle.free()) }, 2000)
console.debug("App started.");
document.getElementById("center_text").innerHTML = '';
}
function on_error(error) {
console.error("Failed to start: " + error);
document.getElementById("the_canvas_id").remove();
document.getElementById("center_text").innerHTML = `
<p>
An error occurred during loading:
</p>
<p style="font-family:Courier New">
${error}
</p>
<p style="font-size:14px">
Make sure you use a modern browser with WebGL and WASM enabled.
</p>`;
}
</script>
</body>
</html>
<!-- Powered by egui: https://github.com/emilk/egui/ -->

View File

@@ -1,202 +0,0 @@
<!-- TODO(emilk): make this example nicer. The layout is HORRIBLE -->
<!DOCTYPE html>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<!-- Disable zooming: -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<head>
<title>egui An immediate mode GUI written in Rust</title>
<style>
html {
/* Remove touch delay: */
touch-action: manipulation;
}
body {
/* Light mode background color for what is not covered by the egui canvas,
or where the egui canvas is translucent. */
background: #909090;
display: flex;
}
.canvas_wrap {
/* height: 200px; */
width: 400px;
}
@media (prefers-color-scheme: dark) {
body {
/* Dark mode background color for what is not covered by the egui canvas,
or where the egui canvas is translucent. */
background: #404040;
}
}
/* Allow canvas to fill entire web page: */
html,
body {
overflow: hidden;
margin: 0 !important;
padding: 0 !important;
height: 100%;
width: 100%;
}
/* Position canvas in center-top: */
canvas {
/* margin-right: auto;
margin-left: auto; */
/* display: block;
position: absolute;
top: 0%;
left: 50%;
transform: translate(-50%, 0%); */
width: 45%;
height: 90%;
}
.centered {
margin-right: auto;
margin-left: auto;
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #f0f0f0;
font-size: 24px;
font-family: Ubuntu-Light, Helvetica, sans-serif;
text-align: center;
}
/* ---------------------------------------------- */
/* Loading animation from https://loading.io/css/ */
.lds-dual-ring {
display: inline-block;
width: 24px;
height: 24px;
}
.lds-dual-ring:after {
content: " ";
display: block;
width: 24px;
height: 24px;
margin: 0px;
border-radius: 50%;
border: 3px solid #fff;
border-color: #fff transparent #fff transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
@keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<!-- The WASM code will resize the canvas dynamically -->
<div>controls</div>
<button class="stop_one">
stop
</button>
<div class="canvas_wrap one">
<canvas id="canvas_id_one"></canvas>
</div>
<div class="canvas_wrap two">
<canvas id="canvas_id_two"></canvas>
</div>
<div class="centered" id="center_text">
<p style="font-size:16px">
Loading…
</p>
<div class="lds-dual-ring"></div>
</div>
<script>
// The `--no-modules`-generated JS from `wasm-bindgen` attempts to use
// `WebAssembly.instantiateStreaming` to instantiate the wasm module,
// but this doesn't work with `file://` urls. This example is frequently
// viewed by simply opening `index.html` in a browser (with a `file://`
// url), so it would fail if we were to call this function!
//
// Work around this for now by deleting the function to ensure that the
// `no_modules.js` script doesn't have access to it. You won't need this
// hack when deploying over HTTP.
delete WebAssembly.instantiateStreaming;
</script>
<!-- this is the JS generated by the `wasm-bindgen` CLI tool -->
<script src="egui_demo_app.js"></script>
<script>
// We'll defer our execution until the wasm is ready to go.
// Here we tell bindgen the path to the wasm file so it can start
// initialization and return to us a promise when it's done.
console.debug("Loading wasm…");
wasm_bindgen("./egui_demo_app_bg.wasm")
.then(on_wasm_loaded)
.catch(on_error);
function on_wasm_loaded() {
console.debug("Wasm loaded. Starting apps…");
// This call installs a bunch of callbacks and then returns:
const handle_one = new wasm_bindgen.WebHandle();
const handle_two = new wasm_bindgen.WebHandle();
Promise.all([handle_one.start("canvas_id_one"), handle_two.start("canvas_id_two")]).then((handles) => {
on_apps_started(handle_one, handle_two)
}).catch(on_error);
}
function on_apps_started(handle_one, handle_two) {
const button = document.getElementsByClassName("stop_one")[0]
button.addEventListener("click", () => {
document.getElementById("canvas_id_one").remove()
handle_one.destroy()
handle_one.free()
});
// Call `handle.destroy()` to stop. Uncomment to quick result:
// setTimeout(() => { handle.destroy() }, 2000)
console.debug("Apps started.");
document.getElementById("center_text").remove();
}
function on_error(error) {
console.error("Failed to start: " + error);
document.getElementById("center_text").innerHTML = `
<p>
An error occurred during loading:
</p>
<p style="font-family:Courier New">
${error}
</p>
<p style="font-size:14px">
Make sure you use a modern browser with WebGL and WASM enabled.
</p>`;
}
</script>
</body>
</html>
<!-- Powered by egui: https://github.com/emilk/egui/ -->

View File

@@ -1,10 +0,0 @@
# `egui` and `eframe` examples
All the examples in this folder uses [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe) to set up a window for [`egui`](https://github.com/emilk/egui/). Some examples are specific to `eframe`, but many are applicable to any `egui` integration.
There are a lot more examples at <https://www.egui.rs>, and it has links to the source code of each example.
Also check out the official docs at <https://docs.rs/egui> and <https://docs.rs/eframe>.
Note that all the examples on `master` are for the latest `master` version of `egui`.
If you want to look for examples for a specific version of egui, go to that tag, e.g. <https://github.com/emilk/egui/tree/latest/examples>.

View File

@@ -1,15 +0,0 @@
[package]
name = "confirm_exit"
version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65"
publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = "0.10"

View File

@@ -1,7 +0,0 @@
Example how to show a confirm dialog before exiting an application.
```sh
cargo run -p confirm_exit
```
![](screenshot.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,54 +0,0 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(320.0, 240.0)),
..Default::default()
};
eframe::run_native(
"Confirm exit",
options,
Box::new(|_cc| Box::<MyApp>::default()),
)
}
#[derive(Default)]
struct MyApp {
allowed_to_close: bool,
show_confirmation_dialog: bool,
}
impl eframe::App for MyApp {
fn on_close_event(&mut self) -> bool {
self.show_confirmation_dialog = true;
self.allowed_to_close
}
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Try to close the window");
});
if self.show_confirmation_dialog {
// Show confirmation dialog:
egui::Window::new("Do you want to quit?")
.collapsible(false)
.resizable(false)
.show(ctx, |ui| {
ui.horizontal(|ui| {
if ui.button("Cancel").clicked() {
self.show_confirmation_dialog = false;
}
if ui.button("Yes!").clicked() {
self.allowed_to_close = true;
frame.close();
}
});
});
}
}
}

View File

@@ -1,17 +0,0 @@
[package]
name = "custom_3d_glow"
version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65"
publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
egui_glow = { path = "../../crates/egui_glow" }
env_logger = "0.10"
glow = "0.12"

View File

@@ -1,15 +0,0 @@
This demo shows how to embed 3D rendering using [`glow`](https://github.com/grovesNL/glow) in `eframe`.
This is very advanced usage, and you need to be careful.
If you are content of having egui sit on top of a 3D background, take a look at:
* [`bevy_egui`](https://github.com/mvlabat/bevy_egui)
* [`three-d`](https://github.com/asny/three-d)
```sh
cargo run -p custom_3d_glow
```
![](screenshot.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -1,202 +0,0 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
#![allow(unsafe_code)]
use eframe::egui;
use egui::mutex::Mutex;
use std::sync::Arc;
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(350.0, 380.0)),
multisampling: 4,
renderer: eframe::Renderer::Glow,
..Default::default()
};
eframe::run_native(
"Custom 3D painting in eframe using glow",
options,
Box::new(|cc| Box::new(MyApp::new(cc))),
)
}
struct MyApp {
/// Behind an `Arc<Mutex<…>>` so we can pass it to [`egui::PaintCallback`] and paint later.
rotating_triangle: Arc<Mutex<RotatingTriangle>>,
angle: f32,
}
impl MyApp {
fn new(cc: &eframe::CreationContext<'_>) -> Self {
let gl = cc
.gl
.as_ref()
.expect("You need to run eframe with the glow backend");
Self {
rotating_triangle: Arc::new(Mutex::new(RotatingTriangle::new(gl))),
angle: 0.0,
}
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.label("The triangle is being painted using ");
ui.hyperlink_to("glow", "https://github.com/grovesNL/glow");
ui.label(" (OpenGL).");
});
egui::Frame::canvas(ui.style()).show(ui, |ui| {
self.custom_painting(ui);
});
ui.label("Drag to rotate!");
});
}
fn on_exit(&mut self, gl: Option<&glow::Context>) {
if let Some(gl) = gl {
self.rotating_triangle.lock().destroy(gl);
}
}
}
impl MyApp {
fn custom_painting(&mut self, ui: &mut egui::Ui) {
let (rect, response) =
ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag());
self.angle += response.drag_delta().x * 0.01;
// Clone locals so we can move them into the paint callback:
let angle = self.angle;
let rotating_triangle = self.rotating_triangle.clone();
let callback = egui::PaintCallback {
rect,
callback: std::sync::Arc::new(egui_glow::CallbackFn::new(move |_info, painter| {
rotating_triangle.lock().paint(painter.gl(), angle);
})),
};
ui.painter().add(callback);
}
}
struct RotatingTriangle {
program: glow::Program,
vertex_array: glow::VertexArray,
}
impl RotatingTriangle {
fn new(gl: &glow::Context) -> Self {
use glow::HasContext as _;
let shader_version = if cfg!(target_arch = "wasm32") {
"#version 300 es"
} else {
"#version 330"
};
unsafe {
let program = gl.create_program().expect("Cannot create program");
let (vertex_shader_source, fragment_shader_source) = (
r#"
const vec2 verts[3] = vec2[3](
vec2(0.0, 1.0),
vec2(-1.0, -1.0),
vec2(1.0, -1.0)
);
const vec4 colors[3] = vec4[3](
vec4(1.0, 0.0, 0.0, 1.0),
vec4(0.0, 1.0, 0.0, 1.0),
vec4(0.0, 0.0, 1.0, 1.0)
);
out vec4 v_color;
uniform float u_angle;
void main() {
v_color = colors[gl_VertexID];
gl_Position = vec4(verts[gl_VertexID], 0.0, 1.0);
gl_Position.x *= cos(u_angle);
}
"#,
r#"
precision mediump float;
in vec4 v_color;
out vec4 out_color;
void main() {
out_color = v_color;
}
"#,
);
let shader_sources = [
(glow::VERTEX_SHADER, vertex_shader_source),
(glow::FRAGMENT_SHADER, fragment_shader_source),
];
let shaders: Vec<_> = shader_sources
.iter()
.map(|(shader_type, shader_source)| {
let shader = gl
.create_shader(*shader_type)
.expect("Cannot create shader");
gl.shader_source(shader, &format!("{}\n{}", shader_version, shader_source));
gl.compile_shader(shader);
assert!(
gl.get_shader_compile_status(shader),
"Failed to compile {shader_type}: {}",
gl.get_shader_info_log(shader)
);
gl.attach_shader(program, shader);
shader
})
.collect();
gl.link_program(program);
assert!(
gl.get_program_link_status(program),
"{}",
gl.get_program_info_log(program)
);
for shader in shaders {
gl.detach_shader(program, shader);
gl.delete_shader(shader);
}
let vertex_array = gl
.create_vertex_array()
.expect("Cannot create vertex array");
Self {
program,
vertex_array,
}
}
}
fn destroy(&self, gl: &glow::Context) {
use glow::HasContext as _;
unsafe {
gl.delete_program(self.program);
gl.delete_vertex_array(self.vertex_array);
}
}
fn paint(&self, gl: &glow::Context, angle: f32) {
use glow::HasContext as _;
unsafe {
gl.use_program(Some(self.program));
gl.uniform_1_f32(
gl.get_uniform_location(self.program, "u_angle").as_ref(),
angle,
);
gl.bind_vertex_array(Some(self.vertex_array));
gl.draw_arrays(glow::TRIANGLES, 0, 3);
}
}
}

View File

@@ -1,15 +0,0 @@
[package]
name = "custom_font"
version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65"
publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = "0.10"

View File

@@ -1,7 +0,0 @@
Example of how to use custom fonts.
```sh
cargo run -p custom_font
```
![](screenshot.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -1,69 +0,0 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(320.0, 240.0)),
..Default::default()
};
eframe::run_native(
"egui example: custom font",
options,
Box::new(|cc| Box::new(MyApp::new(cc))),
)
}
fn setup_custom_fonts(ctx: &egui::Context) {
// Start with the default fonts (we will be adding to them rather than replacing them).
let mut fonts = egui::FontDefinitions::default();
// Install my own font (maybe supporting non-latin characters).
// .ttf and .otf files supported.
fonts.font_data.insert(
"my_font".to_owned(),
egui::FontData::from_static(include_bytes!(
"../../../crates/epaint/fonts/Hack-Regular.ttf"
)),
);
// Put my font first (highest priority) for proportional text:
fonts
.families
.entry(egui::FontFamily::Proportional)
.or_default()
.insert(0, "my_font".to_owned());
// Put my font as last fallback for monospace:
fonts
.families
.entry(egui::FontFamily::Monospace)
.or_default()
.push("my_font".to_owned());
// Tell egui to use these fonts:
ctx.set_fonts(fonts);
}
struct MyApp {
text: String,
}
impl MyApp {
fn new(cc: &eframe::CreationContext<'_>) -> Self {
setup_custom_fonts(&cc.egui_ctx);
Self {
text: "Edit this text field if you want".to_owned(),
}
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("egui using custom fonts");
ui.text_edit_multiline(&mut self.text);
});
}
}

View File

@@ -1,15 +0,0 @@
[package]
name = "custom_font_style"
version = "0.1.0"
authors = ["tami5 <kkharji@proton.me>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65"
publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = "0.10"

View File

@@ -1,7 +0,0 @@
Example how to define custom test styles.
```sh
cargo run -p custom_font_style
```
![](screenshot.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -1,72 +0,0 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
use egui::{FontFamily, FontId, RichText, TextStyle};
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions::default();
eframe::run_native(
"egui example: global font style",
options,
Box::new(|cc| Box::new(MyApp::new(cc))),
)
}
#[inline]
fn heading2() -> TextStyle {
TextStyle::Name("Heading2".into())
}
#[inline]
fn heading3() -> TextStyle {
TextStyle::Name("ContextHeading".into())
}
fn configure_text_styles(ctx: &egui::Context) {
use FontFamily::{Monospace, Proportional};
let mut style = (*ctx.style()).clone();
style.text_styles = [
(TextStyle::Heading, FontId::new(25.0, Proportional)),
(heading2(), FontId::new(22.0, Proportional)),
(heading3(), FontId::new(19.0, Proportional)),
(TextStyle::Body, FontId::new(16.0, Proportional)),
(TextStyle::Monospace, FontId::new(12.0, Monospace)),
(TextStyle::Button, FontId::new(12.0, Proportional)),
(TextStyle::Small, FontId::new(8.0, Proportional)),
]
.into();
ctx.set_style(style);
}
fn content(ui: &mut egui::Ui) {
ui.heading("Top Heading");
ui.add_space(5.);
ui.label(LOREM_IPSUM);
ui.add_space(15.);
ui.label(RichText::new("Sub Heading").text_style(heading2()).strong());
ui.monospace(LOREM_IPSUM);
ui.add_space(15.);
ui.label(RichText::new("Context").text_style(heading3()).strong());
ui.add_space(5.);
ui.label(LOREM_IPSUM);
}
struct MyApp;
impl MyApp {
fn new(cc: &eframe::CreationContext<'_>) -> Self {
configure_text_styles(&cc.egui_ctx);
Self
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, content);
}
}
pub const LOREM_IPSUM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";

View File

@@ -1,15 +0,0 @@
[package]
name = "custom_window_frame"
version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65"
publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = "0.10"

View File

@@ -1,7 +0,0 @@
Example how to show a custom window frame instead of the default OS window chrome decorations.
```sh
cargo run -p custom_window_frame
```
![](screenshot.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,165 +0,0 @@
//! Show a custom window frame instead of the default OS window chrome decorations.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions {
// Hide the OS-specific "chrome" around the window:
decorated: false,
// To have rounded corners we need transparency:
transparent: true,
min_window_size: Some(egui::vec2(400.0, 100.0)),
initial_window_size: Some(egui::vec2(400.0, 240.0)),
..Default::default()
};
eframe::run_native(
"Custom window frame", // unused title
options,
Box::new(|_cc| Box::<MyApp>::default()),
)
}
#[derive(Default)]
struct MyApp {}
impl eframe::App for MyApp {
fn clear_color(&self, _visuals: &egui::Visuals) -> [f32; 4] {
egui::Rgba::TRANSPARENT.to_array() // Make sure we don't paint anything behind the rounded corners
}
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
custom_window_frame(ctx, frame, "egui with custom frame", |ui| {
ui.label("This is just the contents of the window.");
ui.horizontal(|ui| {
ui.label("egui theme:");
egui::widgets::global_dark_light_mode_buttons(ui);
});
});
}
}
fn custom_window_frame(
ctx: &egui::Context,
frame: &mut eframe::Frame,
title: &str,
add_contents: impl FnOnce(&mut egui::Ui),
) {
use egui::*;
let panel_frame = egui::Frame {
fill: ctx.style().visuals.window_fill(),
rounding: 10.0.into(),
stroke: ctx.style().visuals.widgets.noninteractive.fg_stroke,
outer_margin: 0.5.into(), // so the stroke is within the bounds
..Default::default()
};
CentralPanel::default().frame(panel_frame).show(ctx, |ui| {
let app_rect = ui.max_rect();
let title_bar_height = 32.0;
let title_bar_rect = {
let mut rect = app_rect;
rect.max.y = rect.min.y + title_bar_height;
rect
};
title_bar_ui(ui, frame, title_bar_rect, title);
// Add the contents:
let content_rect = {
let mut rect = app_rect;
rect.min.y = title_bar_rect.max.y;
rect
}
.shrink(4.0);
let mut content_ui = ui.child_ui(content_rect, *ui.layout());
add_contents(&mut content_ui);
});
}
fn title_bar_ui(
ui: &mut egui::Ui,
frame: &mut eframe::Frame,
title_bar_rect: eframe::epaint::Rect,
title: &str,
) {
use egui::*;
let painter = ui.painter();
let title_bar_response = ui.interact(title_bar_rect, Id::new("title_bar"), Sense::click());
// Paint the title:
painter.text(
title_bar_rect.center(),
Align2::CENTER_CENTER,
title,
FontId::proportional(20.0),
ui.style().visuals.text_color(),
);
// Paint the line under the title:
painter.line_segment(
[
title_bar_rect.left_bottom() + vec2(1.0, 0.0),
title_bar_rect.right_bottom() + vec2(-1.0, 0.0),
],
ui.visuals().widgets.noninteractive.bg_stroke,
);
// Interact with the title bar (drag to move window):
if title_bar_response.double_clicked() {
frame.set_maximized(!frame.info().window_info.maximized);
} else if title_bar_response.is_pointer_button_down_on() {
frame.drag_window();
}
ui.allocate_ui_at_rect(title_bar_rect, |ui| {
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.visuals_mut().button_frame = false;
ui.add_space(8.0);
close_maximize_minimize(ui, frame);
});
});
}
/// Show some close/maximize/minimize buttons for the native window.
fn close_maximize_minimize(ui: &mut egui::Ui, frame: &mut eframe::Frame) {
use egui::{Button, RichText};
let button_height = 12.0;
let close_response = ui
.add(Button::new(RichText::new("").size(button_height)))
.on_hover_text("Close the window");
if close_response.clicked() {
frame.close();
}
if frame.info().window_info.maximized {
let maximized_response = ui
.add(Button::new(RichText::new("🗗").size(button_height)))
.on_hover_text("Restore window");
if maximized_response.clicked() {
frame.set_maximized(false);
}
} else {
let maximized_response = ui
.add(Button::new(RichText::new("🗗").size(button_height)))
.on_hover_text("Maximize window");
if maximized_response.clicked() {
frame.set_maximized(true);
}
}
let minimized_response = ui
.add(Button::new(RichText::new("🗕").size(button_height)))
.on_hover_text("Minimize the window");
if minimized_response.clicked() {
frame.set_minimized(true);
}
}

View File

@@ -1,19 +0,0 @@
[package]
name = "download_image"
version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65"
publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
egui_extras = { version = "0.22.0", features = ["image"] }
ehttp = "0.2"
env_logger = "0.10"
image = { version = "0.24", default-features = false, features = ["jpeg"] }
poll-promise = "0.2"

View File

@@ -1,7 +0,0 @@
Example how to download and show an image with eframe/egui.
```sh
cargo run -p download_image
```
![](screenshot.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

View File

@@ -1,65 +0,0 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
use egui_extras::RetainedImage;
use poll_promise::Promise;
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions::default();
eframe::run_native(
"Download and show an image with eframe/egui",
options,
Box::new(|_cc| Box::<MyApp>::default()),
)
}
#[derive(Default)]
struct MyApp {
/// `None` when download hasn't started yet.
promise: Option<Promise<ehttp::Result<RetainedImage>>>,
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
let promise = self.promise.get_or_insert_with(|| {
// Begin download.
// We download the image using `ehttp`, a library that works both in WASM and on native.
// We use the `poll-promise` library to communicate with the UI thread.
let ctx = ctx.clone();
let (sender, promise) = Promise::new();
let request = ehttp::Request::get("https://picsum.photos/seed/1.759706314/1024");
ehttp::fetch(request, move |response| {
let image = response.and_then(parse_response);
sender.send(image); // send the results back to the UI thread.
ctx.request_repaint(); // wake up UI thread
});
promise
});
egui::CentralPanel::default().show(ctx, |ui| match promise.ready() {
None => {
ui.spinner(); // still loading
}
Some(Err(err)) => {
ui.colored_label(ui.visuals().error_fg_color, err); // something went wrong
}
Some(Ok(image)) => {
image.show_max_size(ui, ui.available_size());
}
});
}
}
#[allow(clippy::needless_pass_by_value)]
fn parse_response(response: ehttp::Response) -> Result<RetainedImage, String> {
let content_type = response.content_type().unwrap_or_default();
if content_type.starts_with("image/") {
RetainedImage::from_image_bytes(&response.url, &response.bytes)
} else {
Err(format!(
"Expected image, found content-type {:?}",
content_type
))
}
}

View File

@@ -1,16 +0,0 @@
[package]
name = "file_dialog"
version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65"
publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = "0.10"
rfd = "0.11"

View File

@@ -1,7 +0,0 @@
How to show a file dialog using [`rfd`](https://github.com/PolyMeilex/rfd).
```sh
cargo run -p file_dialog
```
![](screenshot.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1,110 +0,0 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions {
drag_and_drop_support: true,
initial_window_size: Some(egui::vec2(320.0, 240.0)),
..Default::default()
};
eframe::run_native(
"Native file dialogs and drag-and-drop files",
options,
Box::new(|_cc| Box::<MyApp>::default()),
)
}
#[derive(Default)]
struct MyApp {
dropped_files: Vec<egui::DroppedFile>,
picked_path: Option<String>,
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.label("Drag-and-drop files onto the window!");
if ui.button("Open file…").clicked() {
if let Some(path) = rfd::FileDialog::new().pick_file() {
self.picked_path = Some(path.display().to_string());
}
}
if let Some(picked_path) = &self.picked_path {
ui.horizontal(|ui| {
ui.label("Picked file:");
ui.monospace(picked_path);
});
}
// Show dropped files (if any):
if !self.dropped_files.is_empty() {
ui.group(|ui| {
ui.label("Dropped files:");
for file in &self.dropped_files {
let mut info = if let Some(path) = &file.path {
path.display().to_string()
} else if !file.name.is_empty() {
file.name.clone()
} else {
"???".to_owned()
};
if let Some(bytes) = &file.bytes {
use std::fmt::Write as _;
write!(info, " ({} bytes)", bytes.len()).ok();
}
ui.label(info);
}
});
}
});
preview_files_being_dropped(ctx);
// Collect dropped files:
ctx.input(|i| {
if !i.raw.dropped_files.is_empty() {
self.dropped_files = i.raw.dropped_files.clone();
}
});
}
}
/// Preview hovering files:
fn preview_files_being_dropped(ctx: &egui::Context) {
use egui::*;
use std::fmt::Write as _;
if !ctx.input(|i| i.raw.hovered_files.is_empty()) {
let text = ctx.input(|i| {
let mut text = "Dropping files:\n".to_owned();
for file in &i.raw.hovered_files {
if let Some(path) = &file.path {
write!(text, "\n{}", path.display()).ok();
} else if !file.mime.is_empty() {
write!(text, "\n{}", file.mime).ok();
} else {
text += "\n???";
}
}
text
});
let painter =
ctx.layer_painter(LayerId::new(Order::Foreground, Id::new("file_drop_target")));
let screen_rect = ctx.screen_rect();
painter.rect_filled(screen_rect, 0.0, Color32::from_black_alpha(192));
painter.text(
screen_rect.center(),
Align2::CENTER_CENTER,
text,
TextStyle::Heading.resolve(&ctx.style()),
Color32::WHITE,
);
}
}

View File

@@ -1,15 +0,0 @@
[package]
name = "hello_world"
version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65"
publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = "0.10"

View File

@@ -1,7 +0,0 @@
Example showing some UI controls like `Label`, `TextEdit`, `Slider`, `Button`.
```sh
cargo run -p hello_world
```
![](screenshot.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

View File

@@ -1,48 +0,0 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(320.0, 240.0)),
..Default::default()
};
eframe::run_native(
"My egui App",
options,
Box::new(|_cc| Box::<MyApp>::default()),
)
}
struct MyApp {
name: String,
age: u32,
}
impl Default for MyApp {
fn default() -> Self {
Self {
name: "Arthur".to_owned(),
age: 42,
}
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("My egui Application");
ui.horizontal(|ui| {
let name_label = ui.label("Your name: ");
ui.text_edit_singleline(&mut self.name)
.labelled_by(name_label.id);
});
ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age"));
if ui.button("Click each year").clicked() {
self.age += 1;
}
ui.label(format!("Hello '{}', age {}", self.name, self.age));
});
}
}

View File

@@ -1,17 +0,0 @@
[package]
name = "hello_world_par"
version = "0.1.0"
authors = ["Maxim Osipenko <maxim1999max@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65"
publish = false
[dependencies]
eframe = { path = "../../crates/eframe", default-features = false, features = [
# accesskit struggles with threading
"default_fonts",
"wgpu",
] }
env_logger = "0.10"

View File

@@ -1,5 +0,0 @@
This example shows that you can use egui in parallel from multiple threads.
```sh
cargo run -p hello_world_par
```

View File

@@ -1,133 +0,0 @@
//! This example shows that you can use egui in parallel from multiple threads.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use std::sync::mpsc;
use std::thread::JoinHandle;
use eframe::egui;
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(1024.0, 768.0)),
..Default::default()
};
eframe::run_native(
"My parallel egui App",
options,
Box::new(|_cc| Box::new(MyApp::new())),
)
}
/// State per thread.
struct ThreadState {
thread_nr: usize,
title: String,
name: String,
age: u32,
}
impl ThreadState {
fn new(thread_nr: usize) -> Self {
let title = format!("Background thread {thread_nr}");
Self {
thread_nr,
title,
name: "Arthur".into(),
age: 12 + thread_nr as u32 * 10,
}
}
fn show(&mut self, ctx: &egui::Context) {
let pos = egui::pos2(16.0, 128.0 * (self.thread_nr as f32 + 1.0));
egui::Window::new(&self.title)
.default_pos(pos)
.show(ctx, |ui| {
ui.horizontal(|ui| {
ui.label("Your name: ");
ui.text_edit_singleline(&mut self.name);
});
ui.add(egui::Slider::new(&mut self.age, 0..=120).text("age"));
if ui.button("Click each year").clicked() {
self.age += 1;
}
ui.label(format!("Hello '{}', age {}", self.name, self.age));
});
}
}
fn new_worker(
thread_nr: usize,
on_done_tx: mpsc::SyncSender<()>,
) -> (JoinHandle<()>, mpsc::SyncSender<egui::Context>) {
let (show_tx, show_rc) = mpsc::sync_channel(0);
let handle = std::thread::Builder::new()
.name(format!("EguiPanelWorker {}", thread_nr))
.spawn(move || {
let mut state = ThreadState::new(thread_nr);
while let Ok(ctx) = show_rc.recv() {
state.show(&ctx);
let _ = on_done_tx.send(());
}
})
.expect("failed to spawn thread");
(handle, show_tx)
}
struct MyApp {
threads: Vec<(JoinHandle<()>, mpsc::SyncSender<egui::Context>)>,
on_done_tx: mpsc::SyncSender<()>,
on_done_rc: mpsc::Receiver<()>,
}
impl MyApp {
fn new() -> Self {
let threads = Vec::with_capacity(3);
let (on_done_tx, on_done_rc) = mpsc::sync_channel(0);
let mut slf = Self {
threads,
on_done_tx,
on_done_rc,
};
slf.spawn_thread();
slf.spawn_thread();
slf
}
fn spawn_thread(&mut self) {
let thread_nr = self.threads.len();
self.threads
.push(new_worker(thread_nr, self.on_done_tx.clone()));
}
}
impl std::ops::Drop for MyApp {
fn drop(&mut self) {
for (handle, show_tx) in self.threads.drain(..) {
std::mem::drop(show_tx);
handle.join().unwrap();
}
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::Window::new("Main thread").show(ctx, |ui| {
if ui.button("Spawn another thread").clicked() {
self.spawn_thread();
}
});
for (_handle, show_tx) in &self.threads {
let _ = show_tx.send(ctx.clone());
}
for _ in 0..self.threads.len() {
let _ = self.on_done_rc.recv();
}
}
}

View File

@@ -1,15 +0,0 @@
[package]
name = "hello_world_simple"
version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65"
publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = "0.10"

View File

@@ -1,7 +0,0 @@
Example showing some UI controls like `Label`, `TextEdit`, `Slider`, `Button`.
```sh
cargo run -p hello_world
```
![](screenshot.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

View File

@@ -1,32 +0,0 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(320.0, 240.0)),
..Default::default()
};
// Our application state:
let mut name = "Arthur".to_owned();
let mut age = 42;
eframe::run_simple_native("My egui App", options, move |ctx, _frame| {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("My egui Application");
ui.horizontal(|ui| {
let name_label = ui.label("Your name: ");
ui.text_edit_singleline(&mut name)
.labelled_by(name_label.id);
});
ui.add(egui::Slider::new(&mut age, 0..=120).text("age"));
if ui.button("Click each year").clicked() {
age += 1;
}
ui.label(format!("Hello '{name}', age {age}"));
});
})
}

View File

@@ -1,15 +0,0 @@
[package]
name = "keyboard_events"
version = "0.1.0"
authors = ["Jose Palazon <jose@palako.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65"
publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = "0.10"

View File

@@ -1,5 +0,0 @@
```sh
cargo run -p keyboard_events
```
![](screenshot.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -1,47 +0,0 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
use egui::*;
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions::default();
eframe::run_native(
"Keyboard events",
options,
Box::new(|_cc| Box::<Content>::default()),
)
}
#[derive(Default)]
struct Content {
text: String,
}
impl eframe::App for Content {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Press/Hold/Release example. Press A to test.");
if ui.button("Clear").clicked() {
self.text.clear();
}
ScrollArea::vertical()
.auto_shrink([false; 2])
.stick_to_bottom(true)
.show(ui, |ui| {
ui.label(&self.text);
});
if ctx.input(|i| i.key_pressed(Key::A)) {
self.text.push_str("\nPressed");
}
if ctx.input(|i| i.key_down(Key::A)) {
self.text.push_str("\nHeld");
ui.ctx().request_repaint(); // make sure we note the holding.
}
if ctx.input(|i| i.key_released(Key::A)) {
self.text.push_str("\nReleased");
}
});
}
}

View File

@@ -1,18 +0,0 @@
[package]
name = "puffin_profiler"
version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65"
publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"puffin",
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = "0.10"
puffin = "0.15"
puffin_http = "0.12"

View File

@@ -1,11 +0,0 @@
Example how to use the [puffin profiler](https://github.com/EmbarkStudios/puffin) with an `eframe` app.
```sh
cargo run -p puffin_profiler &
cargo install puffin_viewer
puffin_viewer --url 127.0.0.1:8585
```
![](screenshot.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -1,68 +0,0 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
start_puffin_server(); // NOTE: you may only want to call this if the users specifies some flag or clicks a button!
let options = eframe::NativeOptions::default();
eframe::run_native(
"My egui App",
options,
Box::new(|_cc| Box::<MyApp>::default()),
)
}
#[derive(Default)]
struct MyApp {}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Example of how to use the puffin profiler with egui");
ui.separator();
let cmd = "cargo install puffin_viewer && puffin_viewer --url 127.0.0.1:8585";
ui.label("To connect, run this:");
ui.horizontal(|ui| {
ui.monospace(cmd);
if ui.small_button("📋").clicked() {
ui.output_mut(|o| o.copied_text = cmd.into());
}
});
ui.separator();
ui.label("Note that this app runs in 'reactive' mode, so you must interact with the app for new profile events to be sent. Waving the mouse over this window is enough.");
if ui
.button(
"Click to sleep a bit. That should be visible as a spike in the profiler view!",
)
.clicked()
{
puffin::profile_scope!("sleep");
std::thread::sleep(std::time::Duration::from_millis(50));
}
});
}
}
fn start_puffin_server() {
puffin::set_scopes_on(true); // tell puffin to collect data
match puffin_http::Server::new("0.0.0.0:8585") {
Ok(puffin_server) => {
eprintln!("Run: cargo install puffin_viewer && puffin_viewer --url 127.0.0.1:8585");
// We can store the server if we want, but in this case we just want
// it to keep running. Dropping it closes the server, so let's not drop it!
#[allow(clippy::mem_forget)]
std::mem::forget(puffin_server);
}
Err(err) => {
eprintln!("Failed to start puffin server: {}", err);
}
};
}

View File

@@ -1,17 +0,0 @@
[package]
name = "retained_image"
version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65"
publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
egui_extras = { version = "0.22.0", features = ["image"] }
env_logger = "0.10"
image = { version = "0.24", default-features = false, features = ["png"] }

View File

@@ -1,7 +0,0 @@
Example how to show an image with eframe/egui.
```sh
cargo run -p retained_image
```
![](screenshot.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 KiB

View File

@@ -1,66 +0,0 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
use egui_extras::RetainedImage;
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(300.0, 900.0)),
..Default::default()
};
eframe::run_native(
"Show an image with eframe/egui",
options,
Box::new(|_cc| Box::<MyApp>::default()),
)
}
struct MyApp {
image: RetainedImage,
tint: egui::Color32,
}
impl Default for MyApp {
fn default() -> Self {
Self {
image: RetainedImage::from_image_bytes(
"rust-logo-256x256.png",
include_bytes!("rust-logo-256x256.png"),
)
.unwrap(),
tint: egui::Color32::from_rgb(255, 0, 255),
}
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("This is an image:");
self.image.show(ui);
ui.heading("This is a rotated image with a tint:");
ui.add(
egui::Image::new(self.image.texture_id(ctx), self.image.size_vec2())
.rotate(45.0_f32.to_radians(), egui::Vec2::splat(0.5))
.tint(self.tint),
);
ui.horizontal(|ui| {
ui.label("Tint:");
egui::color_picker::color_edit_button_srgba(
ui,
&mut self.tint,
egui::color_picker::Alpha::BlendOrAdditive,
);
});
ui.heading("This is an image you can click:");
ui.add(egui::ImageButton::new(
self.image.texture_id(ctx),
self.image.size_vec2(),
));
});
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -1 +0,0 @@
Rust logo by Mozilla, from https://github.com/rust-lang/rust-artwork

View File

@@ -1,21 +0,0 @@
[package]
name = "screenshot"
version = "0.1.0"
authors = [
"René Rössler <rene@freshx.de>",
"Andreas Faber <andreas.mfaber@gmail.com",
]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65"
publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
"wgpu",
] }
env_logger = "0.10"
itertools = "0.10.3"
image = { version = "0.24", default-features = false, features = ["png"] }

View File

@@ -1,7 +0,0 @@
Example how to take screenshots and display them with eframe/egui.
```sh
cargo run -p screenshot
```
![](screenshot.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -1,95 +0,0 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui::{self, ColorImage};
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions {
renderer: eframe::Renderer::Wgpu,
..Default::default()
};
eframe::run_native(
"Take screenshots and display with eframe/egui",
options,
Box::new(|_cc| Box::<MyApp>::default()),
)
}
#[derive(Default)]
struct MyApp {
continuously_take_screenshots: bool,
texture: Option<egui::TextureHandle>,
screenshot: Option<ColorImage>,
save_to_file: bool,
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
if let Some(screenshot) = self.screenshot.take() {
self.texture = Some(ui.ctx().load_texture(
"screenshot",
screenshot,
Default::default(),
));
}
ui.horizontal(|ui| {
ui.checkbox(
&mut self.continuously_take_screenshots,
"continuously take screenshots",
);
if ui.button("save to 'top_left.png'").clicked() {
self.save_to_file = true;
frame.request_screenshot();
}
ui.with_layout(egui::Layout::top_down(egui::Align::RIGHT), |ui| {
if self.continuously_take_screenshots {
if ui
.add(egui::Label::new("hover me!").sense(egui::Sense::hover()))
.hovered()
{
ctx.set_visuals(egui::Visuals::dark());
} else {
ctx.set_visuals(egui::Visuals::light());
};
frame.request_screenshot();
} else if ui.button("take screenshot!").clicked() {
frame.request_screenshot();
}
});
});
if let Some(texture) = self.texture.as_ref() {
ui.image(texture, ui.available_size());
} else {
ui.spinner();
}
ctx.request_repaint();
});
}
fn post_rendering(&mut self, _window_size: [u32; 2], frame: &eframe::Frame) {
if let Some(screenshot) = frame.screenshot() {
if self.save_to_file {
let pixels_per_point = frame.info().native_pixels_per_point;
let region =
egui::Rect::from_two_pos(egui::Pos2::ZERO, egui::Pos2 { x: 100., y: 100. });
let top_left_corner = screenshot.region(&region, pixels_per_point);
image::save_buffer(
"top_left.png",
top_left_corner.as_raw(),
top_left_corner.width() as u32,
top_left_corner.height() as u32,
image::ColorType::Rgba8,
)
.unwrap();
self.save_to_file = false;
}
self.screenshot = Some(screenshot);
}
}
}

View File

@@ -1,15 +0,0 @@
[package]
name = "serial_windows"
version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65"
publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = "0.10"

View File

@@ -1,17 +0,0 @@
Demonstrates how to open several windows after each other.
Expected order of execution:
- When the example runs a first window will be shown.
- Once the first window is closed after a delay a second window will be shown.
- Similarly, when the second window is closed after a delay a third will be shown.
- Once the third is closed the program will stop.
NOTE: this doesn't work on Mac due to <https://github.com/rust-windowing/winit/issues/2431>.
See also <https://github.com/emilk/egui/issues/1918>.
```sh
cargo run -p serial_windows
```
![](screenshot.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -1,63 +0,0 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
if cfg!(target_os = "macos") {
eprintln!("WARNING: this example does not work on Mac! See https://github.com/emilk/egui/issues/1918");
}
let options = eframe::NativeOptions {
run_and_return: true,
initial_window_size: Some(egui::vec2(320.0, 240.0)),
..Default::default()
};
eprintln!("Starting first window…");
eframe::run_native(
"First Window",
options.clone(),
Box::new(|_cc| Box::new(MyApp { has_next: true })),
)?;
std::thread::sleep(std::time::Duration::from_secs(2));
eprintln!("Starting second window…");
eframe::run_native(
"Second Window",
options.clone(),
Box::new(|_cc| Box::new(MyApp { has_next: true })),
)?;
std::thread::sleep(std::time::Duration::from_secs(2));
eprintln!("Starting third window…");
eframe::run_native(
"Third Window",
options,
Box::new(|_cc| Box::new(MyApp { has_next: false })),
)
}
struct MyApp {
pub(crate) has_next: bool,
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
let label_text = if self.has_next {
"When this window is closed the next will be opened after a short delay"
} else {
"This is the last window. Program will end when closed"
};
ui.label(label_text);
if ui.button("Close").clicked() {
eprintln!("Pressed Close button");
frame.close();
}
});
}
}

View File

@@ -1,16 +0,0 @@
[package]
name = "svg"
version = "0.1.0"
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65"
publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
egui_extras = { version = "0.22.0", features = ["svg"] }
env_logger = "0.10"

View File

@@ -1,7 +0,0 @@
Example how to show an SVG image.
```sh
cargo run -p svg
```
![](screenshot.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

View File

@@ -1,51 +0,0 @@
//! A good way of displaying an SVG image in egui.
//!
//! Requires the dependency `egui_extras` with the `svg` feature.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(1000.0, 700.0)),
..Default::default()
};
eframe::run_native(
"svg example",
options,
Box::new(|_cc| Box::<MyApp>::default()),
)
}
struct MyApp {
svg_image: egui_extras::RetainedImage,
}
impl Default for MyApp {
fn default() -> Self {
Self {
svg_image: egui_extras::RetainedImage::from_svg_bytes_with_size(
"rustacean-flat-happy.svg",
include_bytes!("rustacean-flat-happy.svg"),
egui_extras::image::FitTo::Original,
)
.unwrap(),
}
}
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("SVG example");
ui.label("The SVG is rasterized and displayed as a texture.");
ui.separator();
let max_size = ui.available_size();
self.svg_image.show_size(ui, max_size);
});
}
}

View File

@@ -1 +0,0 @@
Rust logo by Mozilla, from https://github.com/rust-lang/rust-artwork

View File

@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 1200 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
<g id="Layer-1" serif:id="Layer 1">
<g transform="matrix(1,0,0,1,1009.4,506.362)">
<path d="M0,-7.203L-12.072,-32.209C-12.009,-33.156 -11.961,-34.107 -11.961,-35.062C-11.961,-63.408 -41.439,-89.533 -91.03,-110.451L-91.03,-93.058C-95.866,-94.977 -100.901,-96.845 -106.147,-98.651L-106.147,-106.759C-177.021,-132.319 -282.53,-148.537 -400.388,-148.537C-503.361,-148.537 -596.917,-136.157 -666.179,-115.983L-666.179,-87.737L-666.181,-87.737L-666.181,-121.925C-737.141,-99.375 -781.135,-68.048 -781.135,-33.41C-781.135,-27.95 -780.034,-22.572 -777.918,-17.297L-785.146,-4.43C-785.146,-4.43 -790.938,3.082 -780.74,18.932C-771.746,32.909 -726.692,87.617 -702.913,116.267C-692.699,130.954 -685.772,140.001 -685.167,139.126C-684.212,137.74 -691.518,110.165 -711.802,78.703C-721.268,61.808 -732.57,39.42 -739.356,22.884C-720.414,34.874 -609.126,90.913 -382.124,90.685C-150.13,90.453 -47.009,17.834 -35.691,7.948C-39.646,23.837 -53.159,55.981 -63.936,78.586C-81.642,110.917 -88.056,139.064 -87.232,140.456C-86.708,141.334 -80.667,132.015 -71.756,116.913C-51.025,87.37 -11.739,30.974 -3.889,16.608C5.007,0.323 0,-7.203 0,-7.203" style="fill:rgb(165,43,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,1079.49,294.885)">
<path d="M0,204.135L-79.343,145.689C-80.088,143.089 -80.833,140.488 -81.603,137.908L-55.541,100.154C-52.881,96.314 -52.345,91.322 -54.072,86.943C-55.803,82.585 -59.587,79.461 -64.062,78.696L-108.128,71.217C-109.837,67.732 -111.626,64.301 -113.422,60.898L-94.907,18.51C-93.004,14.193 -93.402,9.175 -95.929,5.256C-98.446,1.319 -102.715,-0.981 -107.267,-0.802L-151.991,0.823C-154.306,-2.193 -156.658,-5.18 -159.058,-8.114L-148.78,-53.546C-147.738,-58.158 -149.054,-62.989 -152.267,-66.34C-155.462,-69.679 -160.105,-71.062 -164.52,-69.979L-208.082,-59.27C-210.902,-61.763 -213.77,-64.223 -216.67,-66.635L-215.103,-113.276C-214.935,-117.997 -217.136,-122.484 -220.915,-125.105C-224.692,-127.741 -229.485,-128.137 -233.616,-126.179L-274.254,-106.858C-277.527,-108.736 -280.819,-110.595 -284.146,-112.395L-291.327,-158.356C-292.056,-163.012 -295.051,-166.968 -299.246,-168.774C-303.431,-170.591 -308.222,-170.002 -311.894,-167.238L-348.126,-140.053C-351.695,-141.238 -355.279,-142.373 -358.905,-143.46L-374.522,-187.045C-376.11,-191.488 -379.772,-194.751 -384.238,-195.669C-388.688,-196.578 -393.266,-195.037 -396.352,-191.589L-426.851,-157.47C-430.536,-157.893 -434.228,-158.28 -437.927,-158.601L-461.476,-198.277C-463.86,-202.295 -468.073,-204.741 -472.615,-204.741C-477.144,-204.741 -481.365,-202.295 -483.733,-198.277L-507.288,-158.601C-510.989,-158.28 -514.696,-157.893 -518.376,-157.47L-548.875,-191.589C-551.965,-195.037 -556.559,-196.578 -560.997,-195.669C-565.457,-194.739 -569.125,-191.488 -570.704,-187.045L-586.333,-143.46C-589.954,-142.373 -593.538,-141.23 -597.113,-140.053L-633.333,-167.238C-637.016,-170.012 -641.811,-170.599 -646.001,-168.774C-650.182,-166.968 -653.189,-163.012 -653.914,-158.356L-661.1,-112.395C-664.422,-110.595 -667.714,-108.746 -670.995,-106.858L-711.629,-126.179C-715.756,-128.145 -720.574,-127.741 -724.333,-125.105C-728.106,-122.484 -730.313,-117.997 -730.143,-113.276L-728.581,-66.635C-731.475,-64.223 -734.337,-61.763 -737.172,-59.27L-780.726,-69.979C-785.149,-71.053 -789.788,-69.679 -792.991,-66.34C-796.212,-62.989 -797.517,-58.158 -796.482,-53.546L-786.225,-8.114C-788.603,-5.169 -790.958,-2.193 -793.267,0.823L-837.991,-0.802C-842.504,-0.937 -846.812,1.319 -849.334,5.256C-851.861,9.175 -852.244,14.193 -850.363,18.51L-831.835,60.898C-833.634,64.301 -835.421,67.732 -837.144,71.217L-881.207,78.696C-885.686,79.45 -889.459,82.572 -891.201,86.943C-892.929,91.322 -892.368,96.314 -889.727,100.154L-863.661,137.908C-863.862,138.575 -864.048,139.247 -864.248,139.916L-937.944,218.201C-937.944,218.201 -949.24,227.052 -932.797,247.855C-918.297,266.206 -843.846,338.951 -804.526,377.06C-787.92,396.408 -776.542,408.389 -775.354,407.353C-773.478,405.708 -783.326,370.506 -816.036,329.204C-841.252,292.148 -873.977,235.155 -866.303,228.586C-866.303,228.586 -857.574,217.505 -840.061,209.529C-839.42,210.041 -840.723,209.022 -840.061,209.529C-840.061,209.529 -470.466,380.02 -127.632,212.413C-88.468,205.388 -64.759,226.368 -64.759,226.368C-56.583,231.108 -77.755,289.712 -95.166,328.505C-118.845,372.555 -122.317,406.927 -120.31,408.119C-119.042,408.876 -110.427,395.766 -98.138,374.902C-67.814,332.649 -10.492,252.1 0,232.534C11.895,210.352 0,204.135 0,204.135" style="fill:rgb(247,76,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,917.896,244.679)">
<path d="M0,232.466C0,232.466 53.179,230 123.032,159.004L132.93,137.025C132.93,137.025 24.513,29.177 193.048,-45.266C193.048,-45.266 178.293,-21.154 182.622,72.006C182.622,72.006 233.437,54.357 248.336,-27.934C248.336,-27.934 322.456,69.79 167.834,161.443C167.834,161.443 95.294,277.732 -6.971,266.593L0,232.466Z" style="fill:rgb(247,76,0);fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,676.997,488.361)">
<path d="M0,-78.192C0,-78.192 36.935,-118.635 73.871,-78.192C73.871,-78.192 102.893,-24.265 73.871,2.695C73.871,2.695 26.384,40.443 0,2.695C0,2.695 -31.658,-26.964 0,-78.192" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,719.761,425.169)">
<path d="M0,0.004C0,15.75 -9.282,28.518 -20.732,28.518C-32.18,28.518 -41.462,15.75 -41.462,0.004C-41.462,-15.746 -32.18,-28.514 -20.732,-28.514C-9.282,-28.514 0,-15.746 0,0.004" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,512.148,482.736)">
<path d="M0,-83.609C0,-83.609 63.355,-111.661 80.648,-49.047C80.648,-49.047 98.762,23.933 28.618,28.052C28.618,28.052 -60.826,10.824 0,-83.609" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,543.968,426.204)">
<path d="M0,0.002C0,16.241 -9.572,29.411 -21.381,29.411C-33.185,29.411 -42.76,16.241 -42.76,0.002C-42.76,-16.242 -33.185,-29.409 -21.381,-29.409C-9.572,-29.409 0,-16.242 0,0.002" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,593.317,576.574)">
<path d="M0,-40.271L80.796,-46.755C80.796,-46.755 78.058,-33.749 67.517,-23.986C67.517,-23.986 39.727,6.484 7.844,-26.519C7.844,-26.519 2.627,-32.148 0,-40.271" style="fill-rule:nonzero;"/>
</g>
<g transform="matrix(1,0,0,1,269.796,270.778)">
<path d="M0,190.741C-0.667,190.741 -1.321,190.79 -1.973,190.842C-28.207,184.871 -101.946,165.657 -121.437,134.479C-121.437,134.479 -22.21,21.607 -177.297,-50.54L-159.24,74.338C-159.24,74.338 -207.049,42.389 -217.366,-27.008C-217.366,-27.008 -333.789,57.486 -165.982,138.466C-165.982,138.466 -150.762,195.653 -4.633,241.281L-4.526,240.846C-3.055,241.118 -1.549,241.281 0,241.281C13.808,241.281 25.003,229.969 25.003,216.01C25.003,202.054 13.808,190.741 0,190.741" style="fill:rgb(247,76,0);fill-rule:nonzero;"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -1,14 +0,0 @@
[package]
name = "user_attention"
version = "0.1.0"
authors = ["TicClick <ya@ticclick.ch>"]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.65"
publish = false
[dependencies]
eframe = { path = "../../crates/eframe", features = [
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
] }
env_logger = "0.10"

View File

@@ -1,7 +0,0 @@
An example of requesting a user's attention to the main window, and resetting the ongoing attention animations when necessary. Only works on native platforms.
```sh
cargo run -p user_attention
```
![](screenshot.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -1,133 +0,0 @@
use eframe::{
egui::{Button, CentralPanel, Context, UserAttentionType},
CreationContext, NativeOptions,
};
use std::time::{Duration, SystemTime};
fn main() -> eframe::Result<()> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let native_options = NativeOptions {
initial_window_size: Some(eframe::egui::vec2(400., 200.)),
..Default::default()
};
eframe::run_native(
"User attention test",
native_options,
Box::new(|cc| Box::new(Application::new(cc))),
)
}
fn repr(attention: UserAttentionType) -> String {
format!("{:?}", attention)
}
struct Application {
attention: UserAttentionType,
request_at: Option<SystemTime>,
auto_reset: bool,
reset_at: Option<SystemTime>,
}
impl Application {
fn new(_cc: &CreationContext<'_>) -> Self {
Self {
attention: UserAttentionType::Informational,
request_at: None,
auto_reset: false,
reset_at: None,
}
}
fn attention_reset_timeout() -> Duration {
Duration::from_secs(3)
}
fn attention_request_timeout() -> Duration {
Duration::from_secs(2)
}
fn repaint_max_timeout() -> Duration {
Duration::from_secs(1)
}
}
impl eframe::App for Application {
fn update(&mut self, ctx: &Context, frame: &mut eframe::Frame) {
if let Some(request_at) = self.request_at {
if request_at < SystemTime::now() {
self.request_at = None;
frame.request_user_attention(self.attention);
if self.auto_reset {
self.auto_reset = false;
self.reset_at = Some(SystemTime::now() + Self::attention_reset_timeout());
}
}
}
if let Some(reset_at) = self.reset_at {
if reset_at < SystemTime::now() {
self.reset_at = None;
frame.request_user_attention(UserAttentionType::Reset);
}
}
CentralPanel::default().show(ctx, |ui| {
ui.vertical(|ui| {
ui.horizontal(|ui| {
ui.label("Attention type:");
eframe::egui::ComboBox::new("attention", "")
.selected_text(repr(self.attention))
.show_ui(ui, |ui| {
for kind in [
UserAttentionType::Informational,
UserAttentionType::Critical,
] {
ui.selectable_value(&mut self.attention, kind, repr(kind));
}
})
});
let button_enabled = self.request_at.is_none() && self.reset_at.is_none();
let button_text = if button_enabled {
format!(
"Request in {} seconds",
Self::attention_request_timeout().as_secs()
)
} else {
match self.reset_at {
None => "Unfocus the window, fast!".to_owned(),
Some(t) => {
if let Ok(elapsed) = t.duration_since(SystemTime::now()) {
format!("Resetting attention in {} s...", elapsed.as_secs())
} else {
"Resetting attention...".to_owned()
}
}
}
};
let resp = ui
.add_enabled(button_enabled, Button::new(button_text))
.on_hover_text_at_pointer(
"After clicking, unfocus the application's window to see the effect",
);
ui.checkbox(
&mut self.auto_reset,
format!(
"Reset after {} seconds",
Self::attention_reset_timeout().as_secs()
),
);
if resp.clicked() {
self.request_at = Some(SystemTime::now() + Self::attention_request_timeout());
}
});
});
ctx.request_repaint_after(Self::repaint_max_timeout());
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB