[docs] Improve README.md and documentation
This commit is contained in:
parent
ad9783a33d
commit
73cea29f7d
15 changed files with 366 additions and 189 deletions
149
README.md
149
README.md
|
@ -1,68 +1,133 @@
|
||||||
|
# Egui
|
||||||
|
An immediate mode GUI library for Rust. Egui works anywhere you can draw textured triangles.
|
||||||
|
|
||||||
[](https://crates.io/crates/egui)
|
[](https://crates.io/crates/egui)
|
||||||
[](https://docs.rs/egui)
|
[](https://docs.rs/egui)
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
# Egui
|
Sections:
|
||||||
An immediate mode GUI library written in Rust. Works anywhere you can draw textured triangles.
|
* [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:
|
## Demo
|
||||||
Loop:
|
[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
|
||||||
* Gather input: mouse, touches, screen size, ...
|
|
||||||
* Run application code (Immediate Mode GUI)
|
|
||||||
* Output is a triangle mesh
|
|
||||||
* Render with e.g. OpenGL
|
|
||||||
|
|
||||||
## Available backends:
|
[Hobogo](https://emilk.github.io/hobogo/index.html): A small game I made using Egui. Source: https://github.com/emilk/hobogo
|
||||||
Wherever you can render textured triangles you can use Egui.
|
|
||||||
|
|
||||||
* WebAssembly (`egui_web`) for making a web app. [Click to run](https://emilk.github.io/egui/index.html).
|
#### Example:
|
||||||
* [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)
|
|
||||||
|
|
||||||
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
|
<img src="media/demo-2020-08-21.png" width="50%">
|
||||||
[Egui feature demo](https://emilk.github.io/egui/index.html), (partial) source: https://github.com/emilk/egui/blob/master/egui/src/demos/app.rs
|
|
||||||
|
|
||||||
|
## 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
|
## 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
|
* Widgets: label, text button, hyperlink, checkbox, radio button, slider, draggable value, text editing
|
||||||
* Buttons, checkboxes, radio buttons and sliders
|
* Layouts: horizontal, vertical, columns
|
||||||
* Horizontal or vertical layout
|
* Text input: very basic, multiline, copy/paste
|
||||||
* Column layout
|
* Windows: move, resize, name, minimize and close
|
||||||
* Collapsible headers (sections)
|
* Regions: resizing, vertical scrolling, collapsing headers (sections)
|
||||||
* Windows
|
* Rendering: Anti-aliased rendering of lines, circles, text and convex polygons.
|
||||||
* Resizable regions
|
* Tooltips on hover
|
||||||
* Vertical scolling
|
|
||||||
* Simple text input
|
|
||||||
* Anti-aliased rendering of circles, rounded rectangles and lines.
|
|
||||||
|
|
||||||
## 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
|
* All colors have premultiplied alpha
|
||||||
|
|
||||||
## Inspiration
|
### 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.
|
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
|
#### Differences between Egui and Dear ImGui
|
||||||
The name of the gui library is "Egui", written like that in text and as `egui` in code and pronounced as "e-gooey".
|
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.
|
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:
|
Fonts:
|
||||||
* Comfortaa: Open Font License, see OFT.txt
|
* Comfortaa: Open Font License, see OFT.txt
|
||||||
* ProggyClean.ttf, Copyright (c) 2004, 2005 Tristan Grimmer. MIT License. http://www.proggyfonts.net/
|
* ProggyClean.ttf, Copyright (c) 2004, 2005 Tristan Grimmer. MIT License. http://www.proggyfonts.net/
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
|
|
||||||
use crate::Ui;
|
use crate::Ui;
|
||||||
|
|
||||||
/// Implement this trait to write apps that can be compiled both natively using the `egui_glium` 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` crate.
|
/// and deployed as a web site using the [`egui_web`](https://crates.io/crates/egui_web) crate.
|
||||||
pub trait App {
|
pub trait App {
|
||||||
/// Called each time the UI needs repainting, which may be many times per second.
|
/// 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);
|
fn ui(&mut self, ui: &mut Ui, backend: &mut dyn Backend);
|
||||||
|
@ -19,7 +19,7 @@ pub trait App {
|
||||||
/// How the backend runs the app
|
/// How the backend runs the app
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum RunMode {
|
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 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.
|
/// This can also be achieved with `RunMode::Reactive` combined with calling `egui::Context::request_repaint()` each frame.
|
||||||
Continuous,
|
Continuous,
|
||||||
|
|
|
@ -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.
|
//! For instance, a `Frame` adds a frame and background to some contained UI.
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,14 @@ use crate::{paint::*, widgets::*, *};
|
||||||
|
|
||||||
use super::*;
|
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 struct Window<'open> {
|
||||||
pub title_label: Label,
|
pub title_label: Label,
|
||||||
open: Option<&'open mut bool>,
|
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.
|
/// Call this to add a close-button to the window title bar.
|
||||||
/// If the given bool is true, the window will have a close button that sets this bool to false.
|
///
|
||||||
|
/// * 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 {
|
pub fn open(mut self, open: &'open mut bool) -> Self {
|
||||||
self.open = Some(open);
|
self.open = Some(open);
|
||||||
self
|
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.
|
/// Not sure this is a good interface for this.
|
||||||
pub fn mutate(mut self, mutate: impl Fn(&mut Self)) -> Self {
|
pub fn mutate(mut self, mutate: impl Fn(&mut Self)) -> Self {
|
||||||
mutate(&mut self);
|
mutate(&mut self);
|
||||||
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.
|
/// Not sure this is a good interface for this.
|
||||||
pub fn resize(mut self, mutate: impl Fn(Resize) -> Resize) -> Self {
|
pub fn resize(mut self, mutate: impl Fn(Resize) -> Resize) -> Self {
|
||||||
self.resize = mutate(self.resize);
|
self.resize = mutate(self.resize);
|
||||||
self
|
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.
|
/// Not sure this is a good interface for this.
|
||||||
pub fn frame(mut self, frame: Frame) -> Self {
|
pub fn frame(mut self, frame: Frame) -> Self {
|
||||||
self.frame = Some(frame);
|
self.frame = Some(frame);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set initial position of the window.
|
||||||
pub fn default_pos(mut self, default_pos: impl Into<Pos2>) -> Self {
|
pub fn default_pos(mut self, default_pos: impl Into<Pos2>) -> Self {
|
||||||
self.area = self.area.default_pos(default_pos);
|
self.area = self.area.default_pos(default_pos);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set initial size of the window.
|
||||||
pub fn default_size(mut self, default_size: impl Into<Vec2>) -> Self {
|
pub fn default_size(mut self, default_size: impl Into<Vec2>) -> Self {
|
||||||
self.resize = self.resize.default_size(default_size);
|
self.resize = self.resize.default_size(default_size);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set initial width of the window.
|
||||||
pub fn default_width(mut self, default_width: f32) -> Self {
|
pub fn default_width(mut self, default_width: f32) -> Self {
|
||||||
self.resize = self.resize.default_width(default_width);
|
self.resize = self.resize.default_width(default_width);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
/// Set initial height of the window.
|
||||||
pub fn default_height(mut self, default_height: f32) -> Self {
|
pub fn default_height(mut self, default_height: f32) -> Self {
|
||||||
self.resize = self.resize.default_height(default_height);
|
self.resize = self.resize.default_height(default_height);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set initial position and size of the window.
|
||||||
pub fn default_rect(self, rect: Rect) -> Self {
|
pub fn default_rect(self, rect: Rect) -> Self {
|
||||||
self.default_pos(rect.min).default_size(rect.size())
|
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 {
|
pub fn fixed_pos(mut self, pos: impl Into<Pos2>) -> Self {
|
||||||
self.area = self.area.fixed_pos(pos);
|
self.area = self.area.fixed_pos(pos);
|
||||||
self
|
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 {
|
pub fn fixed_size(mut self, size: impl Into<Vec2>) -> Self {
|
||||||
self.resize = self.resize.fixed_size(size);
|
self.resize = self.resize.fixed_size(size);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Can you resize it with the mouse?
|
/// Can the user resize the window by dragging its edges?
|
||||||
/// Note that a window can still auto-resize
|
/// Note that even if you set this to `false` the window may still auto-resize.
|
||||||
pub fn resizable(mut self, resizable: bool) -> Self {
|
pub fn resizable(mut self, resizable: bool) -> Self {
|
||||||
self.resize = self.resize.resizable(resizable);
|
self.resize = self.resize.resizable(resizable);
|
||||||
self
|
self
|
||||||
|
@ -118,14 +133,21 @@ impl<'open> Window<'open> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Not resizable, just takes the size of its contents.
|
/// Not resizable, just takes the size of its contents.
|
||||||
|
/// Also disabled scrolling.
|
||||||
pub fn auto_sized(mut self) -> Self {
|
pub fn auto_sized(mut self) -> Self {
|
||||||
self.resize = self.resize.auto_sized();
|
self.resize = self.resize.auto_sized();
|
||||||
self.scroll = None;
|
self.scroll = None;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enable/disable scrolling. True by default.
|
||||||
pub fn scroll(mut self, scroll: bool) -> Self {
|
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.scroll = None;
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
|
|
|
@ -194,7 +194,7 @@ impl Context {
|
||||||
let paint_commands = self.drain_paint_lists();
|
let paint_commands = self.drain_paint_lists();
|
||||||
let num_primitives = paint_commands.len();
|
let num_primitives = paint_commands.len();
|
||||||
let paint_jobs =
|
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();
|
let mut stats = PaintStats::default();
|
||||||
|
|
|
@ -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 app;
|
||||||
mod fractal_clock;
|
mod fractal_clock;
|
||||||
|
|
||||||
|
|
|
@ -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)]
|
#[derive(Default)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
|
@ -16,7 +20,9 @@ pub struct DemoApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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) {
|
pub fn ui(&mut self, ui: &mut Ui, web_location_hash: &str) {
|
||||||
if self.previous_web_location_hash != web_location_hash {
|
if self.previous_web_location_hash != web_location_hash {
|
||||||
// #fragment end of URL:
|
// #fragment end of URL:
|
||||||
|
@ -34,6 +40,7 @@ impl DemoApp {
|
||||||
self.windows(ui.ctx());
|
self.windows(ui.ctx());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show the open windows.
|
||||||
pub fn windows(&mut self, ctx: &Arc<Context>) {
|
pub fn windows(&mut self, ctx: &Arc<Context>) {
|
||||||
let DemoApp {
|
let DemoApp {
|
||||||
open_windows,
|
open_windows,
|
||||||
|
|
|
@ -6,7 +6,8 @@ const MAX_CLICK_DIST: f32 = 6.0;
|
||||||
const MAX_CLICK_DELAY: f64 = 0.3;
|
const MAX_CLICK_DELAY: f64 = 0.3;
|
||||||
|
|
||||||
/// What the integration gives to the gui.
|
/// 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)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct RawInput {
|
pub struct RawInput {
|
||||||
/// Is the button currently down?
|
/// Is the button currently down?
|
||||||
|
@ -19,13 +20,13 @@ pub struct RawInput {
|
||||||
pub scroll_delta: Vec2,
|
pub scroll_delta: Vec2,
|
||||||
|
|
||||||
/// Size of the screen in points.
|
/// 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,
|
pub screen_size: Vec2,
|
||||||
|
|
||||||
/// Also known as device pixel ratio, > 1 for HDPI screens.
|
/// Also known as device pixel ratio, > 1 for HDPI screens.
|
||||||
pub pixels_per_point: Option<f32>,
|
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,
|
pub time: f64,
|
||||||
|
|
||||||
/// Local time. Only used for the clock in the demo app.
|
/// Local time. Only used for the clock in the demo app.
|
||||||
|
|
|
@ -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(
|
#![warn(
|
||||||
clippy::all,
|
clippy::all,
|
||||||
clippy::dbg_macro,
|
clippy::dbg_macro,
|
||||||
|
|
|
@ -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)]
|
#![allow(clippy::identity_op)]
|
||||||
|
|
||||||
use {
|
use {
|
||||||
|
@ -12,17 +17,22 @@ use {
|
||||||
/// The UV coordinate of a white region of the texture mesh.
|
/// The UV coordinate of a white region of the texture mesh.
|
||||||
const WHITE_UV: (u16, u16) = (1, 1);
|
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)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
pub struct Vertex {
|
pub struct Vertex {
|
||||||
/// Logical pixel coordinates (points)
|
/// Logical pixel coordinates (points).
|
||||||
pub pos: Pos2,
|
/// (0,0) is the top left corner of the screen.
|
||||||
|
pub pos: Pos2, // 64 bit
|
||||||
/// Texel coordinates in the texture
|
/// Texel coordinates in the texture
|
||||||
pub uv: (u16, u16),
|
pub uv: (u16, u16), // 32 bit
|
||||||
/// sRGBA with premultiplied alpha
|
/// sRGBA with premultiplied alpha
|
||||||
pub color: Color,
|
pub color: Color, // 32 bit
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Textured triangles
|
/// Textured triangles.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Triangles {
|
pub struct Triangles {
|
||||||
/// Draw as triangles (i.e. the length is always multiple of three).
|
/// 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 {
|
impl Triangles {
|
||||||
pub fn append(&mut self, triangles: &Triangles) {
|
/// Are all indices within the bounds of the contained vertices?
|
||||||
let index_offset = self.vertices.len() as u32;
|
pub fn is_valid(&self) -> bool {
|
||||||
for index in &triangles.indices {
|
let n = self.vertices.len() as u32;
|
||||||
self.indices.push(index_offset + index);
|
self.indices.iter().all(|&i| i < n)
|
||||||
}
|
|
||||||
self.vertices.extend(triangles.vertices.iter());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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(a);
|
||||||
self.indices.push(b);
|
self.indices.push(b);
|
||||||
self.indices.push(c);
|
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) {
|
pub fn reserve_triangles(&mut self, additional_triangles: usize) {
|
||||||
self.indices.reserve(3 * additional_triangles);
|
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) {
|
pub fn reserve_vertices(&mut self, additional: usize) {
|
||||||
self.vertices.reserve(additional);
|
self.vertices.reserve(additional);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Uniformly colored rectangle
|
/// Uniformly colored rectangle.
|
||||||
pub fn add_rect(&mut self, top_left: Vertex, bottom_right: Vertex) {
|
pub fn add_rect(&mut self, top_left: Vertex, bottom_right: Vertex) {
|
||||||
debug_assert_eq!(top_left.color, bottom_right.color);
|
debug_assert_eq!(top_left.color, bottom_right.color);
|
||||||
|
|
||||||
let idx = self.vertices.len() as u32;
|
let idx = self.vertices.len() as u32;
|
||||||
self.triangle(idx + 0, idx + 1, idx + 2);
|
self.add_triangle(idx + 0, idx + 1, idx + 2);
|
||||||
self.triangle(idx + 2, idx + 1, idx + 3);
|
self.add_triangle(idx + 2, idx + 1, idx + 3);
|
||||||
|
|
||||||
let top_right = Vertex {
|
let top_right = Vertex {
|
||||||
pos: pos2(bottom_right.pos.x, top_left.pos.y),
|
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.
|
/// 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`.
|
/// All the returned meshes will have indices that fit into a `u16`.
|
||||||
pub fn split_to_u16(self) -> Vec<Triangles> {
|
pub fn split_to_u16(self) -> Vec<Triangles> {
|
||||||
const MAX_SIZE: u32 = 1 << 16;
|
const MAX_SIZE: u32 = 1 << 16;
|
||||||
|
@ -146,15 +170,20 @@ impl Triangles {
|
||||||
pub struct PathPoint {
|
pub struct PathPoint {
|
||||||
pos: Pos2,
|
pos: Pos2,
|
||||||
|
|
||||||
/// For filled paths the normal is used for antialiasing.
|
/// For filled paths the normal is used for anti-aliasing (both outlines and filled areas).
|
||||||
/// For outlines the normal is used for figuring out how to make the line wide
|
///
|
||||||
|
/// For outlines the normal is also used for giving thickness to the path
|
||||||
/// (i.e. in what direction to expand).
|
/// (i.e. in what direction to expand).
|
||||||
|
///
|
||||||
/// The normal could be estimated by differences between successive points,
|
/// The normal could be estimated by differences between successive points,
|
||||||
/// but that would be less accurate (and in some cases slower).
|
/// but that would be less accurate (and in some cases slower).
|
||||||
|
///
|
||||||
|
/// Normals are normally unit-length.
|
||||||
normal: Vec2,
|
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)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Path(Vec<PathPoint>);
|
pub struct Path(Vec<PathPoint>);
|
||||||
|
|
||||||
|
@ -277,17 +306,24 @@ impl Path {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// with x right, and y down (GUI coords) we have:
|
/// Add one quadrant of a circle
|
||||||
/// angle = dir
|
///
|
||||||
/// 0 * TAU / 4 = right
|
/// * quadrant 0: right bottom
|
||||||
/// quadrant 0, right bottom
|
/// * quadrant 1: left bottom
|
||||||
/// 1 * TAU / 4 = bottom
|
/// * quadrant 2: left top
|
||||||
/// quadrant 1, left bottom
|
/// * quadrant 3: right top
|
||||||
/// 2 * TAU / 4 = left
|
//
|
||||||
/// quadrant 2 left top
|
// Derivation:
|
||||||
/// 3 * TAU / 4 = top
|
//
|
||||||
/// quadrant 3 right top
|
// * angle 0 * TAU / 4 = right
|
||||||
/// 4 * 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) {
|
pub fn add_circle_quadrant(&mut self, center: Pos2, radius: f32, quadrant: f32) {
|
||||||
// TODO: optimize with precalculated vertices for some radii ranges
|
// TODO: optimize with precalculated vertices for some radii ranges
|
||||||
|
|
||||||
|
@ -316,6 +352,7 @@ pub enum PathType {
|
||||||
}
|
}
|
||||||
use self::PathType::{Closed, Open};
|
use self::PathType::{Closed, Open};
|
||||||
|
|
||||||
|
/// Tesselation quality options
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct PaintOptions {
|
pub struct PaintOptions {
|
||||||
/// Anti-aliasing makes shapes appear smoother, but requires more triangles and is therefore slower.
|
/// 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(
|
pub fn fill_closed_path(
|
||||||
triangles: &mut Triangles,
|
|
||||||
options: PaintOptions,
|
|
||||||
path: &[PathPoint],
|
path: &[PathPoint],
|
||||||
color: Color,
|
color: Color,
|
||||||
|
options: PaintOptions,
|
||||||
|
out: &mut Triangles,
|
||||||
) {
|
) {
|
||||||
if color == color::TRANSPARENT {
|
if color == color::TRANSPARENT {
|
||||||
return;
|
return;
|
||||||
|
@ -353,49 +391,48 @@ pub fn fill_closed_path(
|
||||||
color,
|
color,
|
||||||
};
|
};
|
||||||
if options.anti_alias {
|
if options.anti_alias {
|
||||||
triangles.reserve_triangles(3 * n as usize);
|
out.reserve_triangles(3 * n as usize);
|
||||||
triangles.reserve_vertices(2 * n as usize);
|
out.reserve_vertices(2 * n as usize);
|
||||||
let color_outer = color::TRANSPARENT;
|
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;
|
let idx_outer = idx_inner + 1;
|
||||||
for i in 2..n {
|
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;
|
let mut i0 = n - 1;
|
||||||
for i1 in 0..n {
|
for i1 in 0..n {
|
||||||
let p1 = &path[i1 as usize];
|
let p1 = &path[i1 as usize];
|
||||||
let dm = p1.normal * options.aa_size * 0.5;
|
let dm = p1.normal * options.aa_size * 0.5;
|
||||||
triangles.vertices.push(vert(p1.pos - dm, color));
|
out.vertices.push(vert(p1.pos - dm, color));
|
||||||
triangles.vertices.push(vert(p1.pos + dm, color_outer));
|
out.vertices.push(vert(p1.pos + dm, color_outer));
|
||||||
triangles.triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0);
|
out.add_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.add_triangle(idx_outer + i0 * 2, idx_outer + i1 * 2, idx_inner + 2 * i1);
|
||||||
i0 = i1;
|
i0 = i1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
triangles.reserve_triangles(n as usize);
|
out.reserve_triangles(n as usize);
|
||||||
let idx = triangles.vertices.len() as u32;
|
let idx = out.vertices.len() as u32;
|
||||||
triangles
|
out.vertices.extend(path.iter().map(|p| vert(p.pos, color)));
|
||||||
.vertices
|
|
||||||
.extend(path.iter().map(|p| vert(p.pos, color)));
|
|
||||||
for i in 2..n {
|
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(
|
pub fn paint_path_outline(
|
||||||
triangles: &mut Triangles,
|
|
||||||
options: PaintOptions,
|
|
||||||
path_type: PathType,
|
|
||||||
path: &[PathPoint],
|
path: &[PathPoint],
|
||||||
|
path_type: PathType,
|
||||||
style: LineStyle,
|
style: LineStyle,
|
||||||
|
options: PaintOptions,
|
||||||
|
out: &mut Triangles,
|
||||||
) {
|
) {
|
||||||
if style.color == color::TRANSPARENT {
|
if style.color == color::TRANSPARENT {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let n = path.len() as u32;
|
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 {
|
let vert = |pos, color| Vertex {
|
||||||
pos,
|
pos,
|
||||||
|
@ -422,8 +459,8 @@ pub fn paint_path_outline(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
triangles.reserve_triangles(4 * n as usize);
|
out.reserve_triangles(4 * n as usize);
|
||||||
triangles.reserve_vertices(3 * n as usize);
|
out.reserve_vertices(3 * n as usize);
|
||||||
|
|
||||||
let mut i0 = n - 1;
|
let mut i0 = n - 1;
|
||||||
for i1 in 0..n {
|
for i1 in 0..n {
|
||||||
|
@ -431,20 +468,18 @@ pub fn paint_path_outline(
|
||||||
let p1 = &path[i1 as usize];
|
let p1 = &path[i1 as usize];
|
||||||
let p = p1.pos;
|
let p = p1.pos;
|
||||||
let n = p1.normal;
|
let n = p1.normal;
|
||||||
triangles
|
out.vertices
|
||||||
.vertices
|
|
||||||
.push(vert(p + n * options.aa_size, color_outer));
|
.push(vert(p + n * options.aa_size, color_outer));
|
||||||
triangles.vertices.push(vert(p, color_inner));
|
out.vertices.push(vert(p, color_inner));
|
||||||
triangles
|
out.vertices
|
||||||
.vertices
|
|
||||||
.push(vert(p - n * options.aa_size, color_outer));
|
.push(vert(p - n * options.aa_size, color_outer));
|
||||||
|
|
||||||
if connect_with_previous {
|
if connect_with_previous {
|
||||||
triangles.triangle(idx + 3 * i0 + 0, idx + 3 * i0 + 1, idx + 3 * i1 + 0);
|
out.add_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 + 1, idx + 3 * i1 + 0, idx + 3 * i1 + 1);
|
||||||
|
|
||||||
triangles.triangle(idx + 3 * i0 + 1, idx + 3 * i0 + 2, idx + 3 * i1 + 1);
|
out.add_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 + 2, idx + 3 * i1 + 1, idx + 3 * i1 + 2);
|
||||||
}
|
}
|
||||||
i0 = i1;
|
i0 = i1;
|
||||||
}
|
}
|
||||||
|
@ -462,8 +497,8 @@ pub fn paint_path_outline(
|
||||||
. |-----| inner_rad
|
. |-----| inner_rad
|
||||||
*/
|
*/
|
||||||
|
|
||||||
triangles.reserve_triangles(6 * n as usize);
|
out.reserve_triangles(6 * n as usize);
|
||||||
triangles.reserve_vertices(4 * n as usize);
|
out.reserve_vertices(4 * n as usize);
|
||||||
|
|
||||||
let mut i0 = n - 1;
|
let mut i0 = n - 1;
|
||||||
for i1 in 0..n {
|
for i1 in 0..n {
|
||||||
|
@ -473,44 +508,36 @@ pub fn paint_path_outline(
|
||||||
let p1 = &path[i1 as usize];
|
let p1 = &path[i1 as usize];
|
||||||
let p = p1.pos;
|
let p = p1.pos;
|
||||||
let n = p1.normal;
|
let n = p1.normal;
|
||||||
triangles
|
out.vertices.push(vert(p + n * outer_rad, color_outer));
|
||||||
.vertices
|
out.vertices.push(vert(p + n * inner_rad, color_inner));
|
||||||
.push(vert(p + n * outer_rad, color_outer));
|
out.vertices.push(vert(p - n * inner_rad, color_inner));
|
||||||
triangles
|
out.vertices.push(vert(p - n * outer_rad, color_outer));
|
||||||
.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));
|
|
||||||
|
|
||||||
if connect_with_previous {
|
if connect_with_previous {
|
||||||
triangles.triangle(idx + 4 * i0 + 0, idx + 4 * i0 + 1, idx + 4 * i1 + 0);
|
out.add_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 + 1, idx + 4 * i1 + 0, idx + 4 * i1 + 1);
|
||||||
|
|
||||||
triangles.triangle(idx + 4 * i0 + 1, idx + 4 * i0 + 2, idx + 4 * i1 + 1);
|
out.add_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 + 2, idx + 4 * i1 + 1, idx + 4 * i1 + 2);
|
||||||
|
|
||||||
triangles.triangle(idx + 4 * i0 + 2, idx + 4 * i0 + 3, idx + 4 * i1 + 2);
|
out.add_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 + 3, idx + 4 * i1 + 2, idx + 4 * i1 + 3);
|
||||||
}
|
}
|
||||||
i0 = i1;
|
i0 = i1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
triangles.reserve_triangles(2 * n as usize);
|
out.reserve_triangles(2 * n as usize);
|
||||||
triangles.reserve_vertices(2 * n as usize);
|
out.reserve_vertices(2 * n as usize);
|
||||||
|
|
||||||
let last_index = if path_type == Closed { n } else { n - 1 };
|
let last_index = if path_type == Closed { n } else { n - 1 };
|
||||||
for i in 0..last_index {
|
for i in 0..last_index {
|
||||||
triangles.triangle(
|
out.add_triangle(
|
||||||
idx + (2 * i + 0) % (2 * n),
|
idx + (2 * i + 0) % (2 * n),
|
||||||
idx + (2 * i + 1) % (2 * n),
|
idx + (2 * i + 1) % (2 * n),
|
||||||
idx + (2 * i + 2) % (2 * n),
|
idx + (2 * i + 2) % (2 * n),
|
||||||
);
|
);
|
||||||
triangles.triangle(
|
out.add_triangle(
|
||||||
idx + (2 * i + 2) % (2 * n),
|
idx + (2 * i + 2) % (2 * n),
|
||||||
idx + (2 * i + 1) % (2 * n),
|
idx + (2 * i + 1) % (2 * n),
|
||||||
idx + (2 * i + 3) % (2 * n),
|
idx + (2 * i + 3) % (2 * n),
|
||||||
|
@ -526,21 +553,15 @@ pub fn paint_path_outline(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for p in path {
|
for p in path {
|
||||||
triangles
|
out.vertices.push(vert(p.pos + radius * p.normal, color));
|
||||||
.vertices
|
out.vertices.push(vert(p.pos - radius * p.normal, color));
|
||||||
.push(vert(p.pos + radius * p.normal, color));
|
|
||||||
triangles
|
|
||||||
.vertices
|
|
||||||
.push(vert(p.pos - radius * p.normal, color));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let radius = style.width / 2.0;
|
let radius = style.width / 2.0;
|
||||||
for p in path {
|
for p in path {
|
||||||
triangles
|
out.vertices
|
||||||
.vertices
|
|
||||||
.push(vert(p.pos + radius * p.normal, style.color));
|
.push(vert(p.pos + radius * p.normal, style.color));
|
||||||
triangles
|
out.vertices
|
||||||
.vertices
|
|
||||||
.push(vert(p.pos - radius * p.normal, style.color));
|
.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(
|
pub fn tessellate_paint_command(
|
||||||
reused_path: &mut Path,
|
command: PaintCmd,
|
||||||
options: PaintOptions,
|
options: PaintOptions,
|
||||||
fonts: &Fonts,
|
fonts: &Fonts,
|
||||||
command: PaintCmd,
|
|
||||||
out: &mut Triangles,
|
out: &mut Triangles,
|
||||||
|
scratchpad_path: &mut Path,
|
||||||
) {
|
) {
|
||||||
let path = reused_path;
|
let path = scratchpad_path;
|
||||||
path.clear();
|
path.clear();
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
PaintCmd::Noop => {}
|
PaintCmd::Noop => {}
|
||||||
PaintCmd::Circle {
|
PaintCmd::Circle {
|
||||||
center,
|
center,
|
||||||
|
radius,
|
||||||
fill,
|
fill,
|
||||||
outline,
|
outline,
|
||||||
radius,
|
|
||||||
} => {
|
} => {
|
||||||
if radius > 0.0 {
|
if radius > 0.0 {
|
||||||
path.add_circle(center, radius);
|
path.add_circle(center, radius);
|
||||||
if let Some(fill) = fill {
|
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 {
|
if let Some(line_style) = outline {
|
||||||
paint_path_outline(out, options, Closed, &path.0, outline);
|
paint_path_outline(&path.0, Closed, line_style, options, out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -594,7 +622,7 @@ pub fn tessellate_paint_command(
|
||||||
}
|
}
|
||||||
PaintCmd::LineSegment { points, style } => {
|
PaintCmd::LineSegment { points, style } => {
|
||||||
path.add_line_segment(points);
|
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 {
|
PaintCmd::Path {
|
||||||
path,
|
path,
|
||||||
|
@ -608,19 +636,19 @@ pub fn tessellate_paint_command(
|
||||||
closed,
|
closed,
|
||||||
"You asked to fill a path that is not closed. That makes no sense."
|
"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 };
|
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 {
|
PaintCmd::Rect {
|
||||||
|
mut rect,
|
||||||
corner_radius,
|
corner_radius,
|
||||||
fill,
|
fill,
|
||||||
outline,
|
outline,
|
||||||
mut rect,
|
|
||||||
} => {
|
} => {
|
||||||
if !rect.is_empty() {
|
if !rect.is_empty() {
|
||||||
// It is common to (sometimes accidentally) create an infinitely sized ractangle.
|
// 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);
|
path.add_rounded_rectangle(rect, corner_radius);
|
||||||
if let Some(fill) = fill {
|
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 {
|
if let Some(line_style) = outline {
|
||||||
paint_path_outline(out, options, Closed, &path.0, 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(
|
pub fn tessellate_paint_commands(
|
||||||
|
commands: Vec<(Rect, PaintCmd)>,
|
||||||
options: PaintOptions,
|
options: PaintOptions,
|
||||||
fonts: &Fonts,
|
fonts: &Fonts,
|
||||||
commands: Vec<(Rect, PaintCmd)>,
|
|
||||||
) -> Vec<(Rect, Triangles)> {
|
) -> Vec<(Rect, Triangles)> {
|
||||||
let mut reused_path = Path::default();
|
let mut scratchpad_path = Path::default();
|
||||||
|
|
||||||
let mut jobs = PaintJobs::default();
|
let mut jobs = PaintJobs::default();
|
||||||
for (clip_rect, cmd) in commands {
|
for (clip_rect, cmd) in commands {
|
||||||
|
@ -695,22 +733,22 @@ pub fn tessellate_paint_commands(
|
||||||
}
|
}
|
||||||
|
|
||||||
let out = &mut jobs.last_mut().unwrap().1;
|
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 {
|
if options.debug_paint_clip_rects {
|
||||||
for (clip_rect, triangles) in &mut jobs {
|
for (clip_rect, triangles) in &mut jobs {
|
||||||
tessellate_paint_command(
|
tessellate_paint_command(
|
||||||
&mut reused_path,
|
|
||||||
options,
|
|
||||||
fonts,
|
|
||||||
PaintCmd::Rect {
|
PaintCmd::Rect {
|
||||||
rect: *clip_rect,
|
rect: *clip_rect,
|
||||||
corner_radius: 0.0,
|
corner_radius: 0.0,
|
||||||
fill: None,
|
fill: None,
|
||||||
outline: Some(LineStyle::new(2.0, srgba(150, 255, 150, 255))),
|
outline: Some(LineStyle::new(2.0, srgba(150, 255, 150, 255))),
|
||||||
},
|
},
|
||||||
|
options,
|
||||||
|
fonts,
|
||||||
triangles,
|
triangles,
|
||||||
|
&mut scratchpad_path,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -472,6 +472,10 @@ impl Ui {
|
||||||
self.add(RadioButton::new(checked, text))
|
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`.
|
/// Show a radio button. It is selected if `*curr_value == radio_value`.
|
||||||
/// If clicked, `radio_value` is assigned to `*curr_value`;
|
/// If clicked, `radio_value` is assigned to `*curr_value`;
|
||||||
pub fn radio_value<Value: PartialEq>(
|
pub fn radio_value<Value: PartialEq>(
|
||||||
|
|
|
@ -206,6 +206,8 @@ impl Painter {
|
||||||
triangles: &Triangles,
|
triangles: &Triangles,
|
||||||
texture: &egui::Texture,
|
texture: &egui::Texture,
|
||||||
) {
|
) {
|
||||||
|
debug_assert!(triangles.is_valid());
|
||||||
|
|
||||||
let vertex_buffer = {
|
let vertex_buffer = {
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
struct Vertex {
|
struct Vertex {
|
||||||
|
|
|
@ -235,6 +235,7 @@ impl Painter {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_triangles(&self, triangles: &Triangles) -> Result<(), JsValue> {
|
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 indices: Vec<u16> = triangles.indices.iter().map(|idx| *idx as u16).collect();
|
||||||
|
|
||||||
let mut positions: Vec<f32> = Vec::with_capacity(2 * triangles.vertices.len());
|
let mut positions: Vec<f32> = Vec::with_capacity(2 * triangles.vertices.len());
|
||||||
|
|
|
@ -1,25 +1,33 @@
|
||||||
|
//! Example of how to use Egui
|
||||||
|
|
||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
#![warn(clippy::all)]
|
#![warn(clippy::all)]
|
||||||
|
|
||||||
|
use egui::{Slider, Window};
|
||||||
use egui_glium::{storage::FileStorage, RunMode};
|
use egui_glium::{storage::FileStorage, RunMode};
|
||||||
|
|
||||||
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
|
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
|
||||||
#[derive(Default, serde::Deserialize, serde::Serialize)]
|
#[derive(Default, serde::Deserialize, serde::Serialize)]
|
||||||
struct MyApp {
|
struct MyApp {
|
||||||
counter: u64,
|
my_string: String,
|
||||||
|
value: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl egui::app::App for MyApp {
|
impl egui::app::App for MyApp {
|
||||||
/// This function will be called whenever the Ui needs to be shown,
|
/// This function will be called whenever the Ui needs to be shown,
|
||||||
/// which may be many times per second.
|
/// which may be many times per second.
|
||||||
fn ui(&mut self, ui: &mut egui::Ui, _: &mut dyn egui::app::Backend) {
|
fn ui(&mut self, ui: &mut egui::Ui, _: &mut dyn egui::app::Backend) {
|
||||||
if ui.button("Increment").clicked {
|
let MyApp { my_string, value } = self;
|
||||||
self.counter += 1;
|
|
||||||
}
|
// Example used in `README.md`.
|
||||||
if ui.button("Reset").clicked {
|
Window::new("Debug").show(ui.ctx(), |ui| {
|
||||||
self.counter = 0;
|
ui.label(format!("Hello, world {}", 123));
|
||||||
}
|
if ui.button("Save").clicked {
|
||||||
ui.label(format!("Counter: {}", self.counter));
|
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) {
|
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`.
|
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);
|
egui_glium::run(title, RunMode::Reactive, storage, app);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn my_save_function() {
|
||||||
|
// dummy
|
||||||
|
}
|
||||||
|
|
BIN
media/demo-2020-08-21.png
Normal file
BIN
media/demo-2020-08-21.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
Loading…
Reference in a new issue