[docs] Improve README.md and documentation

This commit is contained in:
Emil Ernerfeldt 2020-08-21 18:53:43 +02:00
parent ad9783a33d
commit 73cea29f7d
15 changed files with 366 additions and 189 deletions

149
README.md
View file

@ -1,68 +1,133 @@
# Egui
An immediate mode GUI library for Rust. Egui works anywhere you can draw textured triangles.
[![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)
![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg)
# Egui
An immediate mode GUI library written in Rust. Works anywhere you can draw textured triangles.
Sections:
* [Demo](#demos)
* [Goals](#goals)
* [State / features](#state)
* [How it works](#how-it-works)
* [Backends](#backends)
* [Other](#other)
## Goals:
* Lightweight
* Short, conveniant syntax
* Responsive (60 Hz without breaking a sweat)
* Portable
* Platform independent (the same code works on the web and as a native app)
## How it works:
Loop:
* Gather input: mouse, touches, screen size, ...
* Run application code (Immediate Mode GUI)
* Output is a triangle mesh
* Render with e.g. OpenGL
## Demo
[Click to run Egui web demo](https://emilk.github.io/egui/index.html). Partial demo source: https://github.com/emilk/egui/blob/master/egui/src/demos/app.rs
## Available backends:
Wherever you can render textured triangles you can use Egui.
[Hobogo](https://emilk.github.io/hobogo/index.html): A small game I made using Egui. Source: https://github.com/emilk/hobogo
* WebAssembly (`egui_web`) for making a web app. [Click to run](https://emilk.github.io/egui/index.html).
* [Glium](https://github.com/glium/glium) for native apps (see example_glium).
* [miniquad](https://github.com/not-fl3/emigui-miniquad) [web demo](https://not-fl3.github.io/miniquad-samples/emigui.html) [demo source](https://github.com/not-fl3/good-web-game/blob/master/examples/emigui.rs)
#### Example:
The same application code can thus be compiled to either into a native app or a web app.
``` rust
Window::new("Debug").show(ui.ctx(), |ui| {
ui.label(format!("Hello, world {}", 123));
if ui.button("Save").clicked {
my_save_function();
}
ui.text_edit(&mut my_string);
ui.add(Slider::f32(&mut value, 0.0..=1.0).text("float"));
});
```
## Demos
[Egui feature demo](https://emilk.github.io/egui/index.html), (partial) source: https://github.com/emilk/egui/blob/master/egui/src/demos/app.rs
<img src="media/demo-2020-08-21.png" width="50%">
## Goals
* Short and convenient syntax
* Responsive: 60 Hz without breaking a sweat
* Portable: the same code works on the web and as a native app
* Safe: difficult to make a mistake
* Easy to integrate into a any environment
* Provide a simple 2D graphics API for custom painting
* Simple: no callbacks, minimal dependencies, avoid unnecessary monomorphization
Egui is *not* a framework. Egui is a library you call into, not an environment you program for.
[Hobogo: A small game using Egui](https://emilk.github.io/hobogo/index.html), source: https://github.com/emilk/hobogo
## State
Alpha state. It works, but is somewhat incomplete.
Alpha state. 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:
### Features:
* Labels
* Buttons, checkboxes, radio buttons and sliders
* Horizontal or vertical layout
* Column layout
* Collapsible headers (sections)
* Windows
* Resizable regions
* Vertical scolling
* Simple text input
* Anti-aliased rendering of circles, rounded rectangles and lines.
* Widgets: label, text button, hyperlink, checkbox, radio button, slider, draggable value, text editing
* Layouts: horizontal, vertical, columns
* Text input: very basic, multiline, copy/paste
* Windows: move, resize, name, minimize and close
* Regions: resizing, vertical scrolling, collapsing headers (sections)
* Rendering: Anti-aliased rendering of lines, circles, text and convex polygons.
* Tooltips on hover
## Conventions
* All coordinates are screen space coordinates, in locial "points" (which may consist of many physical pixels).
## How it works
Loop:
* Gather input (mouse, touches, keyboard, screen size, etc) and give it to Egui
* Run application code (Immediate Mode GUI)
* Tell Egui to tesselate the frame graphics to a triangle mesh
* Render the triangle mesh with your favorite graphics API (see OpenGL examples)
## Backends
Wherever you can render textured triangles you can use Egui.
### Official
I maintain two official Egui backends:
* [egui_web](crates.io/crates/egui_web) for making a web app. Compiles to WASM, renders with WebGL. [Click to run the Egui demo](https://emilk.github.io/egui/index.html).
* [egui_glium](crates.io/crates/egui_glium) for compiling native apps with [Glium](https://github.com/glium/glium) backend.
The same code can be compiled to a native app or a web app.
### 3rd party
* [emigui-miniquad](https://github.com/not-fl3/emigui-miniquad): backend for [Miniquad](https://github.com/not-fl3/miniquad). [Web demo](https://not-fl3.github.io/miniquad-samples/emigui.html) and [demo source](https://github.com/not-fl3/good-web-game/blob/master/examples/emigui.rs).
### Writing your own Egui backend
You need to collect `egui::RawInput`, paint `egui::PaintJobs` and handle `egui::Output`. The basic structure is this:
``` rust
let mut egui_ctx = egui::Context::new();
// game loop:
loop {
let raw_input: egui::RawInput = my_backend.gather_input();
let mut ui = egui_ctx.begin_frame(raw_input);
my_app.ui(&mut ui); // add windows and widgets to `ui` here
let (output, paint_jobs) = egui_ctx.end_frame();
my_backend.paint(paint_jobs);
my_backend.set_cursor_icon(output.cursor_icon);
// Also see `egui::Output` for more
}
```
## Other
### Conventions
* All coordinates are screen space coordinates, in logical "points" (which may consist of many physical pixels). Origin (0, 0) is top left.
* All colors have premultiplied alpha
## 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 from something I hated to do to something I now like to do.
### 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 gui library is "Egui", written like that in text and as `egui` in code and pronounced as "e-gooey".
#### Differences between Egui and Dear ImGui
Dear ImGui has has many years of development and so of course has more features. It has also been heavily optimized for speed, which Egui has not yet been.
Where Dear ImGui uses matching `Begin/End` style function calls, which can be error prone. Egui prefers to use lambdas passed to a wrapping function. Lambdas are a bit ugly though, so I'd like to find a nicer solution to this.
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 we have 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");`.
### Name
The name of the library and the project is "Egui" and pronounced as "e-gooey".
The library was originally called "Emigui", but was renamed to Egui in 2020.
## Credits / Licenses
### Credits / Licenses
Egui author: Emil Ernerfeldt
Egui is under MIT OR Apache-2.0 license.
Fonts:
* Comfortaa: Open Font License, see OFT.txt
* ProggyClean.ttf, Copyright (c) 2004, 2005 Tristan Grimmer. MIT License. http://www.proggyfonts.net/

View file

@ -5,8 +5,8 @@
use crate::Ui;
/// Implement this trait to write apps that can be compiled both natively using the `egui_glium` crate,
/// and deployed as a web site using the `egui_web` crate.
/// Implement this trait to write apps that can be compiled both natively using the [`egui_glium`](https://crates.io/crates/egui_glium) crate,
/// and deployed as a web site using the [`egui_web`](https://crates.io/crates/egui_web) crate.
pub trait App {
/// Called each time the UI needs repainting, which may be many times per second.
fn ui(&mut self, ui: &mut Ui, backend: &mut dyn Backend);
@ -19,7 +19,7 @@ pub trait App {
/// How the backend runs the app
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RunMode {
/// Rapint the UI all the time (at the display refresh rate of e.g. 60 Hz).
/// Repint the UI all the time (at the display refresh rate of e.g. 60 Hz).
/// This is good for games where things are constantly moving.
/// This can also be achieved with `RunMode::Reactive` combined with calling `egui::Context::request_repaint()` each frame.
Continuous,

View file

@ -1,4 +1,4 @@
//! Containers are pieces of the UI which wraps other pieces of UI.
//! Containers are pieces of the UI which wraps other pieces of UI. Examples: `Window`, `ScrollArea`, `Resize`, etc.
//!
//! For instance, a `Frame` adds a frame and background to some contained UI.

View file

@ -4,7 +4,14 @@ use crate::{paint::*, widgets::*, *};
use super::*;
/// A floating window which can be moved, closed, collapsed, resized and scrolled.
/// Builder for a floating window which can be dragged, closed, collapsed, resized and scrolled.
///
/// You can customize:
/// * title
/// * default, minimum, maximum and/or fixed size
/// * if the window has a scroll area
/// * if the window can be collapsed (minimized) to just the title bar
/// * if there should be a close button
pub struct Window<'open> {
pub title_label: Label,
open: Option<&'open mut bool>,
@ -41,71 +48,79 @@ impl<'open> Window<'open> {
}
}
/// If the given bool is false, the window will not be visible.
/// If the given bool is true, the window will have a close button that sets this bool to false.
/// Call this to add a close-button to the window title bar.
///
/// * If `*open == false`, the window will not be visible.
/// * If `*open == true`, the window will have a close button.
/// * If the close button is pressed, `*open` will be set to `false`.
pub fn open(mut self, open: &'open mut bool) -> Self {
self.open = Some(open);
self
}
/// Usage: `Winmdow::new(...).mutate(|w| w.resize = w.resize.auto_expand_width(true))`
/// Usage: `Window::new(...).mutate(|w| w.resize = w.resize.auto_expand_width(true))`
/// Not sure this is a good interface for this.
pub fn mutate(mut self, mutate: impl Fn(&mut Self)) -> Self {
mutate(&mut self);
self
}
/// Usage: `Winmdow::new(...).resize(|r| r.auto_expand_width(true))`
/// Usage: `Window::new(...).resize(|r| r.auto_expand_width(true))`
/// Not sure this is a good interface for this.
pub fn resize(mut self, mutate: impl Fn(Resize) -> Resize) -> Self {
self.resize = mutate(self.resize);
self
}
/// Usage: `Winmdow::new(...).frame(|f| f.fill(Some(BLUE)))`
/// Usage: `Window::new(...).frame(|f| f.fill(Some(BLUE)))`
/// Not sure this is a good interface for this.
pub fn frame(mut self, frame: Frame) -> Self {
self.frame = Some(frame);
self
}
/// Set initial position of the window.
pub fn default_pos(mut self, default_pos: impl Into<Pos2>) -> Self {
self.area = self.area.default_pos(default_pos);
self
}
/// Set initial size of the window.
pub fn default_size(mut self, default_size: impl Into<Vec2>) -> Self {
self.resize = self.resize.default_size(default_size);
self
}
/// Set initial width of the window.
pub fn default_width(mut self, default_width: f32) -> Self {
self.resize = self.resize.default_width(default_width);
self
}
/// Set initial height of the window.
pub fn default_height(mut self, default_height: f32) -> Self {
self.resize = self.resize.default_height(default_height);
self
}
/// Set initial position and size of the window.
pub fn default_rect(self, rect: Rect) -> Self {
self.default_pos(rect.min).default_size(rect.size())
}
/// Positions the window and prevents it from being moved
/// Sets the window position and prevents it from being dragged around.
pub fn fixed_pos(mut self, pos: impl Into<Pos2>) -> Self {
self.area = self.area.fixed_pos(pos);
self
}
/// Sets the window size and prevents it from being resized by dragging its edges.
pub fn fixed_size(mut self, size: impl Into<Vec2>) -> Self {
self.resize = self.resize.fixed_size(size);
self
}
/// Can you resize it with the mouse?
/// Note that a window can still auto-resize
/// Can the user resize the window by dragging its edges?
/// Note that even if you set this to `false` the window may still auto-resize.
pub fn resizable(mut self, resizable: bool) -> Self {
self.resize = self.resize.resizable(resizable);
self
@ -118,14 +133,21 @@ impl<'open> Window<'open> {
}
/// Not resizable, just takes the size of its contents.
/// Also disabled scrolling.
pub fn auto_sized(mut self) -> Self {
self.resize = self.resize.auto_sized();
self.scroll = None;
self
}
/// Enable/disable scrolling. True by default.
pub fn scroll(mut self, scroll: bool) -> Self {
if !scroll {
if scroll {
debug_assert!(
self.scroll.is_some(),
"Window::scroll called multiple times"
);
} else {
self.scroll = None;
}
self

View file

@ -194,7 +194,7 @@ impl Context {
let paint_commands = self.drain_paint_lists();
let num_primitives = paint_commands.len();
let paint_jobs =
tessellator::tessellate_paint_commands(paint_options, self.fonts(), paint_commands);
tessellator::tessellate_paint_commands(paint_commands, paint_options, self.fonts());
{
let mut stats = PaintStats::default();

View file

@ -1,3 +1,6 @@
//! Demo-code for showing how Egui is used.
//!
//! The demo-code is also used in benchmarks and tests.
mod app;
mod fractal_clock;

View file

@ -4,6 +4,10 @@ use crate::{app, color::*, containers::*, demos::FractalClock, paint::*, widgets
// ----------------------------------------------------------------------------
/// Demonstrates how to make an app using Egui.
///
/// Implements `App` so it can be used with
/// [`egui_glium`](https://crates.io/crates/egui_glium) and [`egui_web`](https://crates.io/crates/egui_web).
#[derive(Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
@ -16,7 +20,9 @@ pub struct DemoApp {
}
impl DemoApp {
/// `web_location_hash`: for web demo only. e.g. "#fragmet".
/// Show the app ui (menu bar and windows).
///
/// * `web_location_hash`: for web demo only. e.g. "#fragment". Set to "".
pub fn ui(&mut self, ui: &mut Ui, web_location_hash: &str) {
if self.previous_web_location_hash != web_location_hash {
// #fragment end of URL:
@ -34,6 +40,7 @@ impl DemoApp {
self.windows(ui.ctx());
}
/// Show the open windows.
pub fn windows(&mut self, ctx: &Arc<Context>) {
let DemoApp {
open_windows,

View file

@ -6,7 +6,8 @@ const MAX_CLICK_DIST: f32 = 6.0;
const MAX_CLICK_DELAY: f64 = 0.3;
/// What the integration gives to the gui.
/// All coordinates in egui is in point/logical coordinates.
/// All coordinates in egui is in point/logical coordinates
/// with origin (0, 0) in the top left corner.
#[derive(Clone, Debug, Default)]
pub struct RawInput {
/// Is the button currently down?
@ -19,13 +20,13 @@ pub struct RawInput {
pub scroll_delta: Vec2,
/// Size of the screen in points.
/// TODO: this should be screen_rect for easy sandboxing.
// TODO: this should be screen_rect for easy sandboxing.
pub screen_size: Vec2,
/// Also known as device pixel ratio, > 1 for HDPI screens.
pub pixels_per_point: Option<f32>,
/// Time in seconds. Relative to whatever. Used for animation.
/// Time in seconds. Relative to whatever. Used for animations.
pub time: f64,
/// Local time. Only used for the clock in the demo app.

View file

@ -1,4 +1,26 @@
#![deny(warnings)]
//! Egui core library
//!
//! To get started with Egui, you can use one of the available backends
//! such as [`egui_web`](https://crates.io/crates/egui_web) or [`egui_glium`](https://crates.io/crates/egui_glium).
//!
//! To write your own backend for Egui you need to do this:
//!
//! ``` ignore
//! let mut egui_ctx = egui::Context::new();
//!
//! // game loop:
//! loop {
//! let raw_input: egui::RawInput = my_backend.gather_input();
//! let mut ui = egui_ctx.begin_frame(raw_input);
//! my_app.ui(&mut ui); // add windows and widgets to `ui` here
//! let (output, paint_jobs) = egui_ctx.end_frame();
//! my_backend.paint(paint_jobs);
//! my_backend.set_cursor_icon(output.cursor_icon);
//! // Also see `egui::Output` for more
//! }
//! ```
// #![deny(warnings)] // TODO: remove
#![warn(
clippy::all,
clippy::dbg_macro,

View file

@ -1,3 +1,8 @@
//! Converts graphics primitives into textured triangles.
//!
//! This module converts lines, circles, text and more represented by `PaintCmd`
//! into textured triangles represented by `Triangles`.
#![allow(clippy::identity_op)]
use {
@ -12,17 +17,22 @@ use {
/// The UV coordinate of a white region of the texture mesh.
const WHITE_UV: (u16, u16) = (1, 1);
/// The vertex type.
///
/// Should be friendly to send to GPU as is.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default)]
pub struct Vertex {
/// Logical pixel coordinates (points)
pub pos: Pos2,
/// Logical pixel coordinates (points).
/// (0,0) is the top left corner of the screen.
pub pos: Pos2, // 64 bit
/// Texel coordinates in the texture
pub uv: (u16, u16),
pub uv: (u16, u16), // 32 bit
/// sRGBA with premultiplied alpha
pub color: Color,
pub color: Color, // 32 bit
}
/// Textured triangles
/// Textured triangles.
#[derive(Clone, Debug, Default)]
pub struct Triangles {
/// Draw as triangles (i.e. the length is always multiple of three).
@ -39,36 +49,49 @@ pub type PaintJobs = Vec<PaintJob>;
// ----------------------------------------------------------------------------
/// ## Helpers for adding
impl Triangles {
pub fn append(&mut self, triangles: &Triangles) {
let index_offset = self.vertices.len() as u32;
for index in &triangles.indices {
self.indices.push(index_offset + index);
}
self.vertices.extend(triangles.vertices.iter());
/// Are all indices within the bounds of the contained vertices?
pub fn is_valid(&self) -> bool {
let n = self.vertices.len() as u32;
self.indices.iter().all(|&i| i < n)
}
fn triangle(&mut self, a: u32, b: u32, c: u32) {
/// Append all the indices and vertices of `other` to `self`.
pub fn append(&mut self, other: &Triangles) {
let index_offset = self.vertices.len() as u32;
for index in &other.indices {
self.indices.push(index_offset + index);
}
self.vertices.extend(other.vertices.iter());
}
/// Add a triangle.
pub fn add_triangle(&mut self, a: u32, b: u32, c: u32) {
self.indices.push(a);
self.indices.push(b);
self.indices.push(c);
}
/// Make room for this many additional triangles (will reserve 3x as many indices).
/// See also `reserve_vertices`.
pub fn reserve_triangles(&mut self, additional_triangles: usize) {
self.indices.reserve(3 * additional_triangles);
}
/// Make room for this many additional vertices.
/// See also `reserve_triangles`.
pub fn reserve_vertices(&mut self, additional: usize) {
self.vertices.reserve(additional);
}
/// Uniformly colored rectangle
/// Uniformly colored rectangle.
pub fn add_rect(&mut self, top_left: Vertex, bottom_right: Vertex) {
debug_assert_eq!(top_left.color, bottom_right.color);
let idx = self.vertices.len() as u32;
self.triangle(idx + 0, idx + 1, idx + 2);
self.triangle(idx + 2, idx + 1, idx + 3);
self.add_triangle(idx + 0, idx + 1, idx + 2);
self.add_triangle(idx + 2, idx + 1, idx + 3);
let top_right = Vertex {
pos: pos2(bottom_right.pos.x, top_left.pos.y),
@ -87,7 +110,8 @@ impl Triangles {
}
/// This is for platforms that only support 16-bit index buffers.
/// Splits this mesh into many small if needed.
///
/// Splits this mesh into many smaller meshes (if needed).
/// All the returned meshes will have indices that fit into a `u16`.
pub fn split_to_u16(self) -> Vec<Triangles> {
const MAX_SIZE: u32 = 1 << 16;
@ -146,15 +170,20 @@ impl Triangles {
pub struct PathPoint {
pos: Pos2,
/// For filled paths the normal is used for antialiasing.
/// For outlines the normal is used for figuring out how to make the line wide
/// For filled paths the normal is used for anti-aliasing (both outlines and filled areas).
///
/// For outlines the normal is also used for giving thickness to the path
/// (i.e. in what direction to expand).
///
/// The normal could be estimated by differences between successive points,
/// but that would be less accurate (and in some cases slower).
///
/// Normals are normally unit-length.
normal: Vec2,
}
/// A 2D path that can be tesselated into triangles.
/// A connected line (without thickness or gaps) which can be tesselated
/// to either to an outline (with thickness) or a filled convex area.
#[derive(Clone, Debug, Default)]
pub struct Path(Vec<PathPoint>);
@ -277,17 +306,24 @@ impl Path {
}
}
/// with x right, and y down (GUI coords) we have:
/// angle = dir
/// 0 * TAU / 4 = right
/// quadrant 0, right bottom
/// 1 * TAU / 4 = bottom
/// quadrant 1, left bottom
/// 2 * TAU / 4 = left
/// quadrant 2 left top
/// 3 * TAU / 4 = top
/// quadrant 3 right top
/// 4 * TAU / 4 = right
/// Add one quadrant of a circle
///
/// * quadrant 0: right bottom
/// * quadrant 1: left bottom
/// * quadrant 2: left top
/// * quadrant 3: right top
//
// Derivation:
//
// * angle 0 * TAU / 4 = right
// - quadrant 0: right bottom
// * angle 1 * TAU / 4 = bottom
// - quadrant 1: left bottom
// * angle 2 * TAU / 4 = left
// - quadrant 2: left top
// * angle 3 * TAU / 4 = top
// - quadrant 3: right top
// * angle 4 * TAU / 4 = right
pub fn add_circle_quadrant(&mut self, center: Pos2, radius: f32, quadrant: f32) {
// TODO: optimize with precalculated vertices for some radii ranges
@ -316,6 +352,7 @@ pub enum PathType {
}
use self::PathType::{Closed, Open};
/// Tesselation quality options
#[derive(Clone, Copy)]
pub struct PaintOptions {
/// Anti-aliasing makes shapes appear smoother, but requires more triangles and is therefore slower.
@ -336,11 +373,12 @@ impl Default for PaintOptions {
}
}
/// Tesselate the given convex area into a polygon.
pub fn fill_closed_path(
triangles: &mut Triangles,
options: PaintOptions,
path: &[PathPoint],
color: Color,
options: PaintOptions,
out: &mut Triangles,
) {
if color == color::TRANSPARENT {
return;
@ -353,49 +391,48 @@ pub fn fill_closed_path(
color,
};
if options.anti_alias {
triangles.reserve_triangles(3 * n as usize);
triangles.reserve_vertices(2 * n as usize);
out.reserve_triangles(3 * n as usize);
out.reserve_vertices(2 * n as usize);
let color_outer = color::TRANSPARENT;
let idx_inner = triangles.vertices.len() as u32;
let idx_inner = out.vertices.len() as u32;
let idx_outer = idx_inner + 1;
for i in 2..n {
triangles.triangle(idx_inner + 2 * (i - 1), idx_inner, idx_inner + 2 * i);
out.add_triangle(idx_inner + 2 * (i - 1), idx_inner, idx_inner + 2 * i);
}
let mut i0 = n - 1;
for i1 in 0..n {
let p1 = &path[i1 as usize];
let dm = p1.normal * options.aa_size * 0.5;
triangles.vertices.push(vert(p1.pos - dm, color));
triangles.vertices.push(vert(p1.pos + dm, color_outer));
triangles.triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0);
triangles.triangle(idx_outer + i0 * 2, idx_outer + i1 * 2, idx_inner + 2 * i1);
out.vertices.push(vert(p1.pos - dm, color));
out.vertices.push(vert(p1.pos + dm, color_outer));
out.add_triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0);
out.add_triangle(idx_outer + i0 * 2, idx_outer + i1 * 2, idx_inner + 2 * i1);
i0 = i1;
}
} else {
triangles.reserve_triangles(n as usize);
let idx = triangles.vertices.len() as u32;
triangles
.vertices
.extend(path.iter().map(|p| vert(p.pos, color)));
out.reserve_triangles(n as usize);
let idx = out.vertices.len() as u32;
out.vertices.extend(path.iter().map(|p| vert(p.pos, color)));
for i in 2..n {
triangles.triangle(idx, idx + i - 1, idx + i);
out.add_triangle(idx, idx + i - 1, idx + i);
}
}
}
/// Tesselate the given path as an outline with thickness.
pub fn paint_path_outline(
triangles: &mut Triangles,
options: PaintOptions,
path_type: PathType,
path: &[PathPoint],
path_type: PathType,
style: LineStyle,
options: PaintOptions,
out: &mut Triangles,
) {
if style.color == color::TRANSPARENT {
return;
}
let n = path.len() as u32;
let idx = triangles.vertices.len() as u32;
let idx = out.vertices.len() as u32;
let vert = |pos, color| Vertex {
pos,
@ -422,8 +459,8 @@ pub fn paint_path_outline(
return;
}
triangles.reserve_triangles(4 * n as usize);
triangles.reserve_vertices(3 * n as usize);
out.reserve_triangles(4 * n as usize);
out.reserve_vertices(3 * n as usize);
let mut i0 = n - 1;
for i1 in 0..n {
@ -431,20 +468,18 @@ pub fn paint_path_outline(
let p1 = &path[i1 as usize];
let p = p1.pos;
let n = p1.normal;
triangles
.vertices
out.vertices
.push(vert(p + n * options.aa_size, color_outer));
triangles.vertices.push(vert(p, color_inner));
triangles
.vertices
out.vertices.push(vert(p, color_inner));
out.vertices
.push(vert(p - n * options.aa_size, color_outer));
if connect_with_previous {
triangles.triangle(idx + 3 * i0 + 0, idx + 3 * i0 + 1, idx + 3 * i1 + 0);
triangles.triangle(idx + 3 * i0 + 1, idx + 3 * i1 + 0, idx + 3 * i1 + 1);
out.add_triangle(idx + 3 * i0 + 0, idx + 3 * i0 + 1, idx + 3 * i1 + 0);
out.add_triangle(idx + 3 * i0 + 1, idx + 3 * i1 + 0, idx + 3 * i1 + 1);
triangles.triangle(idx + 3 * i0 + 1, idx + 3 * i0 + 2, idx + 3 * i1 + 1);
triangles.triangle(idx + 3 * i0 + 2, idx + 3 * i1 + 1, idx + 3 * i1 + 2);
out.add_triangle(idx + 3 * i0 + 1, idx + 3 * i0 + 2, idx + 3 * i1 + 1);
out.add_triangle(idx + 3 * i0 + 2, idx + 3 * i1 + 1, idx + 3 * i1 + 2);
}
i0 = i1;
}
@ -462,8 +497,8 @@ pub fn paint_path_outline(
. |-----| inner_rad
*/
triangles.reserve_triangles(6 * n as usize);
triangles.reserve_vertices(4 * n as usize);
out.reserve_triangles(6 * n as usize);
out.reserve_vertices(4 * n as usize);
let mut i0 = n - 1;
for i1 in 0..n {
@ -473,44 +508,36 @@ pub fn paint_path_outline(
let p1 = &path[i1 as usize];
let p = p1.pos;
let n = p1.normal;
triangles
.vertices
.push(vert(p + n * outer_rad, color_outer));
triangles
.vertices
.push(vert(p + n * inner_rad, color_inner));
triangles
.vertices
.push(vert(p - n * inner_rad, color_inner));
triangles
.vertices
.push(vert(p - n * outer_rad, color_outer));
out.vertices.push(vert(p + n * outer_rad, color_outer));
out.vertices.push(vert(p + n * inner_rad, color_inner));
out.vertices.push(vert(p - n * inner_rad, color_inner));
out.vertices.push(vert(p - n * outer_rad, color_outer));
if connect_with_previous {
triangles.triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0);
triangles.triangle(idx + 4 * i0 + 1, idx + 4 * i1 + 0, idx + 4 * i1 + 1);
out.add_triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0);
out.add_triangle(idx + 4 * i0 + 1, idx + 4 * i1 + 0, idx + 4 * i1 + 1);
triangles.triangle(idx + 4 * i0 + 1, idx + 4 * i0 + 2, idx + 4 * i1 + 1);
triangles.triangle(idx + 4 * i0 + 2, idx + 4 * i1 + 1, idx + 4 * i1 + 2);
out.add_triangle(idx + 4 * i0 + 1, idx + 4 * i0 + 2, idx + 4 * i1 + 1);
out.add_triangle(idx + 4 * i0 + 2, idx + 4 * i1 + 1, idx + 4 * i1 + 2);
triangles.triangle(idx + 4 * i0 + 2, idx + 4 * i0 + 3, idx + 4 * i1 + 2);
triangles.triangle(idx + 4 * i0 + 3, idx + 4 * i1 + 2, idx + 4 * i1 + 3);
out.add_triangle(idx + 4 * i0 + 2, idx + 4 * i0 + 3, idx + 4 * i1 + 2);
out.add_triangle(idx + 4 * i0 + 3, idx + 4 * i1 + 2, idx + 4 * i1 + 3);
}
i0 = i1;
}
}
} else {
triangles.reserve_triangles(2 * n as usize);
triangles.reserve_vertices(2 * n as usize);
out.reserve_triangles(2 * n as usize);
out.reserve_vertices(2 * n as usize);
let last_index = if path_type == Closed { n } else { n - 1 };
for i in 0..last_index {
triangles.triangle(
out.add_triangle(
idx + (2 * i + 0) % (2 * n),
idx + (2 * i + 1) % (2 * n),
idx + (2 * i + 2) % (2 * n),
);
triangles.triangle(
out.add_triangle(
idx + (2 * i + 2) % (2 * n),
idx + (2 * i + 1) % (2 * n),
idx + (2 * i + 3) % (2 * n),
@ -526,21 +553,15 @@ pub fn paint_path_outline(
return;
}
for p in path {
triangles
.vertices
.push(vert(p.pos + radius * p.normal, color));
triangles
.vertices
.push(vert(p.pos - radius * p.normal, color));
out.vertices.push(vert(p.pos + radius * p.normal, color));
out.vertices.push(vert(p.pos - radius * p.normal, color));
}
} else {
let radius = style.width / 2.0;
for p in path {
triangles
.vertices
out.vertices
.push(vert(p.pos + radius * p.normal, style.color));
triangles
.vertices
out.vertices
.push(vert(p.pos - radius * p.normal, style.color));
}
}
@ -560,32 +581,39 @@ fn mul_color(color: Color, factor: f32) -> Color {
// ----------------------------------------------------------------------------
/// `reused_path`: only used to reuse memory
/// Tesselate a single `PaintCmd` into a `Triangles`.
///
/// * `command`: the command to tesselate
/// * `options`: tesselation quality
/// * `fonts`: font source when tesselating text
/// * `out`: where the triangles are put
/// * `scratchpad_path`: if you plan to run `tessellate_paint_command`
/// many times, pass it a reference to the same `Path` to avoid excessive allocations.
pub fn tessellate_paint_command(
reused_path: &mut Path,
command: PaintCmd,
options: PaintOptions,
fonts: &Fonts,
command: PaintCmd,
out: &mut Triangles,
scratchpad_path: &mut Path,
) {
let path = reused_path;
let path = scratchpad_path;
path.clear();
match command {
PaintCmd::Noop => {}
PaintCmd::Circle {
center,
radius,
fill,
outline,
radius,
} => {
if radius > 0.0 {
path.add_circle(center, radius);
if let Some(fill) = fill {
fill_closed_path(out, options, &path.0, fill);
fill_closed_path(&path.0, fill, options, out);
}
if let Some(outline) = outline {
paint_path_outline(out, options, Closed, &path.0, outline);
if let Some(line_style) = outline {
paint_path_outline(&path.0, Closed, line_style, options, out);
}
}
}
@ -594,7 +622,7 @@ pub fn tessellate_paint_command(
}
PaintCmd::LineSegment { points, style } => {
path.add_line_segment(points);
paint_path_outline(out, options, Open, &path.0, style);
paint_path_outline(&path.0, Open, style, options, out);
}
PaintCmd::Path {
path,
@ -608,19 +636,19 @@ pub fn tessellate_paint_command(
closed,
"You asked to fill a path that is not closed. That makes no sense."
);
fill_closed_path(out, options, &path.0, fill);
fill_closed_path(&path.0, fill, options, out);
}
if let Some(outline) = outline {
if let Some(line_style) = outline {
let typ = if closed { Closed } else { Open };
paint_path_outline(out, options, typ, &path.0, outline);
paint_path_outline(&path.0, typ, line_style, options, out);
}
}
}
PaintCmd::Rect {
mut rect,
corner_radius,
fill,
outline,
mut rect,
} => {
if !rect.is_empty() {
// It is common to (sometimes accidentally) create an infinitely sized ractangle.
@ -630,10 +658,10 @@ pub fn tessellate_paint_command(
path.add_rounded_rectangle(rect, corner_radius);
if let Some(fill) = fill {
fill_closed_path(out, options, &path.0, fill);
fill_closed_path(&path.0, fill, options, out);
}
if let Some(outline) = outline {
paint_path_outline(out, options, Closed, &path.0, outline);
if let Some(line_style) = outline {
paint_path_outline(&path.0, Closed, line_style, options, out);
}
}
}
@ -678,13 +706,23 @@ pub fn tessellate_paint_command(
}
}
/// Turns `PaintCmd`:s into sets of triangles
/// Turns `PaintCmd`:s into sets of triangles.
///
/// The given commands will be painted back-to-front (painters algorithm).
/// They will be batched together by clip rectangle.
///
/// * `commands`: the command to tesselate
/// * `options`: tesselation quality
/// * `fonts`: font source when tesselating text
///
/// ## Returns
/// A list of clip rectangles with matching `Triangles`.
pub fn tessellate_paint_commands(
commands: Vec<(Rect, PaintCmd)>,
options: PaintOptions,
fonts: &Fonts,
commands: Vec<(Rect, PaintCmd)>,
) -> Vec<(Rect, Triangles)> {
let mut reused_path = Path::default();
let mut scratchpad_path = Path::default();
let mut jobs = PaintJobs::default();
for (clip_rect, cmd) in commands {
@ -695,22 +733,22 @@ pub fn tessellate_paint_commands(
}
let out = &mut jobs.last_mut().unwrap().1;
tessellate_paint_command(&mut reused_path, options, fonts, cmd, out);
tessellate_paint_command(cmd, options, fonts, out, &mut scratchpad_path);
}
if options.debug_paint_clip_rects {
for (clip_rect, triangles) in &mut jobs {
tessellate_paint_command(
&mut reused_path,
options,
fonts,
PaintCmd::Rect {
rect: *clip_rect,
corner_radius: 0.0,
fill: None,
outline: Some(LineStyle::new(2.0, srgba(150, 255, 150, 255))),
},
options,
fonts,
triangles,
&mut scratchpad_path,
)
}
}

View file

@ -472,6 +472,10 @@ impl Ui {
self.add(RadioButton::new(checked, text))
}
pub fn text_edit(&mut self, text: &mut String) -> GuiResponse {
self.add(TextEdit::new(text))
}
/// Show a radio button. It is selected if `*curr_value == radio_value`.
/// If clicked, `radio_value` is assigned to `*curr_value`;
pub fn radio_value<Value: PartialEq>(

View file

@ -206,6 +206,8 @@ impl Painter {
triangles: &Triangles,
texture: &egui::Texture,
) {
debug_assert!(triangles.is_valid());
let vertex_buffer = {
#[derive(Copy, Clone)]
struct Vertex {

View file

@ -235,6 +235,7 @@ impl Painter {
}
fn paint_triangles(&self, triangles: &Triangles) -> Result<(), JsValue> {
debug_assert!(triangles.is_valid());
let indices: Vec<u16> = triangles.indices.iter().map(|idx| *idx as u16).collect();
let mut positions: Vec<f32> = Vec::with_capacity(2 * triangles.vertices.len());

View file

@ -1,25 +1,33 @@
//! Example of how to use Egui
#![deny(warnings)]
#![warn(clippy::all)]
use egui::{Slider, Window};
use egui_glium::{storage::FileStorage, RunMode};
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
#[derive(Default, serde::Deserialize, serde::Serialize)]
struct MyApp {
counter: u64,
my_string: String,
value: f32,
}
impl egui::app::App for MyApp {
/// This function will be called whenever the Ui needs to be shown,
/// which may be many times per second.
fn ui(&mut self, ui: &mut egui::Ui, _: &mut dyn egui::app::Backend) {
if ui.button("Increment").clicked {
self.counter += 1;
}
if ui.button("Reset").clicked {
self.counter = 0;
}
ui.label(format!("Counter: {}", self.counter));
let MyApp { my_string, value } = self;
// Example used in `README.md`.
Window::new("Debug").show(ui.ctx(), |ui| {
ui.label(format!("Hello, world {}", 123));
if ui.button("Save").clicked {
my_save_function();
}
ui.text_edit(my_string);
ui.add(Slider::f32(value, 0.0..=1.0).text("float"));
});
}
fn on_exit(&mut self, storage: &mut dyn egui::app::Storage) {
@ -33,3 +41,7 @@ fn main() {
let app: MyApp = egui::app::get_value(&storage, egui::app::APP_KEY).unwrap_or_default(); // Restore `MyApp` from file, or create new `MyApp`.
egui_glium::run(title, RunMode::Reactive, storage, app);
}
fn my_save_function() {
// dummy
}

BIN
media/demo-2020-08-21.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB