mirror of
https://github.com/Noratrieb/game-wip-dontplay.git
synced 2026-01-17 04:45:02 +01:00
vendor
This commit is contained in:
parent
12163d1338
commit
550b1644cb
363 changed files with 84081 additions and 16 deletions
67
egui/crates/egui_demo_app/Cargo.toml
Normal file
67
egui/crates/egui_demo_app/Cargo.toml
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
[package]
|
||||
name = "egui_demo_app"
|
||||
version = "0.21.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
publish = false
|
||||
default-run = "egui_demo_app"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
|
||||
[features]
|
||||
default = ["glow", "persistence"]
|
||||
|
||||
http = ["ehttp", "image", "poll-promise", "egui_extras/image"]
|
||||
persistence = ["eframe/persistence", "egui/persistence", "serde"]
|
||||
web_screen_reader = ["eframe/web_screen_reader"] # experimental
|
||||
serde = ["dep:serde", "egui_demo_lib/serde", "egui/serde"]
|
||||
syntax_highlighting = ["egui_demo_lib/syntax_highlighting"]
|
||||
|
||||
glow = ["eframe/glow"]
|
||||
wgpu = ["eframe/wgpu", "bytemuck"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4", features = ["js-sys", "wasmbind"] }
|
||||
eframe = { version = "0.21.0", path = "../eframe", default-features = false }
|
||||
egui = { version = "0.21.0", path = "../egui", features = [
|
||||
"extra_debug_asserts",
|
||||
] }
|
||||
egui_demo_lib = { version = "0.21.0", path = "../egui_demo_lib", features = [
|
||||
"chrono",
|
||||
] }
|
||||
tracing = "0.1"
|
||||
|
||||
# Optional dependencies:
|
||||
|
||||
bytemuck = { version = "1.7.1", optional = true }
|
||||
egui_extras = { version = "0.21.0", optional = true, path = "../egui_extras" }
|
||||
|
||||
# feature "http":
|
||||
ehttp = { version = "0.2.0", optional = true }
|
||||
image = { version = "0.24", optional = true, default-features = false, features = [
|
||||
"jpeg",
|
||||
"png",
|
||||
] }
|
||||
poll-promise = { version = "0.2", optional = true, default-features = false }
|
||||
|
||||
# feature "persistence":
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
||||
|
||||
|
||||
# native:
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
tracing-subscriber = "0.3"
|
||||
|
||||
# web:
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
console_error_panic_hook = "0.1.6"
|
||||
tracing-wasm = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
19
egui/crates/egui_demo_app/README.md
Normal file
19
egui/crates/egui_demo_app/README.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# egui demo app
|
||||
This app demonstrates [`egui`](https://github.com/emilk/egui/) and [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe).
|
||||
|
||||
View the demo app online at <https://egui.rs>.
|
||||
|
||||
Run it locally with `cargo run --release -p egui_demo_app`.
|
||||
|
||||
`egui_demo_app` can be compiled to WASM and viewed in a browser locally with:
|
||||
|
||||
```sh
|
||||
./scripts/start_server.sh &
|
||||
./scripts/build_demo_web.sh --open
|
||||
```
|
||||
|
||||
`egui_demo_app` uses [`egui_demo_lib`](https://github.com/emilk/egui/tree/master/crates/egui_demo_lib).
|
||||
|
||||
|
||||
## Running with `wgpu` backend
|
||||
`(cd egui_demo_app && cargo r --features wgpu)`
|
||||
201
egui/crates/egui_demo_app/src/apps/custom3d_glow.rs
Normal file
201
egui/crates/egui_demo_app/src/apps/custom3d_glow.rs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use eframe::egui_glow;
|
||||
use egui::mutex::Mutex;
|
||||
use egui_glow::glow;
|
||||
|
||||
pub struct Custom3d {
|
||||
/// Behind an `Arc<Mutex<…>>` so we can pass it to [`egui::PaintCallback`] and paint later.
|
||||
rotating_triangle: Arc<Mutex<RotatingTriangle>>,
|
||||
angle: f32,
|
||||
}
|
||||
|
||||
impl Custom3d {
|
||||
pub fn new<'a>(cc: &'a eframe::CreationContext<'a>) -> Option<Self> {
|
||||
let gl = cc.gl.as_ref()?;
|
||||
Some(Self {
|
||||
rotating_triangle: Arc::new(Mutex::new(RotatingTriangle::new(gl)?)),
|
||||
angle: 0.0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for Custom3d {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui::ScrollArea::both()
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |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).");
|
||||
});
|
||||
ui.label("It's not a very impressive demo, but it shows you can embed 3D inside of egui.");
|
||||
|
||||
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
||||
self.custom_painting(ui);
|
||||
});
|
||||
ui.label("Drag to rotate!");
|
||||
ui.add(egui_demo_lib::egui_github_link_file!());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn on_exit(&mut self, gl: Option<&glow::Context>) {
|
||||
if let Some(gl) = gl {
|
||||
self.rotating_triangle.lock().destroy(gl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Custom3d {
|
||||
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 cb = egui_glow::CallbackFn::new(move |_info, painter| {
|
||||
rotating_triangle.lock().paint(painter.gl(), angle);
|
||||
});
|
||||
|
||||
let callback = egui::PaintCallback {
|
||||
rect,
|
||||
callback: Arc::new(cb),
|
||||
};
|
||||
ui.painter().add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
struct RotatingTriangle {
|
||||
program: glow::Program,
|
||||
vertex_array: glow::VertexArray,
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)] // we need unsafe code to use glow
|
||||
impl RotatingTriangle {
|
||||
fn new(gl: &glow::Context) -> Option<Self> {
|
||||
use glow::HasContext as _;
|
||||
|
||||
let shader_version = egui_glow::ShaderVersion::get(gl);
|
||||
|
||||
unsafe {
|
||||
let program = gl.create_program().expect("Cannot create program");
|
||||
|
||||
if !shader_version.is_new_shader_interface() {
|
||||
tracing::warn!(
|
||||
"Custom 3D painting hasn't been ported to {:?}",
|
||||
shader_version
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
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.version_declaration(),
|
||||
shader_source
|
||||
),
|
||||
);
|
||||
gl.compile_shader(shader);
|
||||
assert!(
|
||||
gl.get_shader_compile_status(shader),
|
||||
"Failed to compile custom_3d_glow {shader_type}: {}",
|
||||
gl.get_shader_info_log(shader)
|
||||
);
|
||||
|
||||
gl.attach_shader(program, shader);
|
||||
shader
|
||||
})
|
||||
.collect();
|
||||
|
||||
gl.link_program(program);
|
||||
if !gl.get_program_link_status(program) {
|
||||
panic!("{}", 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");
|
||||
|
||||
Some(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
187
egui/crates/egui_demo_app/src/apps/custom3d_wgpu.rs
Normal file
187
egui/crates/egui_demo_app/src/apps/custom3d_wgpu.rs
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
use std::{num::NonZeroU64, sync::Arc};
|
||||
|
||||
use eframe::{
|
||||
egui_wgpu::wgpu::util::DeviceExt,
|
||||
egui_wgpu::{self, wgpu},
|
||||
};
|
||||
|
||||
pub struct Custom3d {
|
||||
angle: f32,
|
||||
}
|
||||
|
||||
impl Custom3d {
|
||||
pub fn new<'a>(cc: &'a eframe::CreationContext<'a>) -> Option<Self> {
|
||||
// Get the WGPU render state from the eframe creation context. This can also be retrieved
|
||||
// from `eframe::Frame` when you don't have a `CreationContext` available.
|
||||
let wgpu_render_state = cc.wgpu_render_state.as_ref()?;
|
||||
|
||||
let device = &wgpu_render_state.device;
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("custom3d"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("./custom3d_wgpu_shader.wgsl").into()),
|
||||
});
|
||||
|
||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("custom3d"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: NonZeroU64::new(16),
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("custom3d"),
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("custom3d"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
entry_point: "vs_main",
|
||||
buffers: &[],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &shader,
|
||||
entry_point: "fs_main",
|
||||
targets: &[Some(wgpu_render_state.target_format.into())],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
});
|
||||
|
||||
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("custom3d"),
|
||||
contents: bytemuck::cast_slice(&[0.0_f32; 4]), // 16 bytes aligned!
|
||||
// Mapping at creation (as done by the create_buffer_init utility) doesn't require us to to add the MAP_WRITE usage
|
||||
// (this *happens* to workaround this bug )
|
||||
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("custom3d"),
|
||||
layout: &bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: uniform_buffer.as_entire_binding(),
|
||||
}],
|
||||
});
|
||||
|
||||
// Because the graphics pipeline must have the same lifetime as the egui render pass,
|
||||
// instead of storing the pipeline in our `Custom3D` struct, we insert it into the
|
||||
// `paint_callback_resources` type map, which is stored alongside the render pass.
|
||||
wgpu_render_state
|
||||
.renderer
|
||||
.write()
|
||||
.paint_callback_resources
|
||||
.insert(TriangleRenderResources {
|
||||
pipeline,
|
||||
bind_group,
|
||||
uniform_buffer,
|
||||
});
|
||||
|
||||
Some(Self { angle: 0.0 })
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for Custom3d {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui::ScrollArea::both()
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("The triangle is being painted using ");
|
||||
ui.hyperlink_to("WGPU", "https://wgpu.rs");
|
||||
ui.label(" (Portable Rust graphics API awesomeness)");
|
||||
});
|
||||
ui.label("It's not a very impressive demo, but it shows you can embed 3D inside of egui.");
|
||||
|
||||
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
||||
self.custom_painting(ui);
|
||||
});
|
||||
ui.label("Drag to rotate!");
|
||||
ui.add(egui_demo_lib::egui_github_link_file!());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Custom3d {
|
||||
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;
|
||||
|
||||
// The callback function for WGPU is in two stages: prepare, and paint.
|
||||
//
|
||||
// The prepare callback is called every frame before paint and is given access to the wgpu
|
||||
// Device and Queue, which can be used, for instance, to update buffers and uniforms before
|
||||
// rendering.
|
||||
//
|
||||
// You can use the main `CommandEncoder` that is passed-in, return an arbitrary number
|
||||
// of user-defined `CommandBuffer`s, or both.
|
||||
// The main command buffer, as well as all user-defined ones, will be submitted together
|
||||
// to the GPU in a single call.
|
||||
//
|
||||
// The paint callback is called after prepare and is given access to the render pass, which
|
||||
// can be used to issue draw commands.
|
||||
let cb = egui_wgpu::CallbackFn::new()
|
||||
.prepare(move |device, queue, _encoder, paint_callback_resources| {
|
||||
let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap();
|
||||
resources.prepare(device, queue, angle);
|
||||
Vec::new()
|
||||
})
|
||||
.paint(move |_info, render_pass, paint_callback_resources| {
|
||||
let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap();
|
||||
resources.paint(render_pass);
|
||||
});
|
||||
|
||||
let callback = egui::PaintCallback {
|
||||
rect,
|
||||
callback: Arc::new(cb),
|
||||
};
|
||||
|
||||
ui.painter().add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
struct TriangleRenderResources {
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
bind_group: wgpu::BindGroup,
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
}
|
||||
|
||||
impl TriangleRenderResources {
|
||||
fn prepare(&self, _device: &wgpu::Device, queue: &wgpu::Queue, angle: f32) {
|
||||
// Update our uniform buffer with the angle from the UI
|
||||
queue.write_buffer(
|
||||
&self.uniform_buffer,
|
||||
0,
|
||||
bytemuck::cast_slice(&[angle, 0.0, 0.0, 0.0]),
|
||||
);
|
||||
}
|
||||
|
||||
fn paint<'rp>(&'rp self, render_pass: &mut wgpu::RenderPass<'rp>) {
|
||||
// Draw our triangle!
|
||||
render_pass.set_pipeline(&self.pipeline);
|
||||
render_pass.set_bind_group(0, &self.bind_group, &[]);
|
||||
render_pass.draw(0..3, 0..1);
|
||||
}
|
||||
}
|
||||
39
egui/crates/egui_demo_app/src/apps/custom3d_wgpu_shader.wgsl
Normal file
39
egui/crates/egui_demo_app/src/apps/custom3d_wgpu_shader.wgsl
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
struct VertexOut {
|
||||
@location(0) color: vec4<f32>,
|
||||
@builtin(position) position: vec4<f32>,
|
||||
};
|
||||
|
||||
struct Uniforms {
|
||||
@size(16) angle: f32, // pad to 16 bytes
|
||||
};
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<uniform> uniforms: Uniforms;
|
||||
|
||||
var<private> v_positions: array<vec2<f32>, 3> = array<vec2<f32>, 3>(
|
||||
vec2<f32>(0.0, 1.0),
|
||||
vec2<f32>(1.0, -1.0),
|
||||
vec2<f32>(-1.0, -1.0),
|
||||
);
|
||||
|
||||
var<private> v_colors: array<vec4<f32>, 3> = array<vec4<f32>, 3>(
|
||||
vec4<f32>(1.0, 0.0, 0.0, 1.0),
|
||||
vec4<f32>(0.0, 1.0, 0.0, 1.0),
|
||||
vec4<f32>(0.0, 0.0, 1.0, 1.0),
|
||||
);
|
||||
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) v_idx: u32) -> VertexOut {
|
||||
var out: VertexOut;
|
||||
|
||||
out.position = vec4<f32>(v_positions[v_idx], 0.0, 1.0);
|
||||
out.position.x = out.position.x * cos(uniforms.angle);
|
||||
out.color = v_colors[v_idx];
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
|
||||
return in.color;
|
||||
}
|
||||
205
egui/crates/egui_demo_app/src/apps/fractal_clock.rs
Normal file
205
egui/crates/egui_demo_app/src/apps/fractal_clock.rs
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
use egui::{containers::*, widgets::*, *};
|
||||
use std::f32::consts::TAU;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct FractalClock {
|
||||
paused: bool,
|
||||
time: f64,
|
||||
zoom: f32,
|
||||
start_line_width: f32,
|
||||
depth: usize,
|
||||
length_factor: f32,
|
||||
luminance_factor: f32,
|
||||
width_factor: f32,
|
||||
line_count: usize,
|
||||
}
|
||||
|
||||
impl Default for FractalClock {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
paused: false,
|
||||
time: 0.0,
|
||||
zoom: 0.25,
|
||||
start_line_width: 2.5,
|
||||
depth: 9,
|
||||
length_factor: 0.8,
|
||||
luminance_factor: 0.8,
|
||||
width_factor: 0.9,
|
||||
line_count: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FractalClock {
|
||||
pub fn ui(&mut self, ui: &mut Ui, seconds_since_midnight: Option<f64>) {
|
||||
if !self.paused {
|
||||
self.time = seconds_since_midnight.unwrap_or_else(|| ui.input(|i| i.time));
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
|
||||
let painter = Painter::new(
|
||||
ui.ctx().clone(),
|
||||
ui.layer_id(),
|
||||
ui.available_rect_before_wrap(),
|
||||
);
|
||||
self.paint(&painter);
|
||||
// Make sure we allocate what we used (everything)
|
||||
ui.expand_to_include_rect(painter.clip_rect());
|
||||
|
||||
Frame::popup(ui.style())
|
||||
.stroke(Stroke::NONE)
|
||||
.show(ui, |ui| {
|
||||
ui.set_max_width(270.0);
|
||||
CollapsingHeader::new("Settings")
|
||||
.show(ui, |ui| self.options_ui(ui, seconds_since_midnight));
|
||||
});
|
||||
}
|
||||
|
||||
fn options_ui(&mut self, ui: &mut Ui, seconds_since_midnight: Option<f64>) {
|
||||
if seconds_since_midnight.is_some() {
|
||||
ui.label(format!(
|
||||
"Local time: {:02}:{:02}:{:02}.{:03}",
|
||||
(self.time % (24.0 * 60.0 * 60.0) / 3600.0).floor(),
|
||||
(self.time % (60.0 * 60.0) / 60.0).floor(),
|
||||
(self.time % 60.0).floor(),
|
||||
(self.time % 1.0 * 100.0).floor()
|
||||
));
|
||||
} else {
|
||||
ui.label("The fractal_clock clock is not showing the correct time");
|
||||
};
|
||||
ui.label(format!("Painted line count: {}", self.line_count));
|
||||
|
||||
ui.checkbox(&mut self.paused, "Paused");
|
||||
ui.add(Slider::new(&mut self.zoom, 0.0..=1.0).text("zoom"));
|
||||
ui.add(Slider::new(&mut self.start_line_width, 0.0..=5.0).text("Start line width"));
|
||||
ui.add(Slider::new(&mut self.depth, 0..=14).text("depth"));
|
||||
ui.add(Slider::new(&mut self.length_factor, 0.0..=1.0).text("length factor"));
|
||||
ui.add(Slider::new(&mut self.luminance_factor, 0.0..=1.0).text("luminance factor"));
|
||||
ui.add(Slider::new(&mut self.width_factor, 0.0..=1.0).text("width factor"));
|
||||
|
||||
egui::reset_button(ui, self);
|
||||
|
||||
ui.hyperlink_to(
|
||||
"Inspired by a screensaver by Rob Mayoff",
|
||||
"http://www.dqd.com/~mayoff/programs/FractalClock/",
|
||||
);
|
||||
ui.add(egui_demo_lib::egui_github_link_file!());
|
||||
}
|
||||
|
||||
fn paint(&mut self, painter: &Painter) {
|
||||
struct Hand {
|
||||
length: f32,
|
||||
angle: f32,
|
||||
vec: Vec2,
|
||||
}
|
||||
|
||||
impl Hand {
|
||||
fn from_length_angle(length: f32, angle: f32) -> Self {
|
||||
Self {
|
||||
length,
|
||||
angle,
|
||||
vec: length * Vec2::angled(angle),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let angle_from_period =
|
||||
|period| TAU * (self.time.rem_euclid(period) / period) as f32 - TAU / 4.0;
|
||||
|
||||
let hands = [
|
||||
// Second hand:
|
||||
Hand::from_length_angle(self.length_factor, angle_from_period(60.0)),
|
||||
// Minute hand:
|
||||
Hand::from_length_angle(self.length_factor, angle_from_period(60.0 * 60.0)),
|
||||
// Hour hand:
|
||||
Hand::from_length_angle(0.5, angle_from_period(12.0 * 60.0 * 60.0)),
|
||||
];
|
||||
|
||||
let mut shapes: Vec<Shape> = Vec::new();
|
||||
|
||||
let rect = painter.clip_rect();
|
||||
let to_screen = emath::RectTransform::from_to(
|
||||
Rect::from_center_size(Pos2::ZERO, rect.square_proportions() / self.zoom),
|
||||
rect,
|
||||
);
|
||||
|
||||
let mut paint_line = |points: [Pos2; 2], color: Color32, width: f32| {
|
||||
let line = [to_screen * points[0], to_screen * points[1]];
|
||||
|
||||
// culling
|
||||
if rect.intersects(Rect::from_two_pos(line[0], line[1])) {
|
||||
shapes.push(Shape::line_segment(line, (width, color)));
|
||||
}
|
||||
};
|
||||
|
||||
let hand_rotations = [
|
||||
hands[0].angle - hands[2].angle + TAU / 2.0,
|
||||
hands[1].angle - hands[2].angle + TAU / 2.0,
|
||||
];
|
||||
|
||||
let hand_rotors = [
|
||||
hands[0].length * emath::Rot2::from_angle(hand_rotations[0]),
|
||||
hands[1].length * emath::Rot2::from_angle(hand_rotations[1]),
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Node {
|
||||
pos: Pos2,
|
||||
dir: Vec2,
|
||||
}
|
||||
|
||||
let mut nodes = Vec::new();
|
||||
|
||||
let mut width = self.start_line_width;
|
||||
|
||||
for (i, hand) in hands.iter().enumerate() {
|
||||
let center = pos2(0.0, 0.0);
|
||||
let end = center + hand.vec;
|
||||
paint_line([center, end], Color32::from_additive_luminance(255), width);
|
||||
if i < 2 {
|
||||
nodes.push(Node {
|
||||
pos: end,
|
||||
dir: hand.vec,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let mut luminance = 0.7; // Start dimmer than main hands
|
||||
|
||||
let mut new_nodes = Vec::new();
|
||||
for _ in 0..self.depth {
|
||||
new_nodes.clear();
|
||||
new_nodes.reserve(nodes.len() * 2);
|
||||
|
||||
luminance *= self.luminance_factor;
|
||||
width *= self.width_factor;
|
||||
|
||||
let luminance_u8 = (255.0 * luminance).round() as u8;
|
||||
if luminance_u8 == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
for &rotor in &hand_rotors {
|
||||
for a in &nodes {
|
||||
let new_dir = rotor * a.dir;
|
||||
let b = Node {
|
||||
pos: a.pos + new_dir,
|
||||
dir: new_dir,
|
||||
};
|
||||
paint_line(
|
||||
[a.pos, b.pos],
|
||||
Color32::from_additive_luminance(luminance_u8),
|
||||
width,
|
||||
);
|
||||
new_nodes.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
std::mem::swap(&mut nodes, &mut new_nodes);
|
||||
}
|
||||
self.line_count = shapes.len();
|
||||
painter.extend(shapes);
|
||||
}
|
||||
}
|
||||
266
egui/crates/egui_demo_app/src/apps/http_app.rs
Normal file
266
egui/crates/egui_demo_app/src/apps/http_app.rs
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
use egui_extras::RetainedImage;
|
||||
use poll_promise::Promise;
|
||||
|
||||
struct Resource {
|
||||
/// HTTP response
|
||||
response: ehttp::Response,
|
||||
|
||||
text: Option<String>,
|
||||
|
||||
/// If set, the response was an image.
|
||||
image: Option<RetainedImage>,
|
||||
|
||||
/// If set, the response was text with some supported syntax highlighting (e.g. ".rs" or ".md").
|
||||
colored_text: Option<ColoredText>,
|
||||
}
|
||||
|
||||
impl Resource {
|
||||
fn from_response(ctx: &egui::Context, response: ehttp::Response) -> Self {
|
||||
let content_type = response.content_type().unwrap_or_default();
|
||||
let image = if content_type.starts_with("image/") {
|
||||
RetainedImage::from_image_bytes(&response.url, &response.bytes).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let text = response.text();
|
||||
let colored_text = text.and_then(|text| syntax_highlighting(ctx, &response, text));
|
||||
let text = text.map(|text| text.to_owned());
|
||||
|
||||
Self {
|
||||
response,
|
||||
text,
|
||||
image,
|
||||
colored_text,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct HttpApp {
|
||||
url: String,
|
||||
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
promise: Option<Promise<ehttp::Result<Resource>>>,
|
||||
}
|
||||
|
||||
impl Default for HttpApp {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
url: "https://raw.githubusercontent.com/emilk/egui/master/README.md".to_owned(),
|
||||
promise: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for HttpApp {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
egui::TopBottomPanel::bottom("http_bottom").show(ctx, |ui| {
|
||||
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);
|
||||
ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| {
|
||||
ui.add(egui_demo_lib::egui_github_link_file!())
|
||||
})
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
let trigger_fetch = ui_url(ui, frame, &mut self.url);
|
||||
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("HTTP requests made using ");
|
||||
ui.hyperlink_to("ehttp", "https://www.github.com/emilk/ehttp");
|
||||
ui.label(".");
|
||||
});
|
||||
|
||||
if trigger_fetch {
|
||||
let ctx = ctx.clone();
|
||||
let (sender, promise) = Promise::new();
|
||||
let request = ehttp::Request::get(&self.url);
|
||||
ehttp::fetch(request, move |response| {
|
||||
ctx.request_repaint(); // wake up UI thread
|
||||
let resource = response.map(|response| Resource::from_response(&ctx, response));
|
||||
sender.send(resource);
|
||||
});
|
||||
self.promise = Some(promise);
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
if let Some(promise) = &self.promise {
|
||||
if let Some(result) = promise.ready() {
|
||||
match result {
|
||||
Ok(resource) => {
|
||||
ui_resource(ui, resource);
|
||||
}
|
||||
Err(error) => {
|
||||
// This should only happen if the fetch API isn't available or something similar.
|
||||
ui.colored_label(
|
||||
ui.visuals().error_fg_color,
|
||||
if error.is_empty() { "Error" } else { error },
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ui.spinner();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn ui_url(ui: &mut egui::Ui, frame: &mut eframe::Frame, url: &mut String) -> bool {
|
||||
let mut trigger_fetch = false;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("URL:");
|
||||
trigger_fetch |= ui
|
||||
.add(egui::TextEdit::singleline(url).desired_width(f32::INFINITY))
|
||||
.lost_focus();
|
||||
});
|
||||
|
||||
if frame.is_web() {
|
||||
ui.label("HINT: paste the url of this page into the field above!");
|
||||
}
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Source code for this example").clicked() {
|
||||
*url = format!(
|
||||
"https://raw.githubusercontent.com/emilk/egui/master/{}",
|
||||
file!()
|
||||
);
|
||||
trigger_fetch = true;
|
||||
}
|
||||
if ui.button("Random image").clicked() {
|
||||
let seed = ui.input(|i| i.time);
|
||||
let side = 640;
|
||||
*url = format!("https://picsum.photos/seed/{}/{}", seed, side);
|
||||
trigger_fetch = true;
|
||||
}
|
||||
});
|
||||
|
||||
trigger_fetch
|
||||
}
|
||||
|
||||
fn ui_resource(ui: &mut egui::Ui, resource: &Resource) {
|
||||
let Resource {
|
||||
response,
|
||||
text,
|
||||
image,
|
||||
colored_text,
|
||||
} = resource;
|
||||
|
||||
ui.monospace(format!("url: {}", response.url));
|
||||
ui.monospace(format!(
|
||||
"status: {} ({})",
|
||||
response.status, response.status_text
|
||||
));
|
||||
ui.monospace(format!(
|
||||
"content-type: {}",
|
||||
response.content_type().unwrap_or_default()
|
||||
));
|
||||
ui.monospace(format!(
|
||||
"size: {:.1} kB",
|
||||
response.bytes.len() as f32 / 1000.0
|
||||
));
|
||||
|
||||
ui.separator();
|
||||
|
||||
egui::ScrollArea::vertical()
|
||||
.auto_shrink([false; 2])
|
||||
.show(ui, |ui| {
|
||||
egui::CollapsingHeader::new("Response headers")
|
||||
.default_open(false)
|
||||
.show(ui, |ui| {
|
||||
egui::Grid::new("response_headers")
|
||||
.spacing(egui::vec2(ui.spacing().item_spacing.x * 2.0, 0.0))
|
||||
.show(ui, |ui| {
|
||||
for header in &response.headers {
|
||||
ui.label(header.0);
|
||||
ui.label(header.1);
|
||||
ui.end_row();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
if let Some(text) = &text {
|
||||
let tooltip = "Click to copy the response body";
|
||||
if ui.button("📋").on_hover_text(tooltip).clicked() {
|
||||
ui.output_mut(|o| o.copied_text = text.clone());
|
||||
}
|
||||
ui.separator();
|
||||
}
|
||||
|
||||
if let Some(image) = image {
|
||||
let mut size = image.size_vec2();
|
||||
size *= (ui.available_width() / size.x).min(1.0);
|
||||
image.show_size(ui, size);
|
||||
} else if let Some(colored_text) = colored_text {
|
||||
colored_text.ui(ui);
|
||||
} else if let Some(text) = &text {
|
||||
selectable_text(ui, text);
|
||||
} else {
|
||||
ui.monospace("[binary]");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn selectable_text(ui: &mut egui::Ui, mut text: &str) {
|
||||
ui.add(
|
||||
egui::TextEdit::multiline(&mut text)
|
||||
.desired_width(f32::INFINITY)
|
||||
.font(egui::TextStyle::Monospace),
|
||||
);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Syntax highlighting:
|
||||
|
||||
#[cfg(feature = "syntect")]
|
||||
fn syntax_highlighting(
|
||||
ctx: &egui::Context,
|
||||
response: &ehttp::Response,
|
||||
text: &str,
|
||||
) -> Option<ColoredText> {
|
||||
let extension_and_rest: Vec<&str> = response.url.rsplitn(2, '.').collect();
|
||||
let extension = extension_and_rest.get(0)?;
|
||||
let theme = crate::syntax_highlighting::CodeTheme::from_style(&ctx.style());
|
||||
Some(ColoredText(crate::syntax_highlighting::highlight(
|
||||
ctx, &theme, text, extension,
|
||||
)))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "syntect"))]
|
||||
fn syntax_highlighting(_ctx: &egui::Context, _: &ehttp::Response, _: &str) -> Option<ColoredText> {
|
||||
None
|
||||
}
|
||||
|
||||
struct ColoredText(egui::text::LayoutJob);
|
||||
|
||||
impl ColoredText {
|
||||
pub fn ui(&self, ui: &mut egui::Ui) {
|
||||
if true {
|
||||
// Selectable text:
|
||||
let mut layouter = |ui: &egui::Ui, _string: &str, wrap_width: f32| {
|
||||
let mut layout_job = self.0.clone();
|
||||
layout_job.wrap.max_width = wrap_width;
|
||||
ui.fonts(|f| f.layout_job(layout_job))
|
||||
};
|
||||
|
||||
let mut text = self.0.text.as_str();
|
||||
ui.add(
|
||||
egui::TextEdit::multiline(&mut text)
|
||||
.font(egui::TextStyle::Monospace)
|
||||
.desired_width(f32::INFINITY)
|
||||
.layouter(&mut layouter),
|
||||
);
|
||||
} else {
|
||||
let mut job = self.0.clone();
|
||||
job.wrap.max_width = ui.available_width();
|
||||
let galley = ui.fonts(|f| f.layout_job(job));
|
||||
let (response, painter) = ui.allocate_painter(galley.size(), egui::Sense::hover());
|
||||
painter.add(egui::Shape::galley(response.rect.min, galley));
|
||||
}
|
||||
}
|
||||
}
|
||||
21
egui/crates/egui_demo_app/src/apps/mod.rs
Normal file
21
egui/crates/egui_demo_app/src/apps/mod.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#[cfg(all(feature = "glow", not(feature = "wgpu")))]
|
||||
mod custom3d_glow;
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
mod custom3d_wgpu;
|
||||
|
||||
mod fractal_clock;
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
mod http_app;
|
||||
|
||||
#[cfg(all(feature = "glow", not(feature = "wgpu")))]
|
||||
pub use custom3d_glow::Custom3d;
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
pub use custom3d_wgpu::Custom3d;
|
||||
|
||||
pub use fractal_clock::FractalClock;
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
pub use http_app::HttpApp;
|
||||
396
egui/crates/egui_demo_app/src/backend_panel.rs
Normal file
396
egui/crates/egui_demo_app/src/backend_panel.rs
Normal file
|
|
@ -0,0 +1,396 @@
|
|||
use egui::Widget;
|
||||
|
||||
/// How often we repaint the demo app by default
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum RunMode {
|
||||
/// This is the default for the demo.
|
||||
///
|
||||
/// If this is selected, egui is only updated if are input events
|
||||
/// (like mouse movements) or there are some animations in the GUI.
|
||||
///
|
||||
/// Reactive mode saves CPU.
|
||||
///
|
||||
/// The downside is that the UI can become out-of-date if something it is supposed to monitor changes.
|
||||
/// For instance, a GUI for a thermostat need to repaint each time the temperature changes.
|
||||
/// To ensure the UI is up to date you need to call `egui::Context::request_repaint()` each
|
||||
/// time such an event happens. You can also chose to call `request_repaint()` once every second
|
||||
/// or after every single frame - this is called [`Continuous`](RunMode::Continuous) mode,
|
||||
/// and for games and interactive tools that need repainting every frame anyway, this should be the default.
|
||||
Reactive,
|
||||
|
||||
/// This will call `egui::Context::request_repaint()` at the end of each frame
|
||||
/// to request the backend to repaint as soon as possible.
|
||||
///
|
||||
/// On most platforms this will mean that egui will run at the display refresh rate of e.g. 60 Hz.
|
||||
///
|
||||
/// For this demo it is not any reason to do so except to
|
||||
/// demonstrate how quickly egui runs.
|
||||
///
|
||||
/// For games or other interactive apps, this is probably what you want to do.
|
||||
/// It will guarantee that egui is always up-to-date.
|
||||
Continuous,
|
||||
}
|
||||
|
||||
/// Default for demo is Reactive since
|
||||
/// 1) We want to use minimal CPU
|
||||
/// 2) There are no external events that could invalidate the UI
|
||||
/// so there are no events to miss.
|
||||
impl Default for RunMode {
|
||||
fn default() -> Self {
|
||||
RunMode::Reactive
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct BackendPanel {
|
||||
pub open: bool,
|
||||
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
// go back to [`RunMode::Reactive`] mode each time we start
|
||||
run_mode: RunMode,
|
||||
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
repaint_after_seconds: f32,
|
||||
|
||||
/// current slider value for current gui scale
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
pixels_per_point: Option<f32>,
|
||||
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
frame_history: crate::frame_history::FrameHistory,
|
||||
|
||||
egui_windows: EguiWindows,
|
||||
}
|
||||
|
||||
impl Default for BackendPanel {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
open: false,
|
||||
run_mode: Default::default(),
|
||||
repaint_after_seconds: 1.0,
|
||||
pixels_per_point: None,
|
||||
frame_history: Default::default(),
|
||||
egui_windows: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BackendPanel {
|
||||
pub fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
self.frame_history
|
||||
.on_new_frame(ctx.input(|i| i.time), frame.info().cpu_usage);
|
||||
|
||||
match self.run_mode {
|
||||
RunMode::Continuous => {
|
||||
// Tell the backend to repaint as soon as possible
|
||||
ctx.request_repaint();
|
||||
}
|
||||
RunMode::Reactive => {
|
||||
// let the computer rest for a bit
|
||||
ctx.request_repaint_after(std::time::Duration::from_secs_f32(
|
||||
self.repaint_after_seconds,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_of_frame(&mut self, ctx: &egui::Context) {
|
||||
self.egui_windows.windows(ctx);
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
egui::trace!(ui);
|
||||
|
||||
self.integration_ui(ui, frame);
|
||||
|
||||
ui.separator();
|
||||
|
||||
self.run_mode_ui(ui);
|
||||
|
||||
ui.separator();
|
||||
|
||||
self.frame_history.ui(ui);
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.label("egui windows:");
|
||||
self.egui_windows.checkboxes(ui);
|
||||
|
||||
ui.separator();
|
||||
|
||||
{
|
||||
let mut debug_on_hover = ui.ctx().debug_on_hover();
|
||||
ui.checkbox(&mut debug_on_hover, "🐛 Debug on hover")
|
||||
.on_hover_text("Show structure of the ui when you hover with the mouse");
|
||||
ui.ctx().set_debug_on_hover(debug_on_hover);
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(feature = "web_screen-reader")]
|
||||
{
|
||||
let mut screen_reader = ui.ctx().options(|o| o.screen_reader);
|
||||
ui.checkbox(&mut screen_reader, "🔈 Screen reader").on_hover_text("Experimental feature: checking this will turn on the screen reader on supported platforms");
|
||||
ui.ctx().options_mut(|o| o.screen_reader = screen_reader);
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
ui.separator();
|
||||
if ui.button("Quit").clicked() {
|
||||
frame.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn integration_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("egui running inside ");
|
||||
ui.hyperlink_to(
|
||||
"eframe",
|
||||
"https://github.com/emilk/egui/tree/master/crates/eframe",
|
||||
);
|
||||
ui.label(".");
|
||||
});
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
ui.collapsing("Web info (location)", |ui| {
|
||||
ui.monospace(format!("{:#?}", frame.info().web_info.location));
|
||||
});
|
||||
|
||||
// On web, the browser controls `pixels_per_point`.
|
||||
let integration_controls_pixels_per_point = frame.is_web();
|
||||
if !integration_controls_pixels_per_point {
|
||||
self.pixels_per_point_ui(ui, &frame.info());
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
ui.horizontal(|ui| {
|
||||
{
|
||||
let mut fullscreen = frame.info().window_info.fullscreen;
|
||||
if ui
|
||||
.checkbox(&mut fullscreen, "🗖 Fullscreen (F11)")
|
||||
.on_hover_text("Fullscreen the window")
|
||||
.changed()
|
||||
{
|
||||
frame.set_fullscreen(fullscreen);
|
||||
}
|
||||
}
|
||||
|
||||
if ui
|
||||
.button("📱 Phone Size")
|
||||
.on_hover_text("Resize the window to be small like a phone.")
|
||||
.clicked()
|
||||
{
|
||||
// frame.set_window_size(egui::vec2(375.0, 812.0)); // iPhone 12 mini
|
||||
frame.set_window_size(egui::vec2(375.0, 667.0)); // iPhone SE 2nd gen
|
||||
frame.set_fullscreen(false);
|
||||
ui.close_menu();
|
||||
}
|
||||
});
|
||||
|
||||
if !frame.info().window_info.fullscreen
|
||||
&& ui
|
||||
.button("Drag me to drag window")
|
||||
.is_pointer_button_down_on()
|
||||
{
|
||||
frame.drag_window();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pixels_per_point_ui(&mut self, ui: &mut egui::Ui, info: &eframe::IntegrationInfo) {
|
||||
let pixels_per_point = self
|
||||
.pixels_per_point
|
||||
.get_or_insert_with(|| ui.ctx().pixels_per_point());
|
||||
|
||||
let mut reset = false;
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().slider_width = 90.0;
|
||||
|
||||
let response = ui
|
||||
.add(
|
||||
egui::Slider::new(pixels_per_point, 0.5..=5.0)
|
||||
.logarithmic(true)
|
||||
.clamp_to_range(true)
|
||||
.text("Scale"),
|
||||
)
|
||||
.on_hover_text("Physical pixels per point.");
|
||||
|
||||
if response.drag_released() {
|
||||
// We wait until mouse release to activate:
|
||||
ui.ctx().set_pixels_per_point(*pixels_per_point);
|
||||
reset = true;
|
||||
} else if !response.is_pointer_button_down_on() {
|
||||
// When not dragging, show the current pixels_per_point so others can change it.
|
||||
reset = true;
|
||||
}
|
||||
|
||||
if let Some(native_pixels_per_point) = info.native_pixels_per_point {
|
||||
let enabled = ui.ctx().pixels_per_point() != native_pixels_per_point;
|
||||
if ui
|
||||
.add_enabled(enabled, egui::Button::new("Reset"))
|
||||
.on_hover_text(format!(
|
||||
"Reset scale to native value ({:.1})",
|
||||
native_pixels_per_point
|
||||
))
|
||||
.clicked()
|
||||
{
|
||||
ui.ctx().set_pixels_per_point(native_pixels_per_point);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if reset {
|
||||
self.pixels_per_point = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn run_mode_ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.horizontal(|ui| {
|
||||
let run_mode = &mut self.run_mode;
|
||||
ui.label("Mode:");
|
||||
ui.radio_value(run_mode, RunMode::Reactive, "Reactive")
|
||||
.on_hover_text("Repaint when there are animations or input (e.g. mouse movement)");
|
||||
ui.radio_value(run_mode, RunMode::Continuous, "Continuous")
|
||||
.on_hover_text("Repaint everything each frame");
|
||||
});
|
||||
|
||||
if self.run_mode == RunMode::Continuous {
|
||||
ui.label(format!(
|
||||
"Repainting the UI each frame. FPS: {:.1}",
|
||||
self.frame_history.fps()
|
||||
));
|
||||
} else {
|
||||
ui.label("Only running UI code when there are animations or input.");
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
ui.label("(but at least every ");
|
||||
egui::DragValue::new(&mut self.repaint_after_seconds)
|
||||
.clamp_range(0.1..=10.0)
|
||||
.speed(0.1)
|
||||
.suffix(" s")
|
||||
.ui(ui)
|
||||
.on_hover_text("Repaint this often, even if there is no input.");
|
||||
ui.label(")");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
struct EguiWindows {
|
||||
// egui stuff:
|
||||
settings: bool,
|
||||
inspection: bool,
|
||||
memory: bool,
|
||||
output_events: bool,
|
||||
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
output_event_history: std::collections::VecDeque<egui::output::OutputEvent>,
|
||||
}
|
||||
|
||||
impl Default for EguiWindows {
|
||||
fn default() -> Self {
|
||||
EguiWindows::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl EguiWindows {
|
||||
fn none() -> Self {
|
||||
Self {
|
||||
settings: false,
|
||||
inspection: false,
|
||||
memory: false,
|
||||
output_events: false,
|
||||
output_event_history: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn checkboxes(&mut self, ui: &mut egui::Ui) {
|
||||
let Self {
|
||||
settings,
|
||||
inspection,
|
||||
memory,
|
||||
output_events,
|
||||
output_event_history: _,
|
||||
} = self;
|
||||
|
||||
ui.checkbox(settings, "🔧 Settings");
|
||||
ui.checkbox(inspection, "🔍 Inspection");
|
||||
ui.checkbox(memory, "📝 Memory");
|
||||
ui.checkbox(output_events, "📤 Output Events");
|
||||
}
|
||||
|
||||
fn windows(&mut self, ctx: &egui::Context) {
|
||||
let Self {
|
||||
settings,
|
||||
inspection,
|
||||
memory,
|
||||
output_events,
|
||||
output_event_history,
|
||||
} = self;
|
||||
|
||||
ctx.output(|o| {
|
||||
for event in &o.events {
|
||||
output_event_history.push_back(event.clone());
|
||||
}
|
||||
});
|
||||
while output_event_history.len() > 1000 {
|
||||
output_event_history.pop_front();
|
||||
}
|
||||
|
||||
egui::Window::new("🔧 Settings")
|
||||
.open(settings)
|
||||
.vscroll(true)
|
||||
.show(ctx, |ui| {
|
||||
ctx.settings_ui(ui);
|
||||
});
|
||||
|
||||
egui::Window::new("🔍 Inspection")
|
||||
.open(inspection)
|
||||
.vscroll(true)
|
||||
.show(ctx, |ui| {
|
||||
ctx.inspection_ui(ui);
|
||||
});
|
||||
|
||||
egui::Window::new("📝 Memory")
|
||||
.open(memory)
|
||||
.resizable(false)
|
||||
.show(ctx, |ui| {
|
||||
ctx.memory_ui(ui);
|
||||
});
|
||||
|
||||
egui::Window::new("📤 Output Events")
|
||||
.open(output_events)
|
||||
.resizable(true)
|
||||
.default_width(520.0)
|
||||
.show(ctx, |ui| {
|
||||
ui.label(
|
||||
"Recent output events from egui. \
|
||||
These are emitted when you interact with widgets, or move focus between them with TAB. \
|
||||
They can be hooked up to a screen reader on supported platforms.",
|
||||
);
|
||||
|
||||
ui.separator();
|
||||
|
||||
egui::ScrollArea::vertical()
|
||||
.stick_to_bottom(true)
|
||||
.show(ui, |ui| {
|
||||
for event in output_event_history {
|
||||
ui.label(format!("{:?}", event));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
132
egui/crates/egui_demo_app/src/frame_history.rs
Normal file
132
egui/crates/egui_demo_app/src/frame_history.rs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
use egui::util::History;
|
||||
|
||||
pub struct FrameHistory {
|
||||
frame_times: History<f32>,
|
||||
}
|
||||
|
||||
impl Default for FrameHistory {
|
||||
fn default() -> Self {
|
||||
let max_age: f32 = 1.0;
|
||||
let max_len = (max_age * 300.0).round() as usize;
|
||||
Self {
|
||||
frame_times: History::new(0..max_len, max_age),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameHistory {
|
||||
// Called first
|
||||
pub fn on_new_frame(&mut self, now: f64, previous_frame_time: Option<f32>) {
|
||||
let previous_frame_time = previous_frame_time.unwrap_or_default();
|
||||
if let Some(latest) = self.frame_times.latest_mut() {
|
||||
*latest = previous_frame_time; // rewrite history now that we know
|
||||
}
|
||||
self.frame_times.add(now, previous_frame_time); // projected
|
||||
}
|
||||
|
||||
pub fn mean_frame_time(&self) -> f32 {
|
||||
self.frame_times.average().unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn fps(&self) -> f32 {
|
||||
1.0 / self.frame_times.mean_time_interval().unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(format!(
|
||||
"Total frames painted: {}",
|
||||
self.frame_times.total_count()
|
||||
))
|
||||
.on_hover_text("Includes this frame.");
|
||||
|
||||
ui.label(format!(
|
||||
"Mean CPU usage: {:.2} ms / frame",
|
||||
1e3 * self.mean_frame_time()
|
||||
))
|
||||
.on_hover_text(
|
||||
"Includes egui layout and tessellation time.\n\
|
||||
Does not include GPU usage, nor overhead for sending data to GPU.",
|
||||
);
|
||||
egui::warn_if_debug_build(ui);
|
||||
|
||||
if !cfg!(target_arch = "wasm32") {
|
||||
egui::CollapsingHeader::new("📊 CPU usage history")
|
||||
.default_open(false)
|
||||
.show(ui, |ui| {
|
||||
self.graph(ui);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn graph(&mut self, ui: &mut egui::Ui) -> egui::Response {
|
||||
use egui::*;
|
||||
|
||||
ui.label("egui CPU usage history");
|
||||
|
||||
let history = &self.frame_times;
|
||||
|
||||
// TODO(emilk): we should not use `slider_width` as default graph width.
|
||||
let height = ui.spacing().slider_width;
|
||||
let size = vec2(ui.available_size_before_wrap().x, height);
|
||||
let (rect, response) = ui.allocate_at_least(size, Sense::hover());
|
||||
let style = ui.style().noninteractive();
|
||||
|
||||
let graph_top_cpu_usage = 0.010;
|
||||
let graph_rect = Rect::from_x_y_ranges(history.max_age()..=0.0, graph_top_cpu_usage..=0.0);
|
||||
let to_screen = emath::RectTransform::from_to(graph_rect, rect);
|
||||
|
||||
let mut shapes = Vec::with_capacity(3 + 2 * history.len());
|
||||
shapes.push(Shape::Rect(epaint::RectShape {
|
||||
rect,
|
||||
rounding: style.rounding,
|
||||
fill: ui.visuals().extreme_bg_color,
|
||||
stroke: ui.style().noninteractive().bg_stroke,
|
||||
}));
|
||||
|
||||
let rect = rect.shrink(4.0);
|
||||
let color = ui.visuals().text_color();
|
||||
let line_stroke = Stroke::new(1.0, color);
|
||||
|
||||
if let Some(pointer_pos) = response.hover_pos() {
|
||||
let y = pointer_pos.y;
|
||||
shapes.push(Shape::line_segment(
|
||||
[pos2(rect.left(), y), pos2(rect.right(), y)],
|
||||
line_stroke,
|
||||
));
|
||||
let cpu_usage = to_screen.inverse().transform_pos(pointer_pos).y;
|
||||
let text = format!("{:.1} ms", 1e3 * cpu_usage);
|
||||
shapes.push(ui.fonts(|f| {
|
||||
Shape::text(
|
||||
f,
|
||||
pos2(rect.left(), y),
|
||||
egui::Align2::LEFT_BOTTOM,
|
||||
text,
|
||||
TextStyle::Monospace.resolve(ui.style()),
|
||||
color,
|
||||
)
|
||||
}));
|
||||
}
|
||||
|
||||
let circle_color = color;
|
||||
let radius = 2.0;
|
||||
let right_side_time = ui.input(|i| i.time); // Time at right side of screen
|
||||
|
||||
for (time, cpu_usage) in history.iter() {
|
||||
let age = (right_side_time - time) as f32;
|
||||
let pos = to_screen.transform_pos_clamped(Pos2::new(age, cpu_usage));
|
||||
|
||||
shapes.push(Shape::line_segment(
|
||||
[pos2(pos.x, rect.bottom()), pos],
|
||||
line_stroke,
|
||||
));
|
||||
|
||||
if cpu_usage < graph_top_cpu_usage {
|
||||
shapes.push(Shape::circle_filled(pos, radius, circle_color));
|
||||
}
|
||||
}
|
||||
|
||||
ui.painter().extend(shapes);
|
||||
|
||||
response
|
||||
}
|
||||
}
|
||||
80
egui/crates/egui_demo_app/src/lib.rs
Normal file
80
egui/crates/egui_demo_app/src/lib.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
//! Demo app for egui
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
mod apps;
|
||||
mod backend_panel;
|
||||
pub(crate) mod frame_history;
|
||||
mod wrap_app;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use eframe::web::AppRunnerRef;
|
||||
|
||||
pub use wrap_app::WrapApp;
|
||||
|
||||
/// Time of day as seconds since midnight. Used for clock in demo app.
|
||||
pub(crate) fn seconds_since_midnight() -> f64 {
|
||||
use chrono::Timelike;
|
||||
let time = chrono::Local::now().time();
|
||||
time.num_seconds_from_midnight() as f64 + 1e-9 * (time.nanosecond() as f64)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use eframe::wasm_bindgen::{self, prelude::*};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
pub struct WebHandle {
|
||||
handle: AppRunnerRef,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
impl WebHandle {
|
||||
#[wasm_bindgen]
|
||||
pub fn stop_web(&self) -> Result<(), wasm_bindgen::JsValue> {
|
||||
let mut app = self.handle.lock();
|
||||
app.destroy()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn set_some_content_from_javascript(&mut self, _some_data: &str) {
|
||||
let _app = self.handle.lock().app_mut::<WrapApp>();
|
||||
// _app.data = some_data;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
pub fn init_wasm_hooks() {
|
||||
// Make sure panics are logged using `console.error`.
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
// Redirect tracing to console.log and friends:
|
||||
tracing_wasm::set_as_global_default();
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
pub async fn start_separate(canvas_id: &str) -> Result<WebHandle, wasm_bindgen::JsValue> {
|
||||
let web_options = eframe::WebOptions::default();
|
||||
eframe::start_web(
|
||||
canvas_id,
|
||||
web_options,
|
||||
Box::new(|cc| Box::new(WrapApp::new(cc))),
|
||||
)
|
||||
.await
|
||||
.map(|handle| WebHandle { handle })
|
||||
}
|
||||
|
||||
/// This is the entry-point for all the web-assembly.
|
||||
/// This is called once from the HTML.
|
||||
/// It loads the app, installs some callbacks, then returns.
|
||||
/// You can add more callbacks like this if you want to call in to your code.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
pub async fn start(canvas_id: &str) -> Result<WebHandle, wasm_bindgen::JsValue> {
|
||||
init_wasm_hooks();
|
||||
start_separate(canvas_id).await
|
||||
}
|
||||
36
egui/crates/egui_demo_app/src/main.rs
Normal file
36
egui/crates/egui_demo_app/src/main.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
//! Demo app for egui
|
||||
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
// When compiling natively:
|
||||
fn main() -> Result<(), eframe::Error> {
|
||||
{
|
||||
// Silence wgpu log spam (https://github.com/gfx-rs/wgpu/issues/3206)
|
||||
let mut rust_log = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_owned());
|
||||
for loud_crate in ["naga", "wgpu_core", "wgpu_hal"] {
|
||||
if !rust_log.contains(&format!("{loud_crate}=")) {
|
||||
rust_log += &format!(",{loud_crate}=warn");
|
||||
}
|
||||
}
|
||||
std::env::set_var("RUST_LOG", rust_log);
|
||||
}
|
||||
|
||||
// Log to stdout (if you run with `RUST_LOG=debug`).
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let options = eframe::NativeOptions {
|
||||
drag_and_drop_support: true,
|
||||
|
||||
initial_window_size: Some([1280.0, 1024.0].into()),
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
renderer: eframe::Renderer::Wgpu,
|
||||
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(
|
||||
"egui demo app",
|
||||
options,
|
||||
Box::new(|cc| Box::new(egui_demo_app::WrapApp::new(cc))),
|
||||
)
|
||||
}
|
||||
457
egui/crates/egui_demo_app/src/wrap_app.rs
Normal file
457
egui/crates/egui_demo_app/src/wrap_app.rs
Normal file
|
|
@ -0,0 +1,457 @@
|
|||
use egui_demo_lib::is_mobile;
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
use eframe::glow;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use core::any::Any;
|
||||
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
struct EasyMarkApp {
|
||||
editor: egui_demo_lib::easy_mark::EasyMarkEditor,
|
||||
}
|
||||
|
||||
impl eframe::App for EasyMarkApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
self.editor.panels(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct DemoApp {
|
||||
demo_windows: egui_demo_lib::DemoWindows,
|
||||
}
|
||||
|
||||
impl eframe::App for DemoApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
self.demo_windows.ui(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct FractalClockApp {
|
||||
fractal_clock: crate::apps::FractalClock,
|
||||
}
|
||||
|
||||
impl eframe::App for FractalClockApp {
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default()
|
||||
.frame(egui::Frame::dark_canvas(&ctx.style()))
|
||||
.show(ctx, |ui| {
|
||||
self.fractal_clock
|
||||
.ui(ui, Some(crate::seconds_since_midnight()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct ColorTestApp {
|
||||
color_test: egui_demo_lib::ColorTest,
|
||||
}
|
||||
|
||||
impl eframe::App for ColorTestApp {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
if frame.is_web() {
|
||||
ui.label(
|
||||
"NOTE: Some old browsers stuck on WebGL1 without sRGB support will not pass the color test.",
|
||||
);
|
||||
ui.separator();
|
||||
}
|
||||
egui::ScrollArea::both().auto_shrink([false; 2]).show(ui, |ui| {
|
||||
self.color_test.ui(ui);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
enum Anchor {
|
||||
Demo,
|
||||
EasyMarkEditor,
|
||||
#[cfg(feature = "http")]
|
||||
Http,
|
||||
Clock,
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
Custom3d,
|
||||
Colors,
|
||||
}
|
||||
|
||||
impl Anchor {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn all() -> Vec<Self> {
|
||||
vec![
|
||||
Anchor::Demo,
|
||||
Anchor::EasyMarkEditor,
|
||||
#[cfg(feature = "http")]
|
||||
Anchor::Http,
|
||||
Anchor::Clock,
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
Anchor::Custom3d,
|
||||
Anchor::Colors,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Anchor {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Anchor> for egui::WidgetText {
|
||||
fn from(value: Anchor) -> Self {
|
||||
Self::RichText(egui::RichText::new(value.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Anchor {
|
||||
fn default() -> Self {
|
||||
Self::Demo
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// The state that we persist (serialize).
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct State {
|
||||
demo: DemoApp,
|
||||
easy_mark_editor: EasyMarkApp,
|
||||
#[cfg(feature = "http")]
|
||||
http: crate::apps::HttpApp,
|
||||
clock: FractalClockApp,
|
||||
color_test: ColorTestApp,
|
||||
|
||||
selected_anchor: Anchor,
|
||||
backend_panel: super::backend_panel::BackendPanel,
|
||||
}
|
||||
|
||||
/// Wraps many demo/test apps into one.
|
||||
pub struct WrapApp {
|
||||
state: State,
|
||||
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
custom3d: Option<crate::apps::Custom3d>,
|
||||
|
||||
dropped_files: Vec<egui::DroppedFile>,
|
||||
}
|
||||
|
||||
impl WrapApp {
|
||||
pub fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||
#[allow(unused_mut)]
|
||||
let mut slf = Self {
|
||||
state: State::default(),
|
||||
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
custom3d: crate::apps::Custom3d::new(_cc),
|
||||
|
||||
dropped_files: Default::default(),
|
||||
};
|
||||
|
||||
#[cfg(feature = "persistence")]
|
||||
if let Some(storage) = _cc.storage {
|
||||
if let Some(state) = eframe::get_value(storage, eframe::APP_KEY) {
|
||||
slf.state = state;
|
||||
}
|
||||
}
|
||||
|
||||
slf
|
||||
}
|
||||
|
||||
fn apps_iter_mut(&mut self) -> impl Iterator<Item = (&str, Anchor, &mut dyn eframe::App)> {
|
||||
let mut vec = vec![
|
||||
(
|
||||
"✨ Demos",
|
||||
Anchor::Demo,
|
||||
&mut self.state.demo as &mut dyn eframe::App,
|
||||
),
|
||||
(
|
||||
"🖹 EasyMark editor",
|
||||
Anchor::EasyMarkEditor,
|
||||
&mut self.state.easy_mark_editor as &mut dyn eframe::App,
|
||||
),
|
||||
#[cfg(feature = "http")]
|
||||
(
|
||||
"⬇ HTTP",
|
||||
Anchor::Http,
|
||||
&mut self.state.http as &mut dyn eframe::App,
|
||||
),
|
||||
(
|
||||
"🕑 Fractal Clock",
|
||||
Anchor::Clock,
|
||||
&mut self.state.clock as &mut dyn eframe::App,
|
||||
),
|
||||
];
|
||||
|
||||
#[cfg(any(feature = "glow", feature = "wgpu"))]
|
||||
if let Some(custom3d) = &mut self.custom3d {
|
||||
vec.push((
|
||||
"🔺 3D painting",
|
||||
Anchor::Custom3d,
|
||||
custom3d as &mut dyn eframe::App,
|
||||
));
|
||||
}
|
||||
|
||||
vec.push((
|
||||
"🎨 Color test",
|
||||
Anchor::Colors,
|
||||
&mut self.state.color_test as &mut dyn eframe::App,
|
||||
));
|
||||
|
||||
vec.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for WrapApp {
|
||||
#[cfg(feature = "persistence")]
|
||||
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
||||
eframe::set_value(storage, eframe::APP_KEY, &self.state);
|
||||
}
|
||||
|
||||
fn clear_color(&self, visuals: &egui::Visuals) -> [f32; 4] {
|
||||
visuals.panel_fill.to_normalized_gamma_f32()
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
if let Some(anchor) = frame.info().web_info.location.hash.strip_prefix('#') {
|
||||
let anchor = Anchor::all().into_iter().find(|x| x.to_string() == anchor);
|
||||
if let Some(v) = anchor {
|
||||
self.state.selected_anchor = v;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if ctx.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::F11)) {
|
||||
frame.set_fullscreen(!frame.info().window_info.fullscreen);
|
||||
}
|
||||
|
||||
egui::TopBottomPanel::top("wrap_app_top_bar").show(ctx, |ui| {
|
||||
egui::trace!(ui);
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.visuals_mut().button_frame = false;
|
||||
self.bar_contents(ui, frame);
|
||||
});
|
||||
});
|
||||
|
||||
self.state.backend_panel.update(ctx, frame);
|
||||
|
||||
if !is_mobile(ctx) {
|
||||
self.backend_panel(ctx, frame);
|
||||
}
|
||||
|
||||
self.show_selected_app(ctx, frame);
|
||||
|
||||
self.state.backend_panel.end_of_frame(ctx);
|
||||
|
||||
self.ui_file_drag_and_drop(ctx);
|
||||
|
||||
// On web, the browser controls `pixels_per_point`.
|
||||
if !frame.is_web() {
|
||||
egui::gui_zoom::zoom_with_keyboard_shortcuts(ctx, frame.info().native_pixels_per_point);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "glow")]
|
||||
fn on_exit(&mut self, gl: Option<&glow::Context>) {
|
||||
if let Some(custom3d) = &mut self.custom3d {
|
||||
custom3d.on_exit(gl);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn as_any_mut(&mut self) -> Option<&mut dyn Any> {
|
||||
Some(&mut *self)
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapApp {
|
||||
fn backend_panel(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
// The backend-panel can be toggled on/off.
|
||||
// We show a little animation when the user switches it.
|
||||
let is_open =
|
||||
self.state.backend_panel.open || ctx.memory(|mem| mem.everything_is_visible());
|
||||
|
||||
egui::SidePanel::left("backend_panel")
|
||||
.resizable(false)
|
||||
.show_animated(ctx, is_open, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.heading("💻 Backend");
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
self.backend_panel_contents(ui, frame);
|
||||
});
|
||||
}
|
||||
|
||||
fn backend_panel_contents(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
self.state.backend_panel.ui(ui, frame);
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
if ui
|
||||
.button("Reset egui")
|
||||
.on_hover_text("Forget scroll, positions, sizes etc")
|
||||
.clicked()
|
||||
{
|
||||
ui.ctx().memory_mut(|mem| *mem = Default::default());
|
||||
ui.close_menu();
|
||||
}
|
||||
|
||||
if ui.button("Reset everything").clicked() {
|
||||
self.state = Default::default();
|
||||
ui.ctx().memory_mut(|mem| *mem = Default::default());
|
||||
ui.close_menu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn show_selected_app(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
let selected_anchor = self.state.selected_anchor;
|
||||
for (_name, anchor, app) in self.apps_iter_mut() {
|
||||
if anchor == selected_anchor || ctx.memory(|mem| mem.everything_is_visible()) {
|
||||
app.update(ctx, frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bar_contents(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
|
||||
egui::widgets::global_dark_light_mode_switch(ui);
|
||||
|
||||
ui.separator();
|
||||
|
||||
if is_mobile(ui.ctx()) {
|
||||
ui.menu_button("💻 Backend", |ui| {
|
||||
ui.set_style(ui.ctx().style()); // ignore the "menu" style set by `menu_button`.
|
||||
self.backend_panel_contents(ui, frame);
|
||||
});
|
||||
} else {
|
||||
ui.toggle_value(&mut self.state.backend_panel.open, "💻 Backend");
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
let mut selected_anchor = self.state.selected_anchor;
|
||||
for (name, anchor, _app) in self.apps_iter_mut() {
|
||||
if ui
|
||||
.selectable_label(selected_anchor == anchor, name)
|
||||
.clicked()
|
||||
{
|
||||
selected_anchor = anchor;
|
||||
if frame.is_web() {
|
||||
ui.output_mut(|o| o.open_url(format!("#{}", anchor)));
|
||||
}
|
||||
}
|
||||
}
|
||||
self.state.selected_anchor = selected_anchor;
|
||||
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if false {
|
||||
// TODO(emilk): fix the overlap on small screens
|
||||
if clock_button(ui, crate::seconds_since_midnight()).clicked() {
|
||||
self.state.selected_anchor = Anchor::Clock;
|
||||
if frame.is_web() {
|
||||
ui.output_mut(|o| o.open_url("#clock"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
egui::warn_if_debug_build(ui);
|
||||
});
|
||||
}
|
||||
|
||||
fn ui_file_drag_and_drop(&mut self, ctx: &egui::Context) {
|
||||
use egui::*;
|
||||
use std::fmt::Write as _;
|
||||
|
||||
// Preview hovering files:
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
// Collect dropped files:
|
||||
ctx.input(|i| {
|
||||
if !i.raw.dropped_files.is_empty() {
|
||||
self.dropped_files = i.raw.dropped_files.clone();
|
||||
}
|
||||
});
|
||||
|
||||
// Show dropped files (if any):
|
||||
if !self.dropped_files.is_empty() {
|
||||
let mut open = true;
|
||||
egui::Window::new("Dropped files")
|
||||
.open(&mut open)
|
||||
.show(ctx, |ui| {
|
||||
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 {
|
||||
write!(info, " ({} bytes)", bytes.len()).ok();
|
||||
}
|
||||
ui.label(info);
|
||||
}
|
||||
});
|
||||
if !open {
|
||||
self.dropped_files.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clock_button(ui: &mut egui::Ui, seconds_since_midnight: f64) -> egui::Response {
|
||||
let time = seconds_since_midnight;
|
||||
let time = format!(
|
||||
"{:02}:{:02}:{:02}.{:02}",
|
||||
(time % (24.0 * 60.0 * 60.0) / 3600.0).floor(),
|
||||
(time % (60.0 * 60.0) / 60.0).floor(),
|
||||
(time % 60.0).floor(),
|
||||
(time % 1.0 * 100.0).floor()
|
||||
);
|
||||
|
||||
ui.button(egui::RichText::new(time).monospace())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue