mirror of
https://github.com/emilk/egui.git
synced 2026-09-01 14:20:04 -04:00
Move all crates into a crates directory (#1940)
This commit is contained in:
188
crates/egui_demo_app/src/apps/custom3d_glow.rs
Normal file
188
crates/egui_demo_app/src/apps/custom3d_glow.rs
Normal file
@@ -0,0 +1,188 @@
|
||||
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>) -> Self {
|
||||
Self {
|
||||
rotating_triangle: Arc::new(Mutex::new(RotatingTriangle::new(
|
||||
cc.gl.as_ref().expect("GL Enabled"),
|
||||
))),
|
||||
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) -> Self {
|
||||
use glow::HasContext as _;
|
||||
|
||||
let shader_version = if cfg!(target_arch = "wasm32") {
|
||||
"#version 300 es"
|
||||
} else {
|
||||
"#version 330"
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let program = gl.create_program().expect("Cannot create program");
|
||||
|
||||
let (vertex_shader_source, fragment_shader_source) = (
|
||||
r#"
|
||||
const vec2 verts[3] = vec2[3](
|
||||
vec2(0.0, 1.0),
|
||||
vec2(-1.0, -1.0),
|
||||
vec2(1.0, -1.0)
|
||||
);
|
||||
const vec4 colors[3] = vec4[3](
|
||||
vec4(1.0, 0.0, 0.0, 1.0),
|
||||
vec4(0.0, 1.0, 0.0, 1.0),
|
||||
vec4(0.0, 0.0, 1.0, 1.0)
|
||||
);
|
||||
out vec4 v_color;
|
||||
uniform float u_angle;
|
||||
void main() {
|
||||
v_color = colors[gl_VertexID];
|
||||
gl_Position = vec4(verts[gl_VertexID], 0.0, 1.0);
|
||||
gl_Position.x *= cos(u_angle);
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
precision mediump float;
|
||||
in vec4 v_color;
|
||||
out vec4 out_color;
|
||||
void main() {
|
||||
out_color = v_color;
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
let shader_sources = [
|
||||
(glow::VERTEX_SHADER, vertex_shader_source),
|
||||
(glow::FRAGMENT_SHADER, fragment_shader_source),
|
||||
];
|
||||
|
||||
let shaders: Vec<_> = shader_sources
|
||||
.iter()
|
||||
.map(|(shader_type, shader_source)| {
|
||||
let shader = gl
|
||||
.create_shader(*shader_type)
|
||||
.expect("Cannot create shader");
|
||||
gl.shader_source(shader, &format!("{}\n{}", shader_version, shader_source));
|
||||
gl.compile_shader(shader);
|
||||
if !gl.get_shader_compile_status(shader) {
|
||||
panic!("{}", 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");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
177
crates/egui_demo_app/src/apps/custom3d_wgpu.rs
Normal file
177
crates/egui_demo_app/src/apps/custom3d_wgpu.rs
Normal file
@@ -0,0 +1,177 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use eframe::{
|
||||
egui_wgpu::{self, wgpu},
|
||||
wgpu::util::DeviceExt,
|
||||
};
|
||||
|
||||
pub struct Custom3d {
|
||||
angle: f32,
|
||||
}
|
||||
|
||||
impl Custom3d {
|
||||
pub fn new<'a>(cc: &'a eframe::CreationContext<'a>) -> 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().expect("WGPU enabled");
|
||||
|
||||
let device = &wgpu_render_state.device;
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: None,
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("./custom3d_wgpu_shader.wgsl").into()),
|
||||
});
|
||||
|
||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: None,
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: None,
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: None,
|
||||
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: None,
|
||||
contents: bytemuck::cast_slice(&[0.0]),
|
||||
usage: wgpu::BufferUsages::COPY_DST
|
||||
| wgpu::BufferUsages::MAP_WRITE
|
||||
| wgpu::BufferUsages::UNIFORM,
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: None,
|
||||
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
|
||||
.egui_rpass
|
||||
.write()
|
||||
.paint_callback_resources
|
||||
.insert(TriangleRenderResources {
|
||||
pipeline,
|
||||
bind_group,
|
||||
uniform_buffer,
|
||||
});
|
||||
|
||||
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.
|
||||
//
|
||||
// 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, paint_callback_resources| {
|
||||
let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap();
|
||||
resources.prepare(device, queue, angle);
|
||||
})
|
||||
.paint(move |_info, rpass, paint_callback_resources| {
|
||||
let resources: &TriangleRenderResources = paint_callback_resources.get().unwrap();
|
||||
resources.paint(rpass);
|
||||
});
|
||||
|
||||
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]));
|
||||
}
|
||||
|
||||
fn paint<'rpass>(&'rpass self, rpass: &mut wgpu::RenderPass<'rpass>) {
|
||||
// Draw our triangle!
|
||||
rpass.set_pipeline(&self.pipeline);
|
||||
rpass.set_bind_group(0, &self.bind_group, &[]);
|
||||
rpass.draw(0..3, 0..1);
|
||||
}
|
||||
}
|
||||
39
crates/egui_demo_app/src/apps/custom3d_wgpu_shader.wgsl
Normal file
39
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 {
|
||||
angle: f32,
|
||||
};
|
||||
|
||||
@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
crates/egui_demo_app/src/apps/fractal_clock.rs
Normal file
205
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().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
crates/egui_demo_app/src/apps/http_app.rs
Normal file
266
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().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().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().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().layout_job(job);
|
||||
let (response, painter) = ui.allocate_painter(galley.size(), egui::Sense::hover());
|
||||
painter.add(egui::Shape::galley(response.rect.min, galley));
|
||||
}
|
||||
}
|
||||
}
|
||||
21
crates/egui_demo_app/src/apps/mod.rs
Normal file
21
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;
|
||||
Reference in New Issue
Block a user