1
0
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:
Emil Ernerfeldt
2022-08-20 10:41:49 +02:00
committed by GitHub
parent 5c63648c02
commit 041f2e64ba
260 changed files with 59 additions and 57 deletions

View 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);
}
}
}

View 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);
}
}

View 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;
}

View 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);
}
}

View 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));
}
}
}

View 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;