1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-30 05:10:03 -04:00

Merge remote-tracking branch 'egui/master' into dynamic-grid

This commit is contained in:
René Rössler
2022-02-07 10:03:48 +01:00
179 changed files with 8173 additions and 3350 deletions

View File

@@ -18,22 +18,43 @@ all-features = true
[lib]
[features]
default = ["datetime"]
# Enable additional checks if debug assertions are enabled (debug builds).
extra_debug_asserts = ["egui/extra_debug_asserts"]
# Always enable additional checks.
extra_asserts = ["egui/extra_asserts"]
datetime = ["egui_extras/chrono", "chrono"]
http = ["ehttp", "image", "poll-promise"]
persistence = [
"egui/persistence",
"epi/persistence",
"egui_extras/persistence",
"serde",
]
serialize = ["egui/serialize", "serde"]
syntax_highlighting = ["syntect"]
[dependencies]
egui = { version = "0.16.0", path = "../egui", default-features = false }
epi = { version = "0.16.0", path = "../epi" }
egui_extras = { version = "0.16.0", path = "../egui_extras" }
chrono = { version = "0.4", features = ["js-sys", "wasmbind"], optional = true }
enum-map = { version = "1", features = ["serde"] }
enum-map = { version = "2", features = ["serde"] }
unicode_names2 = { version = "0.4.0", default-features = false }
# feature "http":
ehttp = { version = "0.1.0", optional = true }
image = { version = "0.23", default-features = false, features = [
ehttp = { version = "0.2.0", optional = true }
image = { version = "0.24", default-features = false, features = [
"jpeg",
"png",
], optional = true }
poll-promise = { version = "0.1", default-features = false, optional = true }
# feature "syntax_highlighting":
syntect = { version = "4", default-features = false, features = [
"default-fancy",
@@ -45,25 +66,6 @@ serde = { version = "1", features = ["derive"], optional = true }
[dev-dependencies]
criterion = { version = "0.3", default-features = false }
[features]
default = ["datetime"]
datetime = ["egui_extras/datetime", "chrono"]
# Enable additional checks if debug assertions are enabled (debug builds).
extra_debug_asserts = ["egui/extra_debug_asserts"]
# Always enable additional checks.
extra_asserts = ["egui/extra_asserts"]
http = ["ehttp", "image"]
persistence = [
"egui/persistence",
"epi/persistence",
"egui_extras/persistence",
"serde",
]
serialize = ["egui/serialize", "serde"]
syntax_highlighting = ["syntect"]
[[bench]]
name = "benchmark"
harness = false

View File

@@ -4,16 +4,16 @@ use egui::epaint::TextShape;
use egui_demo_lib::LOREM_IPSUM_LONG;
pub fn criterion_benchmark(c: &mut Criterion) {
let raw_input = egui::RawInput::default();
use egui::RawInput;
{
let mut ctx = egui::CtxRef::default();
let ctx = egui::Context::default();
let mut demo_windows = egui_demo_lib::DemoWindows::default();
// The most end-to-end benchmark.
c.bench_function("demo_with_tessellate__realistic", |b| {
b.iter(|| {
let (_output, shapes) = ctx.run(raw_input.clone(), |ctx| {
let (_output, shapes) = ctx.run(RawInput::default(), |ctx| {
demo_windows.ui(ctx);
});
ctx.tessellate(shapes)
@@ -22,13 +22,13 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("demo_no_tessellate", |b| {
b.iter(|| {
ctx.run(raw_input.clone(), |ctx| {
ctx.run(RawInput::default(), |ctx| {
demo_windows.ui(ctx);
})
})
});
let (_output, shapes) = ctx.run(raw_input.clone(), |ctx| {
let (_output, shapes) = ctx.run(RawInput::default(), |ctx| {
demo_windows.ui(ctx);
});
c.bench_function("demo_only_tessellate", |b| {
@@ -37,12 +37,12 @@ pub fn criterion_benchmark(c: &mut Criterion) {
}
if false {
let mut ctx = egui::CtxRef::default();
let ctx = egui::Context::default();
ctx.memory().set_everything_is_visible(true); // give us everything
let mut demo_windows = egui_demo_lib::DemoWindows::default();
c.bench_function("demo_full_no_tessellate", |b| {
b.iter(|| {
ctx.run(raw_input.clone(), |ctx| {
ctx.run(RawInput::default(), |ctx| {
demo_windows.ui(ctx);
})
})
@@ -50,8 +50,8 @@ pub fn criterion_benchmark(c: &mut Criterion) {
}
{
let mut ctx = egui::CtxRef::default();
let _ = ctx.run(raw_input, |ctx| {
let ctx = egui::Context::default();
let _ = ctx.run(RawInput::default(), |ctx| {
egui::CentralPanel::default().show(ctx, |ui| {
c.bench_function("label &str", |b| {
b.iter(|| {
@@ -68,40 +68,68 @@ pub fn criterion_benchmark(c: &mut Criterion) {
}
{
let pixels_per_point = 1.0;
let wrap_width = 512.0;
let text_style = egui::TextStyle::Body;
let color = egui::Color32::WHITE;
let fonts =
egui::epaint::text::Fonts::new(pixels_per_point, egui::FontDefinitions::default());
c.bench_function("text_layout_uncached", |b| {
b.iter(|| {
use egui::epaint::text::{layout, LayoutJob};
let ctx = egui::Context::default();
ctx.begin_frame(RawInput::default());
let job = LayoutJob::simple(
egui::CentralPanel::default().show(&ctx, |ui| {
c.bench_function("Painter::rect", |b| {
let painter = ui.painter();
let rect = ui.max_rect();
b.iter(|| {
painter.rect(rect, 2.0, egui::Color32::RED, (1.0, egui::Color32::WHITE));
})
});
});
// Don't call `end_frame` to not have to drain the huge paint list
}
{
let pixels_per_point = 1.0;
let max_texture_side = 8 * 1024;
let wrap_width = 512.0;
let font_id = egui::FontId::default();
let color = egui::Color32::WHITE;
let fonts = egui::epaint::text::Fonts::new(
pixels_per_point,
max_texture_side,
egui::FontDefinitions::default(),
);
{
let mut locked_fonts = fonts.lock();
c.bench_function("text_layout_uncached", |b| {
b.iter(|| {
use egui::epaint::text::{layout, LayoutJob};
let job = LayoutJob::simple(
LOREM_IPSUM_LONG.to_owned(),
font_id.clone(),
color,
wrap_width,
);
layout(&mut locked_fonts.fonts, job.into())
})
});
}
c.bench_function("text_layout_cached", |b| {
b.iter(|| {
fonts.layout(
LOREM_IPSUM_LONG.to_owned(),
egui::TextStyle::Body,
font_id.clone(),
color,
wrap_width,
);
layout(&fonts, job.into())
)
})
});
c.bench_function("text_layout_cached", |b| {
b.iter(|| fonts.layout(LOREM_IPSUM_LONG.to_owned(), text_style, color, wrap_width))
});
let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), text_style, color, wrap_width);
let galley = fonts.layout(LOREM_IPSUM_LONG.to_owned(), font_id, color, wrap_width);
let mut tessellator = egui::epaint::Tessellator::from_options(Default::default());
let mut mesh = egui::epaint::Mesh::default();
let text_shape = TextShape::new(egui::Pos2::ZERO, galley);
let font_image_size = fonts.font_image_size();
c.bench_function("tessellate_text", |b| {
b.iter(|| {
tessellator.tessellate_text(
fonts.font_image().size(),
text_shape.clone(),
&mut mesh,
);
tessellator.tessellate_text(font_image_size, text_shape.clone(), &mut mesh);
mesh.clear();
})
});

View File

@@ -34,7 +34,7 @@ impl epi::App for ColorTest {
"🎨 Color test"
}
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
if frame.is_web() {
ui.label(
@@ -43,14 +43,14 @@ impl epi::App for ColorTest {
ui.separator();
}
ScrollArea::both().auto_shrink([false; 2]).show(ui, |ui| {
self.ui(ui, Some(frame));
self.ui(ui);
});
});
}
}
impl ColorTest {
pub fn ui(&mut self, ui: &mut Ui, tex_allocator: Option<&dyn epi::TextureAllocator>) {
pub fn ui(&mut self, ui: &mut Ui) {
ui.set_max_width(680.0);
ui.vertical_centered(|ui| {
@@ -70,13 +70,7 @@ impl ColorTest {
ui.spacing_mut().item_spacing.y = 0.0; // No spacing between gradients
let g = Gradient::one_color(Color32::from_rgb(255, 165, 0));
self.vertex_gradient(ui, "orange rgb(255, 165, 0) - vertex", WHITE, &g);
self.tex_gradient(
ui,
tex_allocator,
"orange rgb(255, 165, 0) - texture",
WHITE,
&g,
);
self.tex_gradient(ui, "orange rgb(255, 165, 0) - texture", WHITE, &g);
});
ui.separator();
@@ -99,20 +93,18 @@ impl ColorTest {
{
let g = Gradient::one_color(Color32::from(tex_color * vertex_color));
self.vertex_gradient(ui, "Ground truth (vertices)", WHITE, &g);
self.tex_gradient(ui, tex_allocator, "Ground truth (texture)", WHITE, &g);
}
if let Some(tex_allocator) = tex_allocator {
ui.horizontal(|ui| {
let g = Gradient::one_color(Color32::from(tex_color));
let tex = self.tex_mngr.get(tex_allocator, &g);
let texel_offset = 0.5 / (g.0.len() as f32);
let uv =
Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0));
ui.add(Image::new(tex, GRADIENT_SIZE).tint(vertex_color).uv(uv))
.on_hover_text(format!("A texture that is {} texels wide", g.0.len()));
ui.label("GPU result");
});
self.tex_gradient(ui, "Ground truth (texture)", WHITE, &g);
}
ui.horizontal(|ui| {
let g = Gradient::one_color(Color32::from(tex_color));
let tex = self.tex_mngr.get(ui.ctx(), &g);
let texel_offset = 0.5 / (g.0.len() as f32);
let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0));
ui.add(Image::new(tex, GRADIENT_SIZE).tint(vertex_color).uv(uv))
.on_hover_text(format!("A texture that is {} texels wide", g.0.len()));
ui.label("GPU result");
});
});
ui.separator();
@@ -120,18 +112,18 @@ impl ColorTest {
// TODO: test color multiplication (image tint),
// to make sure vertex and texture color multiplication is done in linear space.
self.show_gradients(ui, tex_allocator, WHITE, (RED, GREEN));
self.show_gradients(ui, WHITE, (RED, GREEN));
if self.srgb {
ui.label("Notice the darkening in the center of the naive sRGB interpolation.");
}
ui.separator();
self.show_gradients(ui, tex_allocator, RED, (TRANSPARENT, GREEN));
self.show_gradients(ui, RED, (TRANSPARENT, GREEN));
ui.separator();
self.show_gradients(ui, tex_allocator, WHITE, (TRANSPARENT, GREEN));
self.show_gradients(ui, WHITE, (TRANSPARENT, GREEN));
if self.srgb {
ui.label(
"Notice how the linear blend stays green while the naive sRGBA interpolation looks gray in the middle.",
@@ -142,15 +134,14 @@ impl ColorTest {
// TODO: another ground truth where we do the alpha-blending against the background also.
// TODO: exactly the same thing, but with vertex colors (no textures)
self.show_gradients(ui, tex_allocator, WHITE, (TRANSPARENT, BLACK));
self.show_gradients(ui, WHITE, (TRANSPARENT, BLACK));
ui.separator();
self.show_gradients(ui, tex_allocator, BLACK, (TRANSPARENT, WHITE));
self.show_gradients(ui, BLACK, (TRANSPARENT, WHITE));
ui.separator();
ui.label("Additive blending: add more and more blue to the red background:");
self.show_gradients(
ui,
tex_allocator,
RED,
(TRANSPARENT, Color32::from_rgb_additive(0, 0, 255)),
);
@@ -160,13 +151,7 @@ impl ColorTest {
pixel_test(ui);
}
fn show_gradients(
&mut self,
ui: &mut Ui,
tex_allocator: Option<&dyn epi::TextureAllocator>,
bg_fill: Color32,
(left, right): (Color32, Color32),
) {
fn show_gradients(&mut self, ui: &mut Ui, bg_fill: Color32, (left, right): (Color32, Color32)) {
let is_opaque = left.is_opaque() && right.is_opaque();
ui.horizontal(|ui| {
@@ -186,13 +171,7 @@ impl ColorTest {
if is_opaque {
let g = Gradient::ground_truth_linear_gradient(left, right);
self.vertex_gradient(ui, "Ground Truth (CPU gradient) - vertices", bg_fill, &g);
self.tex_gradient(
ui,
tex_allocator,
"Ground Truth (CPU gradient) - texture",
bg_fill,
&g,
);
self.tex_gradient(ui, "Ground Truth (CPU gradient) - texture", bg_fill, &g);
} else {
let g = Gradient::ground_truth_linear_gradient(left, right).with_bg_fill(bg_fill);
self.vertex_gradient(
@@ -203,20 +182,13 @@ impl ColorTest {
);
self.tex_gradient(
ui,
tex_allocator,
"Ground Truth (CPU gradient, CPU blending) - texture",
bg_fill,
&g,
);
let g = Gradient::ground_truth_linear_gradient(left, right);
self.vertex_gradient(ui, "CPU gradient, GPU blending - vertices", bg_fill, &g);
self.tex_gradient(
ui,
tex_allocator,
"CPU gradient, GPU blending - texture",
bg_fill,
&g,
);
self.tex_gradient(ui, "CPU gradient, GPU blending - texture", bg_fill, &g);
}
let g = Gradient::texture_gradient(left, right);
@@ -226,13 +198,7 @@ impl ColorTest {
bg_fill,
&g,
);
self.tex_gradient(
ui,
tex_allocator,
"Texture of width 2 (test texture sampler)",
bg_fill,
&g,
);
self.tex_gradient(ui, "Texture of width 2 (test texture sampler)", bg_fill, &g);
if self.srgb {
let g =
@@ -243,41 +209,26 @@ impl ColorTest {
bg_fill,
&g,
);
self.tex_gradient(
ui,
tex_allocator,
"Naive sRGBA interpolation (WRONG)",
bg_fill,
&g,
);
self.tex_gradient(ui, "Naive sRGBA interpolation (WRONG)", bg_fill, &g);
}
});
}
fn tex_gradient(
&mut self,
ui: &mut Ui,
tex_allocator: Option<&dyn epi::TextureAllocator>,
label: &str,
bg_fill: Color32,
gradient: &Gradient,
) {
fn tex_gradient(&mut self, ui: &mut Ui, label: &str, bg_fill: Color32, gradient: &Gradient) {
if !self.texture_gradients {
return;
}
if let Some(tex_allocator) = tex_allocator {
ui.horizontal(|ui| {
let tex = self.tex_mngr.get(tex_allocator, gradient);
let texel_offset = 0.5 / (gradient.0.len() as f32);
let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0));
ui.add(Image::new(tex, GRADIENT_SIZE).bg_fill(bg_fill).uv(uv))
.on_hover_text(format!(
"A texture that is {} texels wide",
gradient.0.len()
));
ui.label(label);
});
}
ui.horizontal(|ui| {
let tex = self.tex_mngr.get(ui.ctx(), gradient);
let texel_offset = 0.5 / (gradient.0.len() as f32);
let uv = Rect::from_min_max(pos2(texel_offset, 0.0), pos2(1.0 - texel_offset, 1.0));
ui.add(Image::new(tex, GRADIENT_SIZE).bg_fill(bg_fill).uv(uv))
.on_hover_text(format!(
"A texture that is {} texels wide",
gradient.0.len()
));
ui.label(label);
});
}
fn vertex_gradient(&mut self, ui: &mut Ui, label: &str, bg_fill: Color32, gradient: &Gradient) {
@@ -384,18 +335,21 @@ impl Gradient {
}
#[derive(Default)]
struct TextureManager(HashMap<Gradient, TextureId>);
struct TextureManager(HashMap<Gradient, TextureHandle>);
impl TextureManager {
fn get(&mut self, tex_allocator: &dyn epi::TextureAllocator, gradient: &Gradient) -> TextureId {
*self.0.entry(gradient.clone()).or_insert_with(|| {
fn get(&mut self, ctx: &egui::Context, gradient: &Gradient) -> &TextureHandle {
self.0.entry(gradient.clone()).or_insert_with(|| {
let pixels = gradient.to_pixel_row();
let width = pixels.len();
let height = 1;
tex_allocator.alloc(epi::Image {
size: [width, height],
pixels,
})
ctx.load_texture(
"color_test_gradient",
epaint::ColorImage {
size: [width, height],
pixels,
},
)
})
}
}

View File

@@ -16,7 +16,7 @@ impl epi::App for DemoApp {
fn setup(
&mut self,
_ctx: &egui::CtxRef,
_ctx: &egui::Context,
_frame: &epi::Frame,
_storage: Option<&dyn epi::Storage>,
) {
@@ -31,7 +31,7 @@ impl epi::App for DemoApp {
epi::set_value(storage, epi::APP_KEY, self);
}
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
self.demo_windows.ui(ctx);
}
}

View File

@@ -26,7 +26,7 @@ impl super::Demo for CodeEditor {
"🖮 Code Editor"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
use super::View as _;
egui::Window::new(self.name())
.open(open)
@@ -71,7 +71,7 @@ impl super::View for CodeEditor {
ui.collapsing("Theme", |ui| {
ui.group(|ui| {
theme.ui(ui);
theme.store_in_memory(ui.ctx());
theme.clone().store_in_memory(ui.ctx());
});
});
@@ -85,7 +85,7 @@ impl super::View for CodeEditor {
egui::ScrollArea::vertical().show(ui, |ui| {
ui.add(
egui::TextEdit::multiline(code)
.text_style(egui::TextStyle::Monospace) // for cursor height
.font(egui::TextStyle::Monospace) // for cursor height
.code_editor()
.desired_rows(10)
.lock_focus(true)

View File

@@ -68,7 +68,7 @@ impl super::Demo for CodeExample {
"🖮 Code Example"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
use super::View;
egui::Window::new(self.name())
.open(open)
@@ -98,7 +98,8 @@ impl CodeExample {
);
ui.horizontal(|ui| {
let indentation = 8.0 * ui.fonts()[egui::TextStyle::Monospace].glyph_width(' ');
let font_id = egui::TextStyle::Monospace.resolve(ui.style());
let indentation = 8.0 * ui.fonts().glyph_width(&font_id, ' ');
let item_spacing = ui.spacing_mut().item_spacing;
ui.add_space(indentation - item_spacing.x);

View File

@@ -47,7 +47,7 @@ impl super::Demo for ContextMenus {
"☰ Context Menus"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
use super::View;
egui::Window::new(self.name())
.vscroll(false)

View File

@@ -10,7 +10,7 @@ impl super::Demo for DancingStrings {
"♫ Dancing Strings"
}
fn show(&mut self, ctx: &CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &Context, open: &mut bool) {
use super::View as _;
Window::new(self.name())
.open(open)

View File

@@ -1,5 +1,5 @@
use super::Demo;
use egui::{CtxRef, ScrollArea, Ui};
use egui::{Context, ScrollArea, Ui};
use std::collections::BTreeSet;
// ----------------------------------------------------------------------------
@@ -26,6 +26,7 @@ impl Default for Demos {
Box::new(super::MiscDemoWindow::default()),
Box::new(super::multi_touch::MultiTouch::default()),
Box::new(super::painting::Painting::default()),
Box::new(super::paint_bezier::PaintBezier::default()),
Box::new(super::plot_demo::PlotDemo::default()),
Box::new(super::scrolling::Scrolling::default()),
Box::new(super::sliders::Sliders::default()),
@@ -60,7 +61,7 @@ impl Demos {
}
}
pub fn windows(&mut self, ctx: &CtxRef) {
pub fn windows(&mut self, ctx: &Context) {
let Self { demos, open } = self;
for demo in demos {
let mut is_open = open.contains(demo.name());
@@ -115,7 +116,7 @@ impl Tests {
}
}
pub fn windows(&mut self, ctx: &CtxRef) {
pub fn windows(&mut self, ctx: &Context) {
let Self { demos, open } = self;
for demo in demos {
let mut is_open = open.contains(demo.name());
@@ -151,7 +152,7 @@ pub struct DemoWindows {
impl DemoWindows {
/// Show the app ui (menu bar and windows).
/// `sidebar_ui` can be used to optionally show some things in the sidebar
pub fn ui(&mut self, ctx: &CtxRef) {
pub fn ui(&mut self, ctx: &Context) {
let Self { demos, tests } = self;
egui::SidePanel::right("egui_demo_panel")
@@ -216,7 +217,7 @@ impl DemoWindows {
}
/// Show the open windows.
fn windows(&mut self, ctx: &CtxRef) {
fn windows(&mut self, ctx: &Context) {
let Self { demos, tests } = self;
demos.windows(ctx);

View File

@@ -25,7 +25,7 @@ pub fn drag_source(ui: &mut Ui, id: Id, body: impl FnOnce(&mut Ui)) {
// (anything with `Order::Tooltip` always gets an empty `Response`)
// So this is fine!
if let Some(pointer_pos) = ui.input().pointer.interact_pos() {
if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() {
let delta = pointer_pos - response.rect.center();
ui.ctx().translate_layer(layer_id, delta);
}
@@ -66,7 +66,7 @@ pub fn drop_target<R>(
ui.painter().set(
where_to_put_background,
epaint::RectShape {
corner_radius: style.corner_radius,
rounding: style.rounding,
fill,
stroke,
rect,
@@ -101,7 +101,7 @@ impl super::Demo for DragAndDropDemo {
"✋ Drag and Drop"
}
fn show(&mut self, ctx: &CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &Context, open: &mut bool) {
use super::View as _;
Window::new(self.name())
.open(open)

View File

@@ -2,15 +2,15 @@ use std::collections::BTreeMap;
pub struct FontBook {
filter: String,
text_style: egui::TextStyle,
named_chars: BTreeMap<egui::TextStyle, BTreeMap<char, String>>,
font_id: egui::FontId,
named_chars: BTreeMap<egui::FontFamily, BTreeMap<char, String>>,
}
impl Default for FontBook {
fn default() -> Self {
Self {
filter: Default::default(),
text_style: egui::TextStyle::Button,
font_id: egui::FontId::proportional(20.0),
named_chars: Default::default(),
}
}
@@ -21,7 +21,7 @@ impl super::Demo for FontBook {
"🔤 Font Book"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name()).open(open).show(ctx, |ui| {
use super::View as _;
self.ui(ui);
@@ -34,7 +34,7 @@ impl super::View for FontBook {
ui.label(format!(
"The selected font supports {} characters.",
self.named_chars
.get(&self.text_style)
.get(&self.font_id.family)
.map(|map| map.len())
.unwrap_or_default()
));
@@ -51,13 +51,7 @@ impl super::View for FontBook {
ui.separator();
egui::ComboBox::from_label("Text style")
.selected_text(format!("{:?}", self.text_style))
.show_ui(ui, |ui| {
for style in egui::TextStyle::all() {
ui.selectable_value(&mut self.text_style, style, format!("{:?}", style));
}
});
egui::introspection::font_id_ui(ui, &mut self.font_id);
ui.horizontal(|ui| {
ui.label("Filter:");
@@ -68,16 +62,11 @@ impl super::View for FontBook {
}
});
let text_style = self.text_style;
let filter = &self.filter;
let named_chars = self.named_chars.entry(text_style).or_insert_with(|| {
ui.fonts()[text_style]
.characters()
.iter()
.filter(|chr| !chr.is_whitespace() && !chr.is_ascii_control())
.map(|&chr| (chr, char_name(chr)))
.collect()
});
let named_chars = self
.named_chars
.entry(self.font_id.family.clone())
.or_insert_with(|| available_characters(ui, self.font_id.family.clone()));
ui.separator();
@@ -88,12 +77,14 @@ impl super::View for FontBook {
for (&chr, name) in named_chars {
if filter.is_empty() || name.contains(filter) || *filter == chr.to_string() {
let button = egui::Button::new(
egui::RichText::new(chr.to_string()).text_style(text_style),
egui::RichText::new(chr.to_string()).font(self.font_id.clone()),
)
.frame(false);
let tooltip_ui = |ui: &mut egui::Ui| {
ui.label(egui::RichText::new(chr.to_string()).text_style(text_style));
ui.label(
egui::RichText::new(chr.to_string()).font(self.font_id.clone()),
);
ui.label(format!("{}\nU+{:X}\n\nClick to copy", name, chr as u32));
};
@@ -107,6 +98,18 @@ impl super::View for FontBook {
}
}
fn available_characters(ui: &egui::Ui, family: egui::FontFamily) -> BTreeMap<char, String> {
ui.fonts()
.lock()
.fonts
.font(&egui::FontId::new(10.0, family)) // size is arbitrary for getting the characters
.characters()
.iter()
.filter(|chr| !chr.is_whitespace() && !chr.is_ascii_control())
.map(|&chr| (chr, char_name(chr)))
.collect()
}
fn char_name(chr: char) -> String {
special_char_name(chr)
.map(|s| s.to_owned())

View File

@@ -11,7 +11,7 @@ impl super::Demo for GridDemo {
"▣ Grid Demo"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.open(open)
.resizable(true)

View File

@@ -76,7 +76,7 @@ impl super::Demo for LayoutTest {
"Layout Test"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.open(open)
.resizable(false)

View File

@@ -31,7 +31,7 @@ impl Demo for MiscDemoWindow {
"✨ Misc Demos"
}
fn show(&mut self, ctx: &CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &Context, open: &mut bool) {
Window::new(self.name())
.open(open)
.vscroll(true)
@@ -140,7 +140,8 @@ impl Widgets {
ui.horizontal_wrapped(|ui| {
// Trick so we don't have to add spaces in the text below:
ui.spacing_mut().item_spacing.x = ui.fonts()[TextStyle::Body].glyph_width(' ');
let width = ui.fonts().glyph_width(&TextStyle::Body.resolve(ui.style()), ' ');
ui.spacing_mut().item_spacing.x = width;
ui.label(RichText::new("Text can have").color(Color32::from_rgb(110, 255, 110)));
ui.colored_label(Color32::from_rgb(128, 140, 255), "color"); // Shortcut version
@@ -268,7 +269,7 @@ impl ColorWidgets {
#[cfg_attr(feature = "serde", serde(default))]
struct BoxPainting {
size: Vec2,
corner_radius: f32,
rounding: f32,
stroke_width: f32,
num_boxes: usize,
}
@@ -277,7 +278,7 @@ impl Default for BoxPainting {
fn default() -> Self {
Self {
size: vec2(64.0, 32.0),
corner_radius: 5.0,
rounding: 5.0,
stroke_width: 2.0,
num_boxes: 1,
}
@@ -288,7 +289,7 @@ impl BoxPainting {
pub fn ui(&mut self, ui: &mut Ui) {
ui.add(Slider::new(&mut self.size.x, 0.0..=500.0).text("width"));
ui.add(Slider::new(&mut self.size.y, 0.0..=500.0).text("height"));
ui.add(Slider::new(&mut self.corner_radius, 0.0..=50.0).text("corner_radius"));
ui.add(Slider::new(&mut self.rounding, 0.0..=50.0).text("rounding"));
ui.add(Slider::new(&mut self.stroke_width, 0.0..=10.0).text("stroke_width"));
ui.add(Slider::new(&mut self.num_boxes, 0..=8).text("num_boxes"));
@@ -297,7 +298,7 @@ impl BoxPainting {
let (rect, _response) = ui.allocate_at_least(self.size, Sense::hover());
ui.painter().rect(
rect,
self.corner_radius,
self.rounding,
Color32::from_gray(64),
Stroke::new(self.stroke_width, Color32::WHITE),
);
@@ -417,7 +418,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
"This is a demonstration of ",
first_row_indentation,
TextFormat {
style: TextStyle::Body,
color: default_color,
..Default::default()
},
@@ -426,7 +426,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
"the egui text layout engine. ",
0.0,
TextFormat {
style: TextStyle::Body,
color: strong_color,
..Default::default()
},
@@ -435,7 +434,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
"It supports ",
0.0,
TextFormat {
style: TextStyle::Body,
color: default_color,
..Default::default()
},
@@ -444,7 +442,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
"different ",
0.0,
TextFormat {
style: TextStyle::Body,
color: Color32::from_rgb(110, 255, 110),
..Default::default()
},
@@ -453,7 +450,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
"colors, ",
0.0,
TextFormat {
style: TextStyle::Body,
color: Color32::from_rgb(128, 140, 255),
..Default::default()
},
@@ -462,7 +458,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
"backgrounds, ",
0.0,
TextFormat {
style: TextStyle::Body,
color: default_color,
background: Color32::from_rgb(128, 32, 32),
..Default::default()
@@ -472,7 +467,7 @@ fn text_layout_ui(ui: &mut egui::Ui) {
"mixing ",
0.0,
TextFormat {
style: TextStyle::Heading,
font_id: FontId::proportional(20.0),
color: default_color,
..Default::default()
},
@@ -481,7 +476,7 @@ fn text_layout_ui(ui: &mut egui::Ui) {
"fonts, ",
0.0,
TextFormat {
style: TextStyle::Monospace,
font_id: FontId::monospace(14.0),
color: default_color,
..Default::default()
},
@@ -490,7 +485,7 @@ fn text_layout_ui(ui: &mut egui::Ui) {
"raised text, ",
0.0,
TextFormat {
style: TextStyle::Small,
font_id: FontId::proportional(8.0),
color: default_color,
valign: Align::TOP,
..Default::default()
@@ -500,7 +495,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
"with ",
0.0,
TextFormat {
style: TextStyle::Body,
color: default_color,
..Default::default()
},
@@ -509,7 +503,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
"underlining",
0.0,
TextFormat {
style: TextStyle::Body,
color: default_color,
underline: Stroke::new(1.0, Color32::LIGHT_BLUE),
..Default::default()
@@ -519,7 +512,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
" and ",
0.0,
TextFormat {
style: TextStyle::Body,
color: default_color,
..Default::default()
},
@@ -528,7 +520,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
"strikethrough",
0.0,
TextFormat {
style: TextStyle::Body,
color: default_color,
strikethrough: Stroke::new(2.0, Color32::RED.linear_multiply(0.5)),
..Default::default()
@@ -538,7 +529,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
". Of course, ",
0.0,
TextFormat {
style: TextStyle::Body,
color: default_color,
..Default::default()
},
@@ -547,7 +537,6 @@ fn text_layout_ui(ui: &mut egui::Ui) {
"you can",
0.0,
TextFormat {
style: TextStyle::Body,
color: default_color,
strikethrough: Stroke::new(1.0, strong_color),
..Default::default()
@@ -557,7 +546,7 @@ fn text_layout_ui(ui: &mut egui::Ui) {
" mix these!",
0.0,
TextFormat {
style: TextStyle::Small,
font_id: FontId::proportional(8.0),
color: Color32::LIGHT_BLUE,
background: Color32::from_rgb(128, 0, 0),
underline: Stroke::new(1.0, strong_color),

View File

@@ -16,6 +16,7 @@ pub mod grid_demo;
pub mod layout_test;
pub mod misc_demo_window;
pub mod multi_touch;
pub mod paint_bezier;
pub mod painting;
pub mod password;
pub mod plot_demo;
@@ -47,5 +48,5 @@ pub trait Demo {
fn name(&self) -> &'static str;
/// Show windows, etc
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool);
fn show(&mut self, ctx: &egui::Context, open: &mut bool);
}

View File

@@ -26,7 +26,7 @@ impl super::Demo for MultiTouch {
"👌 Multi Touch"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.open(open)
.default_size(vec2(512.0, 512.0))
@@ -77,7 +77,7 @@ impl super::View for MultiTouch {
// color and width:
let mut stroke_width = 1.;
let color = Color32::GRAY;
if let Some(multi_touch) = ui.input().multi_touch() {
if let Some(multi_touch) = ui.ctx().multi_touch() {
// This adjusts the current zoom factor and rotation angle according to the dynamic
// change (for the current frame) of the touch gesture:
self.zoom *= multi_touch.zoom_delta;

View File

@@ -0,0 +1,254 @@
use egui::emath::RectTransform;
use egui::epaint::{CircleShape, CubicBezierShape, QuadraticBezierShape};
use egui::*;
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct PaintBezier {
/// Current bezier curve degree, it can be 3, 4.
bezier: usize,
/// Track the bezier degree before change in order to clean the remaining points.
degree_backup: usize,
/// Points already clicked. once it reaches the 'bezier' degree, it will be pushed into the 'shapes'
points: Vec<Pos2>,
/// Track last points set in order to draw auxiliary lines.
backup_points: Vec<Pos2>,
/// Quadratic shapes already drawn.
q_shapes: Vec<QuadraticBezierShape>,
/// Cubic shapes already drawn.
/// Since `Shape` can't be 'serialized', we can't use Shape as variable type.
c_shapes: Vec<CubicBezierShape>,
/// Stroke for auxiliary lines.
aux_stroke: Stroke,
/// Stroke for bezier curve.
stroke: Stroke,
/// Fill for bezier curve.
fill: Color32,
/// The curve should be closed or not.
closed: bool,
/// Display the bounding box or not.
show_bounding_box: bool,
/// Storke for the bounding box.
bounding_box_stroke: Stroke,
}
impl Default for PaintBezier {
fn default() -> Self {
Self {
bezier: 4, // default bezier degree, a cubic bezier curve
degree_backup: 4,
points: Default::default(),
backup_points: Default::default(),
q_shapes: Default::default(),
c_shapes: Default::default(),
aux_stroke: Stroke::new(1.0, Color32::RED),
stroke: Stroke::new(1.0, Color32::LIGHT_BLUE),
fill: Default::default(),
closed: false,
show_bounding_box: false,
bounding_box_stroke: Stroke::new(1.0, Color32::LIGHT_GREEN),
}
}
}
impl PaintBezier {
pub fn ui_control(&mut self, ui: &mut egui::Ui) -> egui::Response {
ui.horizontal(|ui| {
ui.vertical(|ui| {
egui::stroke_ui(ui, &mut self.stroke, "Curve Stroke");
egui::stroke_ui(ui, &mut self.aux_stroke, "Auxiliary Stroke");
ui.horizontal(|ui| {
ui.label("Fill Color:");
if ui.color_edit_button_srgba(&mut self.fill).changed()
&& self.fill != Color32::TRANSPARENT
{
self.closed = true;
}
if ui.checkbox(&mut self.closed, "Closed").clicked() && !self.closed {
self.fill = Color32::TRANSPARENT;
}
});
egui::stroke_ui(ui, &mut self.bounding_box_stroke, "Bounding Box Stroke");
});
ui.separator();
ui.vertical(|ui| {
{
let mut tessellation_options = *(ui.ctx().tessellation_options());
let tessellation_options = &mut tessellation_options;
tessellation_options.ui(ui);
let mut new_tessellation_options = ui.ctx().tessellation_options();
*new_tessellation_options = *tessellation_options;
}
ui.checkbox(&mut self.show_bounding_box, "Bounding Box");
});
ui.separator();
ui.vertical(|ui| {
if ui.radio_value(&mut self.bezier, 3, "Quadratic").clicked()
&& self.degree_backup != self.bezier
{
self.points.clear();
self.degree_backup = self.bezier;
};
if ui.radio_value(&mut self.bezier, 4, "Cubic").clicked()
&& self.degree_backup != self.bezier
{
self.points.clear();
self.degree_backup = self.bezier;
};
// ui.radio_value(self.bezier, 5, "Quintic");
ui.label("Click 3 or 4 points to build a bezier curve!");
if ui.button("Clear Painting").clicked() {
self.points.clear();
self.backup_points.clear();
self.q_shapes.clear();
self.c_shapes.clear();
}
})
})
.response
}
pub fn ui_content(&mut self, ui: &mut Ui) -> egui::Response {
let (mut response, painter) =
ui.allocate_painter(ui.available_size_before_wrap(), Sense::click());
let to_screen = emath::RectTransform::from_to(
Rect::from_min_size(Pos2::ZERO, response.rect.square_proportions()),
response.rect,
);
let from_screen = to_screen.inverse();
if response.clicked() {
if let Some(pointer_pos) = response.interact_pointer_pos() {
let canvas_pos = from_screen * pointer_pos;
self.points.push(canvas_pos);
if self.points.len() >= self.bezier {
self.backup_points = self.points.clone();
let points = self.points.drain(..).collect::<Vec<_>>();
match points.len() {
3 => {
let quadratic = QuadraticBezierShape::from_points_stroke(
points,
self.closed,
self.fill,
self.stroke,
);
self.q_shapes.push(quadratic);
}
4 => {
let cubic = CubicBezierShape::from_points_stroke(
points,
self.closed,
self.fill,
self.stroke,
);
self.c_shapes.push(cubic);
}
_ => {
unreachable!();
}
}
}
response.mark_changed();
}
}
let mut shapes = Vec::new();
for shape in self.q_shapes.iter() {
shapes.push(shape.to_screen(&to_screen).into());
if self.show_bounding_box {
shapes.push(self.build_bounding_box(shape.bounding_rect(), &to_screen));
}
}
for shape in self.c_shapes.iter() {
shapes.push(shape.to_screen(&to_screen).into());
if self.show_bounding_box {
shapes.push(self.build_bounding_box(shape.bounding_rect(), &to_screen));
}
}
painter.extend(shapes);
if !self.points.is_empty() {
painter.extend(build_auxiliary_line(
&self.points,
&to_screen,
&self.aux_stroke,
));
} else if !self.backup_points.is_empty() {
painter.extend(build_auxiliary_line(
&self.backup_points,
&to_screen,
&self.aux_stroke,
));
}
response
}
pub fn build_bounding_box(&self, bbox: Rect, to_screen: &RectTransform) -> Shape {
let bbox = Rect {
min: to_screen * bbox.min,
max: to_screen * bbox.max,
};
let bbox_shape = epaint::RectShape::stroke(bbox, 0.0, self.bounding_box_stroke);
bbox_shape.into()
}
}
/// An internal function to create auxiliary lines around the current bezier curve
/// or to auxiliary lines (points) before the points meet the bezier curve requirements.
fn build_auxiliary_line(
points: &[Pos2],
to_screen: &RectTransform,
aux_stroke: &Stroke,
) -> Vec<Shape> {
let mut shapes = Vec::new();
if points.len() >= 2 {
let points: Vec<Pos2> = points.iter().map(|p| to_screen * *p).collect();
shapes.push(egui::Shape::line(points, *aux_stroke));
}
for point in points.iter() {
let center = to_screen * *point;
let radius = aux_stroke.width * 3.0;
let circle = CircleShape {
center,
radius,
fill: aux_stroke.color,
stroke: *aux_stroke,
};
shapes.push(circle.into());
}
shapes
}
impl super::Demo for PaintBezier {
fn name(&self) -> &'static str {
"✔ Bezier Curve"
}
fn show(&mut self, ctx: &Context, open: &mut bool) {
use super::View as _;
Window::new(self.name())
.open(open)
.default_size(vec2(512.0, 512.0))
.vscroll(false)
.show(ctx, |ui| self.ui(ui));
}
}
impl super::View for PaintBezier {
fn ui(&mut self, ui: &mut Ui) {
// ui.vertical_centered(|ui| {
// ui.add(crate::__egui_github_link_file!());
// });
self.ui_control(ui);
Frame::dark_canvas(ui.style()).show(ui, |ui| {
self.ui_content(ui);
});
}
}

View File

@@ -74,7 +74,7 @@ impl super::Demo for Painting {
"🖊 Painting"
}
fn show(&mut self, ctx: &CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &Context, open: &mut bool) {
use super::View as _;
Window::new(self.name())
.open(open)

View File

@@ -19,7 +19,7 @@ pub fn password_ui(ui: &mut egui::Ui, password: &mut String) -> egui::Response {
// Get state for this widget.
// You should get state by value, not by reference to avoid borrowing of `Memory`.
let mut show_plaintext = ui.memory().data.get_temp::<bool>(state_id).unwrap_or(false);
let mut show_plaintext = ui.data().get_temp::<bool>(state_id).unwrap_or(false);
// Process ui, change a local copy of the state
// We want TextEdit to fill entire space, and have button after that, so in that case we can
@@ -42,7 +42,7 @@ pub fn password_ui(ui: &mut egui::Ui, password: &mut String) -> egui::Response {
});
// Store the (possibly changed) state:
ui.memory().data.insert_temp(state_id, show_plaintext);
ui.data().insert_temp(state_id, show_plaintext);
// All done! Return the interaction response so the user can check what happened
// (hovered, clicked, …) and maybe show a tooltip:

View File

@@ -71,8 +71,8 @@ impl LineDemo {
ui.vertical(|ui| {
ui.style_mut().wrap = Some(false);
ui.checkbox(animate, "animate");
ui.checkbox(square, "square view")
ui.checkbox(animate, "Animate");
ui.checkbox(square, "Square view")
.on_hover_text("Always keep the viewport square.");
ui.checkbox(proportional, "Proportional data axes")
.on_hover_text("Tick are the same size on both axes.");
@@ -237,19 +237,11 @@ impl Widget for &mut MarkerDemo {
}
}
#[derive(PartialEq)]
#[derive(Default, PartialEq)]
struct LegendDemo {
config: Legend,
}
impl Default for LegendDemo {
fn default() -> Self {
Self {
config: Legend::default(),
}
}
}
impl LegendDemo {
fn line_with_slope(slope: f64) -> Line {
Line::new(Values::from_explicit_callback(move |x| slope * x, .., 100))
@@ -269,9 +261,10 @@ impl Widget for &mut LegendDemo {
egui::Grid::new("settings").show(ui, |ui| {
ui.label("Text style:");
ui.horizontal(|ui| {
TextStyle::all().for_each(|style| {
ui.selectable_value(&mut config.text_style, style, format!("{:?}", style));
});
let all_text_styles = ui.style().text_styles();
for style in all_text_styles {
ui.selectable_value(&mut config.text_style, style.clone(), style.to_string());
}
});
ui.end_row();
@@ -292,7 +285,9 @@ impl Widget for &mut LegendDemo {
ui.end_row();
});
let legend_plot = Plot::new("legend_demo").legend(*config).data_aspect(1.0);
let legend_plot = Plot::new("legend_demo")
.legend(config.clone())
.data_aspect(1.0);
legend_plot
.show(ui, |plot_ui| {
plot_ui.line(LegendDemo::line_with_slope(0.5).name("lines"));
@@ -305,10 +300,82 @@ impl Widget for &mut LegendDemo {
}
}
#[derive(PartialEq, Default)]
struct ItemsDemo {}
#[derive(PartialEq)]
struct LinkedAxisDemo {
link_x: bool,
link_y: bool,
group: plot::LinkedAxisGroup,
}
impl ItemsDemo {}
impl Default for LinkedAxisDemo {
fn default() -> Self {
let link_x = true;
let link_y = false;
Self {
link_x,
link_y,
group: plot::LinkedAxisGroup::new(link_x, link_y),
}
}
}
impl LinkedAxisDemo {
fn line_with_slope(slope: f64) -> Line {
Line::new(Values::from_explicit_callback(move |x| slope * x, .., 100))
}
fn sin() -> Line {
Line::new(Values::from_explicit_callback(move |x| x.sin(), .., 100))
}
fn cos() -> Line {
Line::new(Values::from_explicit_callback(move |x| x.cos(), .., 100))
}
fn configure_plot(plot_ui: &mut plot::PlotUi) {
plot_ui.line(LinkedAxisDemo::line_with_slope(0.5));
plot_ui.line(LinkedAxisDemo::line_with_slope(1.0));
plot_ui.line(LinkedAxisDemo::line_with_slope(2.0));
plot_ui.line(LinkedAxisDemo::sin());
plot_ui.line(LinkedAxisDemo::cos());
}
}
impl Widget for &mut LinkedAxisDemo {
fn ui(self, ui: &mut Ui) -> Response {
ui.horizontal(|ui| {
ui.label("Linked axes:");
ui.checkbox(&mut self.link_x, "X");
ui.checkbox(&mut self.link_y, "Y");
});
self.group.set_link_x(self.link_x);
self.group.set_link_y(self.link_y);
ui.horizontal(|ui| {
Plot::new("linked_axis_1")
.data_aspect(1.0)
.width(250.0)
.height(250.0)
.link_axis(self.group.clone())
.show(ui, LinkedAxisDemo::configure_plot);
Plot::new("linked_axis_2")
.data_aspect(2.0)
.width(150.0)
.height(250.0)
.link_axis(self.group.clone())
.show(ui, LinkedAxisDemo::configure_plot);
});
Plot::new("linked_axis_3")
.data_aspect(0.5)
.width(250.0)
.height(150.0)
.link_axis(self.group.clone())
.show(ui, LinkedAxisDemo::configure_plot)
.response
}
}
#[derive(PartialEq, Default)]
struct ItemsDemo {
texture: Option<egui::TextureHandle>,
}
impl Widget for &mut ItemsDemo {
fn ui(self, ui: &mut Ui) -> Response {
@@ -343,13 +410,15 @@ impl Widget for &mut ItemsDemo {
);
Arrows::new(arrow_origins, arrow_tips)
};
let texture: &egui::TextureHandle = self.texture.get_or_insert_with(|| {
ui.ctx()
.load_texture("plot_demo", egui::ColorImage::example())
});
let image = PlotImage::new(
TextureId::Egui,
texture,
Value::new(0.0, 10.0),
[
ui.fonts().font_image().width as f32 / 100.0,
ui.fonts().font_image().height as f32 / 100.0,
],
5.0 * vec2(texture.aspect_ratio(), 1.0),
);
let plot = Plot::new("items_demo")
@@ -376,15 +445,9 @@ impl Widget for &mut ItemsDemo {
}
}
#[derive(PartialEq)]
#[derive(Default, PartialEq)]
struct InteractionDemo {}
impl Default for InteractionDemo {
fn default() -> Self {
Self {}
}
}
impl Widget for &mut InteractionDemo {
fn ui(self, ui: &mut Ui) -> Response {
let plot = Plot::new("interaction_demo").height(300.0);
@@ -532,15 +595,40 @@ impl ChartsDemo {
.name("Set 4")
.stack_on(&[&chart1, &chart2, &chart3]);
let mut x_fmt: fn(f64) -> String = |val| {
if val >= 0.0 && val <= 4.0 && is_approx_integer(val) {
// Only label full days from 0 to 4
format!("Day {}", val)
} else {
// Otherwise return empty string (i.e. no label)
String::new()
}
};
let mut y_fmt: fn(f64) -> String = |val| {
let percent = 100.0 * val;
if is_approx_integer(percent) && !is_approx_zero(percent) {
// Only show integer percentages,
// and don't show at Y=0 (label overlaps with X axis label)
format!("{}%", percent)
} else {
String::new()
}
};
if !self.vertical {
chart1 = chart1.horizontal();
chart2 = chart2.horizontal();
chart3 = chart3.horizontal();
chart4 = chart4.horizontal();
std::mem::swap(&mut x_fmt, &mut y_fmt);
}
Plot::new("Stacked Bar Chart Demo")
.legend(Legend::default())
.x_axis_formatter(x_fmt)
.y_axis_formatter(y_fmt)
.data_aspect(1.0)
.show(ui, |plot_ui| {
plot_ui.bar_chart(chart1);
@@ -623,11 +711,12 @@ enum Panel {
Charts,
Items,
Interaction,
LinkedAxes,
}
impl Default for Panel {
fn default() -> Self {
Self::Charts
Self::Lines
}
}
@@ -639,6 +728,7 @@ pub struct PlotDemo {
charts_demo: ChartsDemo,
items_demo: ItemsDemo,
interaction_demo: InteractionDemo,
linked_axes_demo: LinkedAxisDemo,
open_panel: Panel,
}
@@ -647,7 +737,7 @@ impl super::Demo for PlotDemo {
"🗠 Plot"
}
fn show(&mut self, ctx: &CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &Context, open: &mut bool) {
use super::View as _;
Window::new(self.name())
.open(open)
@@ -663,6 +753,7 @@ impl super::View for PlotDemo {
egui::reset_button(ui, self);
ui.collapsing("Instructions", |ui| {
ui.label("Pan by dragging, or scroll (+ shift = horizontal).");
ui.label("Box zooming: Right click to zoom in and zoom out using a selection.");
if cfg!(target_arch = "wasm32") {
ui.label("Zoom with ctrl / ⌘ + pointer wheel, or with pinch gesture.");
} else if cfg!(target_os = "macos") {
@@ -682,6 +773,7 @@ impl super::View for PlotDemo {
ui.selectable_value(&mut self.open_panel, Panel::Charts, "Charts");
ui.selectable_value(&mut self.open_panel, Panel::Items, "Items");
ui.selectable_value(&mut self.open_panel, Panel::Interaction, "Interaction");
ui.selectable_value(&mut self.open_panel, Panel::LinkedAxes, "Linked Axes");
});
ui.separator();
@@ -704,6 +796,17 @@ impl super::View for PlotDemo {
Panel::Interaction => {
ui.add(&mut self.interaction_demo);
}
Panel::LinkedAxes => {
ui.add(&mut self.linked_axes_demo);
}
}
}
}
fn is_approx_zero(val: f64) -> bool {
val.abs() < 1e-6
}
fn is_approx_integer(val: f64) -> bool {
val.fract().abs() < 1e-6
}

View File

@@ -29,7 +29,7 @@ impl super::Demo for Scrolling {
"↕ Scrolling"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.open(open)
.resizable(false)
@@ -81,7 +81,7 @@ fn huge_content_lines(ui: &mut egui::Ui) {
ui.add_space(4.0);
let text_style = TextStyle::Body;
let row_height = ui.fonts()[text_style].row_height();
let row_height = ui.text_style_height(&text_style);
let num_rows = 10_000;
ScrollArea::vertical().auto_shrink([false; 2]).show_rows(
ui,
@@ -101,8 +101,8 @@ fn huge_content_painter(ui: &mut egui::Ui) {
ui.label("A lot of rows, but only the visible ones are painted, so performance is still good:");
ui.add_space(4.0);
let text_style = TextStyle::Body;
let row_height = ui.fonts()[text_style].row_height() + ui.spacing().item_spacing.y;
let font_id = TextStyle::Body.resolve(ui.style());
let row_height = ui.fonts().row_height(&font_id) + ui.spacing().item_spacing.y;
let num_rows = 10_000;
ScrollArea::vertical()
@@ -130,7 +130,7 @@ fn huge_content_painter(ui: &mut egui::Ui) {
pos2(x, y),
Align2::LEFT_TOP,
text,
text_style,
font_id.clone(),
ui.visuals().text_color(),
);
used_rect = used_rect.union(text_rect);
@@ -210,32 +210,34 @@ impl super::View for ScrollTo {
}
ui.separator();
let (current_scroll, max_scroll) = scroll_area.show(ui, |ui| {
if scroll_top {
ui.scroll_to_cursor(Align::TOP);
}
ui.vertical(|ui| {
for item in 1..=50 {
if track_item && item == self.track_item {
let response =
ui.colored_label(Color32::YELLOW, format!("This is item {}", item));
response.scroll_to_me(self.tack_item_align);
} else {
ui.label(format!("This is item {}", item));
}
let (current_scroll, max_scroll) = scroll_area
.show(ui, |ui| {
if scroll_top {
ui.scroll_to_cursor(Align::TOP);
}
});
ui.vertical(|ui| {
for item in 1..=50 {
if track_item && item == self.track_item {
let response =
ui.colored_label(Color32::YELLOW, format!("This is item {}", item));
response.scroll_to_me(self.tack_item_align);
} else {
ui.label(format!("This is item {}", item));
}
}
});
if scroll_bottom {
ui.scroll_to_cursor(Align::BOTTOM);
}
if scroll_bottom {
ui.scroll_to_cursor(Align::BOTTOM);
}
let margin = ui.visuals().clip_rect_margin;
let margin = ui.visuals().clip_rect_margin;
let current_scroll = ui.clip_rect().top() - ui.min_rect().top() + margin;
let max_scroll = ui.min_rect().height() - ui.clip_rect().height() + 2.0 * margin;
(current_scroll, max_scroll)
});
let current_scroll = ui.clip_rect().top() - ui.min_rect().top() + margin;
let max_scroll = ui.min_rect().height() - ui.clip_rect().height() + 2.0 * margin;
(current_scroll, max_scroll)
})
.inner;
ui.separator();
ui.label(format!(
@@ -253,17 +255,11 @@ impl super::View for ScrollTo {
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
#[derive(PartialEq)]
#[derive(Default, PartialEq)]
struct ScrollStickTo {
n_items: usize,
}
impl Default for ScrollStickTo {
fn default() -> Self {
Self { n_items: 0 }
}
}
impl super::View for ScrollStickTo {
fn ui(&mut self, ui: &mut Ui) {
ui.label("Rows enter from the bottom, we want the scroll handle to start and stay at bottom unless moved");
@@ -271,7 +267,7 @@ impl super::View for ScrollStickTo {
ui.add_space(4.0);
let text_style = TextStyle::Body;
let row_height = ui.fonts()[text_style].row_height();
let row_height = ui.text_style_height(&text_style);
ScrollArea::vertical().stick_to_bottom().show_rows(
ui,
row_height,

View File

@@ -36,7 +36,7 @@ impl super::Demo for Sliders {
"⬌ Sliders"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.open(open)
.resizable(false)

View File

@@ -13,7 +13,7 @@ impl super::Demo for TableDemo {
"☰ Table Demo"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.open(open)
.resizable(true)

View File

@@ -6,7 +6,7 @@ impl super::Demo for CursorTest {
"Cursor Test"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name()).open(open).show(ctx, |ui| {
use super::View as _;
self.ui(ui);
@@ -38,7 +38,7 @@ impl super::Demo for IdTest {
"ID Test"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name()).open(open).show(ctx, |ui| {
use super::View as _;
self.ui(ui);
@@ -52,7 +52,7 @@ impl super::View for IdTest {
ui.label("\
Widgets that store state require unique and persisting identifiers so we can track their state between frames.\n\
For instance, collapsable headers needs to store wether or not they are open. \
For instance, collapsable headers needs to store whether or not they are open. \
Their Id:s are derived from their names. \
If you fail to give them unique names then clicking one will open both. \
To help you debug this, an error message is printed on screen:");
@@ -115,7 +115,7 @@ impl super::Demo for ManualLayoutTest {
"Manual Layout Test"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.resizable(false)
.open(open)
@@ -202,7 +202,7 @@ impl super::Demo for TableTest {
"Table Test"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name()).open(open).show(ctx, |ui| {
use super::View as _;
self.ui(ui);
@@ -314,7 +314,7 @@ impl super::Demo for InputTest {
"Input Test"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.open(open)
.resizable(false)
@@ -383,7 +383,7 @@ impl super::Demo for WindowResizeTest {
"↔ Window Resize"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
use egui::*;
Window::new("↔ auto-sized")

View File

@@ -19,7 +19,7 @@ impl super::Demo for TextEdit {
"🖹 TextEdit"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.open(open)
.resizable(false)

View File

@@ -23,6 +23,8 @@ pub struct WidgetGallery {
#[cfg(feature = "datetime")]
#[serde(with = "serde_date_format")]
date: chrono::Date<chrono::Utc>,
#[cfg_attr(feature = "serde", serde(skip))]
texture: Option<egui::TextureHandle>,
}
impl Default for WidgetGallery {
@@ -38,6 +40,7 @@ impl Default for WidgetGallery {
animate_progress_bar: false,
#[cfg(feature = "datetime")]
date: chrono::offset::Utc::now().date(),
texture: None,
}
}
}
@@ -47,7 +50,7 @@ impl super::Demo for WidgetGallery {
"🗄 Widget Gallery"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.open(open)
.resizable(true)
@@ -108,8 +111,14 @@ impl WidgetGallery {
color,
animate_progress_bar,
date,
texture,
} = self;
let texture: &egui::TextureHandle = texture.get_or_insert_with(|| {
ui.ctx()
.load_texture("example", egui::ColorImage::example())
});
ui.add(doc_link_label("Label", "label,heading"));
ui.label("Welcome to the widget gallery!");
ui.end_row();
@@ -189,17 +198,14 @@ impl WidgetGallery {
ui.color_edit_button_srgba(color);
ui.end_row();
let img_size = 16.0 * texture.size_vec2() / texture.size_vec2().y;
ui.add(doc_link_label("Image", "Image"));
ui.image(egui::TextureId::Egui, [24.0, 16.0])
.on_hover_text("The egui font texture was the convenient choice to show here.");
ui.image(texture, img_size);
ui.end_row();
ui.add(doc_link_label("ImageButton", "ImageButton"));
if ui
.add(egui::ImageButton::new(egui::TextureId::Egui, [24.0, 16.0]))
.on_hover_text("The egui font texture was the convenient choice to show here.")
.clicked()
{
if ui.add(egui::ImageButton::new(texture, img_size)).clicked() {
*boolean = !*boolean;
}
ui.end_row();
@@ -218,10 +224,11 @@ impl WidgetGallery {
ui.add(doc_link_label("CollapsingHeader", "collapsing"));
ui.collapsing("Click to see what is hidden!", |ui| {
ui.horizontal_wrapped(|ui| {
ui.label(
"Not much, as it turns out - but here is a gold star for you for checking:",
);
ui.colored_label(egui::Color32::GOLD, "");
ui.spacing_mut().item_spacing.x = 0.0;
ui.label("It's a ");
ui.add(doc_link_label("Spinner", "spinner"));
ui.add_space(4.0);
ui.add(egui::Spinner::new());
});
});
ui.end_row();
@@ -239,10 +246,6 @@ impl WidgetGallery {
This toggle switch is just 15 lines of code.",
);
ui.end_row();
ui.add(doc_link_label("Spinner", "spinner"));
ui.add(egui::Spinner::new());
ui.end_row();
}
}

View File

@@ -36,7 +36,7 @@ impl super::Demo for WindowOptions {
"🗖 Window Options"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
let Self {
title,
title_bar,

View File

@@ -7,7 +7,7 @@ impl super::Demo for WindowWithPanels {
"🗖 Window With Panels"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
use super::View as _;
let window = egui::Window::new("Window with Panels")
.default_width(600.0)

View File

@@ -37,7 +37,7 @@ impl epi::App for FractalClock {
"🕑 Fractal Clock"
}
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
egui::CentralPanel::default()
.frame(Frame::dark_canvas(&ctx.style()))
.show(ctx, |ui| self.ui(ui, crate::seconds_since_midnight()));
@@ -97,6 +97,7 @@ impl FractalClock {
"Inspired by a screensaver by Rob Mayoff",
"http://www.dqd.com/~mayoff/programs/FractalClock/",
);
ui.add(crate::__egui_github_link_file!());
}
fn paint(&mut self, painter: &Painter) {

View File

@@ -1,4 +1,4 @@
use std::sync::mpsc::Receiver;
use poll_promise::Promise;
struct Resource {
/// HTTP response
@@ -7,7 +7,7 @@ struct Resource {
text: Option<String>,
/// If set, the response was an image.
image: Option<epi::Image>,
texture: Option<egui::TextureHandle>,
/// If set, the response was text with some supported syntax highlighting (e.g. ".rs" or ".md").
colored_text: Option<ColoredText>,
@@ -17,21 +17,21 @@ 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/") {
decode_image(&response.bytes)
load_image(&response.bytes).ok()
} else {
None
};
let text = response.text();
let texture = image.map(|image| ctx.load_texture(&response.url, image));
let colored_text = text
.as_ref()
.and_then(|text| syntax_highlighting(ctx, &response, text));
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,
texture,
colored_text,
}
}
@@ -42,22 +42,14 @@ pub struct HttpApp {
url: String,
#[cfg_attr(feature = "serde", serde(skip))]
in_progress: Option<Receiver<Result<ehttp::Response, String>>>,
#[cfg_attr(feature = "serde", serde(skip))]
result: Option<Result<Resource, String>>,
#[cfg_attr(feature = "serde", serde(skip))]
tex_mngr: TexMngr,
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(),
in_progress: Default::default(),
result: Default::default(),
tex_mngr: Default::default(),
promise: Default::default(),
}
}
}
@@ -67,15 +59,7 @@ impl epi::App for HttpApp {
"⬇ HTTP"
}
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
if let Some(receiver) = &mut self.in_progress {
// Are we there yet?
if let Ok(result) = receiver.try_recv() {
self.in_progress = None;
self.result = Some(result.map(|response| Resource::from_response(ctx, response)));
}
}
fn update(&mut self, ctx: &egui::Context, frame: &epi::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| {
@@ -94,33 +78,36 @@ impl epi::App for HttpApp {
});
if trigger_fetch {
let request = ehttp::Request::get(&self.url);
let ctx = ctx.clone();
let frame = frame.clone();
let (sender, receiver) = std::sync::mpsc::channel();
self.in_progress = Some(receiver);
let (sender, promise) = Promise::new();
let request = ehttp::Request::get(&self.url);
ehttp::fetch(request, move |response| {
sender.send(response).ok();
frame.request_repaint();
frame.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 self.in_progress.is_some() {
ui.label("Please wait…");
} else if let Some(result) = &self.result {
match result {
Ok(resource) => {
ui_resource(ui, frame, &mut self.tex_mngr, resource);
}
Err(error) => {
// This should only happen if the fetch API isn't available or something similar.
ui.colored_label(
egui::Color32::RED,
if error.is_empty() { "Error" } else { error },
);
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(
egui::Color32::RED,
if error.is_empty() { "Error" } else { error },
);
}
}
} else {
ui.add(egui::Spinner::new());
}
}
});
@@ -160,11 +147,11 @@ fn ui_url(ui: &mut egui::Ui, frame: &epi::Frame, url: &mut String) -> bool {
trigger_fetch
}
fn ui_resource(ui: &mut egui::Ui, frame: &epi::Frame, tex_mngr: &mut TexMngr, resource: &Resource) {
fn ui_resource(ui: &mut egui::Ui, resource: &Resource) {
let Resource {
response,
text,
image,
texture,
colored_text,
} = resource;
@@ -211,12 +198,10 @@ fn ui_resource(ui: &mut egui::Ui, frame: &epi::Frame, tex_mngr: &mut TexMngr, re
ui.separator();
}
if let Some(image) = image {
if let Some(texture_id) = tex_mngr.texture(frame, &response.url, image) {
let mut size = egui::Vec2::new(image.size[0] as f32, image.size[1] as f32);
size *= (ui.available_width() / size.x).min(1.0);
ui.image(texture_id, size);
}
if let Some(texture) = texture {
let mut size = texture.size_vec2();
size *= (ui.available_width() / size.x).min(1.0);
ui.image(texture, size);
} else if let Some(colored_text) = colored_text {
colored_text.ui(ui);
} else if let Some(text) = &text {
@@ -231,7 +216,7 @@ fn selectable_text(ui: &mut egui::Ui, mut text: &str) {
ui.add(
egui::TextEdit::multiline(&mut text)
.desired_width(f32::INFINITY)
.text_style(egui::TextStyle::Monospace),
.font(egui::TextStyle::Monospace),
);
}
@@ -272,7 +257,7 @@ impl ColoredText {
let mut text = self.0.text.as_str();
ui.add(
egui::TextEdit::multiline(&mut text)
.text_style(egui::TextStyle::Monospace)
.font(egui::TextStyle::Monospace)
.desired_width(f32::INFINITY)
.layouter(&mut layouter),
);
@@ -287,39 +272,14 @@ impl ColoredText {
}
// ----------------------------------------------------------------------------
// Texture/image handling is very manual at the moment.
/// Immediate mode texture manager that supports at most one texture at the time :)
#[derive(Default)]
struct TexMngr {
loaded_url: String,
texture_id: Option<egui::TextureId>,
}
impl TexMngr {
fn texture(
&mut self,
frame: &epi::Frame,
url: &str,
image: &epi::Image,
) -> Option<egui::TextureId> {
if self.loaded_url != url {
if let Some(texture_id) = self.texture_id.take() {
frame.free_texture(texture_id);
}
self.texture_id = Some(frame.alloc_texture(image.clone()));
self.loaded_url = url.to_owned();
}
self.texture_id
}
}
fn decode_image(bytes: &[u8]) -> Option<epi::Image> {
use image::GenericImageView;
let image = image::load_from_memory(bytes).ok()?;
fn load_image(image_data: &[u8]) -> Result<egui::ColorImage, image::ImageError> {
let image = image::load_from_memory(image_data)?;
let size = [image.width() as _, image.height() as _];
let image_buffer = image.to_rgba8();
let size = [image.width() as usize, image.height() as usize];
let pixels = image_buffer.into_vec();
Some(epi::Image::from_rgba_unmultiplied(size, &pixels))
let pixels = image_buffer.as_flat_samples();
Ok(egui::ColorImage::from_rgba_unmultiplied(
size,
pixels.as_slice(),
))
}

View File

@@ -51,6 +51,7 @@ pub struct BackendPanel {
run_mode: RunMode,
/// current slider value for current gui scale
#[cfg_attr(feature = "serde", serde(skip))]
pixels_per_point: Option<f32>,
/// maximum size of the web browser canvas
@@ -78,7 +79,7 @@ impl Default for BackendPanel {
}
impl BackendPanel {
pub fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
pub fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
self.frame_history
.on_new_frame(ctx.input().time, frame.info().cpu_usage);
@@ -88,7 +89,7 @@ impl BackendPanel {
}
}
pub fn end_of_frame(&mut self, ctx: &egui::CtxRef) {
pub fn end_of_frame(&mut self, ctx: &egui::Context) {
self.egui_windows.windows(ctx);
}
@@ -127,9 +128,9 @@ impl BackendPanel {
ui.separator();
{
let mut screen_reader = ui.ctx().memory().options.screen_reader;
let mut screen_reader = ui.ctx().options().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().memory().options.screen_reader = screen_reader;
ui.ctx().options().screen_reader = screen_reader;
}
if !frame.is_web() {
@@ -195,12 +196,10 @@ impl BackendPanel {
ui: &mut egui::Ui,
info: &epi::IntegrationInfo,
) -> Option<f32> {
self.pixels_per_point = self
.pixels_per_point
.or(info.native_pixels_per_point)
.or_else(|| Some(ui.ctx().pixels_per_point()));
let pixels_per_point = self.pixels_per_point.as_mut()?;
let pixels_per_point = self.pixels_per_point.get_or_insert_with(|| {
info.native_pixels_per_point
.unwrap_or_else(|| ui.ctx().pixels_per_point())
});
ui.horizontal(|ui| {
ui.spacing_mut().slider_width = 90.0;
@@ -325,7 +324,7 @@ impl EguiWindows {
ui.checkbox(output_events, "📤 Output Events");
}
fn windows(&mut self, ctx: &egui::CtxRef) {
fn windows(&mut self, ctx: &egui::Context) {
let Self {
settings,
inspection,

View File

@@ -34,7 +34,7 @@ impl epi::App for EasyMarkEditor {
"🖹 EasyMark editor"
}
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
egui::TopBottomPanel::bottom("easy_mark_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| {
@@ -88,7 +88,7 @@ impl EasyMarkEditor {
let response = if self.highlight_editor {
let mut layouter = |ui: &egui::Ui, easymark: &str, wrap_width: f32| {
let mut layout_job = highlighter.highlight(ui.visuals(), easymark);
let mut layout_job = highlighter.highlight(ui.style(), easymark);
layout_job.wrap_width = wrap_width;
ui.fonts().layout_job(layout_job)
};
@@ -96,7 +96,7 @@ impl EasyMarkEditor {
ui.add(
egui::TextEdit::multiline(code)
.desired_width(f32::INFINITY)
.text_style(egui::TextStyle::Monospace) // for cursor height
.font(egui::TextStyle::Monospace) // for cursor height
.layouter(&mut layouter),
)
} else {

View File

@@ -5,23 +5,23 @@ use crate::easy_mark::easy_mark_parser;
/// In practice, the highlighter is fast enough not to need any caching.
#[derive(Default)]
pub struct MemoizedEasymarkHighlighter {
visuals: egui::Visuals,
style: egui::Style,
code: String,
output: egui::text::LayoutJob,
}
impl MemoizedEasymarkHighlighter {
pub fn highlight(&mut self, visuals: &egui::Visuals, code: &str) -> egui::text::LayoutJob {
if (&self.visuals, self.code.as_str()) != (visuals, code) {
self.visuals = visuals.clone();
pub fn highlight(&mut self, egui_style: &egui::Style, code: &str) -> egui::text::LayoutJob {
if (&self.style, self.code.as_str()) != (egui_style, code) {
self.style = egui_style.clone();
self.code = code.to_owned();
self.output = highlight_easymark(visuals, code);
self.output = highlight_easymark(egui_style, code);
}
self.output.clone()
}
}
pub fn highlight_easymark(visuals: &egui::Visuals, mut text: &str) -> egui::text::LayoutJob {
pub fn highlight_easymark(egui_style: &egui::Style, mut text: &str) -> egui::text::LayoutJob {
let mut job = egui::text::LayoutJob::default();
let mut style = easy_mark_parser::Style::default();
let mut start_of_line = true;
@@ -33,7 +33,7 @@ pub fn highlight_easymark(visuals: &egui::Visuals, mut text: &str) -> egui::text
&text[..end],
0.0,
format_from_style(
visuals,
egui_style,
&easy_mark_parser::Style {
code: true,
..Default::default()
@@ -50,7 +50,7 @@ pub fn highlight_easymark(visuals: &egui::Visuals, mut text: &str) -> egui::text
let end = text[1..]
.find(&['`', '\n'][..])
.map_or_else(|| text.len(), |i| i + 2);
job.append(&text[..end], 0.0, format_from_style(visuals, &style));
job.append(&text[..end], 0.0, format_from_style(egui_style, &style));
text = &text[end..];
style.code = false;
continue;
@@ -61,7 +61,7 @@ pub fn highlight_easymark(visuals: &egui::Visuals, mut text: &str) -> egui::text
if text.starts_with('\\') && text.len() >= 2 {
skip = 2;
} else if start_of_line && text.starts_with(' ') {
// indentation we don't preview indentation, because it is confusing
// we don't preview indentation, because it is confusing
skip = 1;
} else if start_of_line && text.starts_with("# ") {
style.heading = true;
@@ -69,15 +69,15 @@ pub fn highlight_easymark(visuals: &egui::Visuals, mut text: &str) -> egui::text
} else if start_of_line && text.starts_with("> ") {
style.quoted = true;
skip = 2;
// indentation we don't preview indentation, because it is confusing
// we don't preview indentation, because it is confusing
} else if start_of_line && text.starts_with("- ") {
skip = 2;
// indentation we don't preview indentation, because it is confusing
// we don't preview indentation, because it is confusing
} else if text.starts_with('*') {
skip = 1;
if style.strong {
// Include the character that i ending ths style:
job.append(&text[..skip], 0.0, format_from_style(visuals, &style));
// Include the character that is ending this style:
job.append(&text[..skip], 0.0, format_from_style(egui_style, &style));
text = &text[skip..];
skip = 0;
}
@@ -85,8 +85,8 @@ pub fn highlight_easymark(visuals: &egui::Visuals, mut text: &str) -> egui::text
} else if text.starts_with('$') {
skip = 1;
if style.small {
// Include the character that i ending ths style:
job.append(&text[..skip], 0.0, format_from_style(visuals, &style));
// Include the character that is ending this style:
job.append(&text[..skip], 0.0, format_from_style(egui_style, &style));
text = &text[skip..];
skip = 0;
}
@@ -94,8 +94,8 @@ pub fn highlight_easymark(visuals: &egui::Visuals, mut text: &str) -> egui::text
} else if text.starts_with('^') {
skip = 1;
if style.raised {
// Include the character that i ending ths style:
job.append(&text[..skip], 0.0, format_from_style(visuals, &style));
// Include the character that is ending this style:
job.append(&text[..skip], 0.0, format_from_style(egui_style, &style));
text = &text[skip..];
skip = 0;
}
@@ -114,12 +114,16 @@ pub fn highlight_easymark(visuals: &egui::Visuals, mut text: &str) -> egui::text
.map_or_else(|| text.len(), |i| (skip + i).max(1));
if line_end <= end {
job.append(&text[..line_end], 0.0, format_from_style(visuals, &style));
job.append(
&text[..line_end],
0.0,
format_from_style(egui_style, &style),
);
text = &text[line_end..];
start_of_line = true;
style = Default::default();
} else {
job.append(&text[..end], 0.0, format_from_style(visuals, &style));
job.append(&text[..end], 0.0, format_from_style(egui_style, &style));
text = &text[end..];
start_of_line = false;
}
@@ -129,17 +133,17 @@ pub fn highlight_easymark(visuals: &egui::Visuals, mut text: &str) -> egui::text
}
fn format_from_style(
visuals: &egui::Visuals,
egui_style: &egui::Style,
emark_style: &easy_mark_parser::Style,
) -> egui::text::TextFormat {
use egui::{Align, Color32, Stroke, TextStyle};
let color = if emark_style.strong || emark_style.heading {
visuals.strong_text_color()
egui_style.visuals.strong_text_color()
} else if emark_style.quoted {
visuals.weak_text_color()
egui_style.visuals.weak_text_color()
} else {
visuals.text_color()
egui_style.visuals.text_color()
};
let text_style = if emark_style.heading {
@@ -153,7 +157,7 @@ fn format_from_style(
};
let background = if emark_style.code {
visuals.code_bg_color
egui_style.visuals.code_bg_color
} else {
Color32::TRANSPARENT
};
@@ -177,7 +181,7 @@ fn format_from_style(
};
egui::text::TextFormat {
style: text_style,
font_id: text_style.resolve(egui_style),
color,
background,
italics: emark_style.italics,

View File

@@ -1,9 +1,6 @@
//! A parser for `EasyMark`: a very simple markup language.
//!
//! WARNING: `EasyMark` is subject to change.
//!
//! This module does not depend on anything else in egui
//! and should perhaps be its own crate.
//
//! # `EasyMark` design goals:
//! 1. easy to parse

View File

@@ -18,7 +18,8 @@ pub fn easy_mark_it<'em>(ui: &mut Ui, items: impl Iterator<Item = easy_mark::Ite
ui.allocate_ui_with_layout(initial_size, layout, |ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.set_row_height(ui.fonts()[TextStyle::Body].row_height());
let row_height = ui.text_style_height(&TextStyle::Body);
ui.set_row_height(row_height);
for item in items {
item_ui(ui, item);
@@ -27,7 +28,7 @@ pub fn easy_mark_it<'em>(ui: &mut Ui, items: impl Iterator<Item = easy_mark::Ite
}
pub fn item_ui(ui: &mut Ui, item: easy_mark::Item<'_>) {
let row_height = ui.fonts()[TextStyle::Body].row_height();
let row_height = ui.text_style_height(&TextStyle::Body);
let one_indent = row_height / 2.0;
match item {
@@ -133,7 +134,7 @@ fn rich_text_from_style(text: &str, style: &easy_mark::Style) -> RichText {
}
fn bullet_point(ui: &mut Ui, width: f32) -> Response {
let row_height = ui.fonts()[TextStyle::Body].row_height();
let row_height = ui.text_style_height(&TextStyle::Body);
let (rect, response) = ui.allocate_exact_size(vec2(width, row_height), Sense::hover());
ui.painter().circle_filled(
rect.center(),
@@ -144,7 +145,8 @@ fn bullet_point(ui: &mut Ui, width: f32) -> Response {
}
fn numbered_point(ui: &mut Ui, width: f32, number: &str) -> Response {
let row_height = ui.fonts()[TextStyle::Body].row_height();
let font_id = TextStyle::Body.resolve(ui.style());
let row_height = ui.fonts().row_height(&font_id);
let (rect, response) = ui.allocate_exact_size(vec2(width, row_height), Sense::hover());
let text = format!("{}.", number);
let text_color = ui.visuals().strong_text_color();
@@ -152,7 +154,7 @@ fn numbered_point(ui: &mut Ui, width: f32, number: &str) -> Response {
rect.right_center(),
Align2::RIGHT_CENTER,
text,
TextStyle::Body,
font_id,
text_color,
);
response

View File

@@ -76,7 +76,7 @@ impl FrameHistory {
let mut shapes = Vec::with_capacity(3 + 2 * history.len());
shapes.push(Shape::Rect(epaint::RectShape {
rect,
corner_radius: style.corner_radius,
rounding: style.rounding,
fill: ui.visuals().extreme_bg_color,
stroke: ui.style().noninteractive().bg_stroke,
}));
@@ -94,11 +94,11 @@ impl FrameHistory {
let cpu_usage = to_screen.inverse().transform_pos(pointer_pos).y;
let text = format!("{:.1} ms", 1e3 * cpu_usage);
shapes.push(Shape::text(
ui.fonts(),
&*ui.fonts(),
pos2(rect.left(), y),
egui::Align2::LEFT_BOTTOM,
text,
TextStyle::Monospace,
TextStyle::Monospace.resolve(ui.style()),
color,
));
}

View File

@@ -140,7 +140,7 @@ Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, tur
#[test]
fn test_egui_e2e() {
let mut demo_windows = crate::DemoWindows::default();
let mut ctx = egui::CtxRef::default();
let ctx = egui::Context::default();
let raw_input = egui::RawInput::default();
const NUM_FRAMES: usize = 5;
@@ -156,7 +156,7 @@ fn test_egui_e2e() {
#[test]
fn test_egui_zero_window_size() {
let mut demo_windows = crate::DemoWindows::default();
let mut ctx = egui::CtxRef::default();
let ctx = egui::Context::default();
let raw_input = egui::RawInput {
screen_rect: Some(egui::Rect::from_min_max(egui::Pos2::ZERO, egui::Pos2::ZERO)),
..Default::default()
@@ -176,7 +176,7 @@ fn test_egui_zero_window_size() {
/// Time of day as seconds since midnight. Used for clock in demo app.
pub(crate) fn seconds_since_midnight() -> Option<f64> {
#[cfg(feature = "chrono")]
#[cfg(feature = "datetime")]
{
use chrono::Timelike;
let time = chrono::Local::now().time();
@@ -184,6 +184,6 @@ pub(crate) fn seconds_since_midnight() -> Option<f64> {
time.num_seconds_from_midnight() as f64 + 1e-9 * (time.nanosecond() as f64);
Some(seconds_since_midnight)
}
#[cfg(not(feature = "chrono"))]
#[cfg(not(feature = "datetime"))]
None
}

View File

@@ -13,7 +13,7 @@ pub fn code_view_ui(ui: &mut egui::Ui, mut code: &str) {
ui.add(
egui::TextEdit::multiline(&mut code)
.text_style(egui::TextStyle::Monospace) // for cursor height
.font(egui::TextStyle::Monospace) // for cursor height
.code_editor()
.desired_rows(1)
.lock_focus(true)
@@ -66,7 +66,7 @@ enum SyntectTheme {
#[cfg(feature = "syntect")]
impl SyntectTheme {
fn all() -> impl Iterator<Item = Self> {
fn all() -> impl ExactSizeIterator<Item = Self> {
[
Self::Base16EightiesDark,
Self::Base16MochaDark,
@@ -116,7 +116,7 @@ impl SyntectTheme {
}
}
#[derive(Clone, Copy, Hash, PartialEq)]
#[derive(Clone, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct CodeTheme {
@@ -146,27 +146,21 @@ impl CodeTheme {
pub fn from_memory(ctx: &egui::Context) -> Self {
if ctx.style().visuals.dark_mode {
ctx.memory()
.data
ctx.data()
.get_persisted(egui::Id::new("dark"))
.unwrap_or_else(CodeTheme::dark)
} else {
ctx.memory()
.data
ctx.data()
.get_persisted(egui::Id::new("light"))
.unwrap_or_else(CodeTheme::light)
}
}
pub fn store_in_memory(&self, ctx: &egui::Context) {
pub fn store_in_memory(self, ctx: &egui::Context) {
if self.dark_mode {
ctx.memory()
.data
.insert_persisted(egui::Id::new("dark"), *self);
ctx.data().insert_persisted(egui::Id::new("dark"), self);
} else {
ctx.memory()
.data
.insert_persisted(egui::Id::new("light"), *self);
ctx.data().insert_persisted(egui::Id::new("light"), self);
}
}
}
@@ -201,34 +195,34 @@ impl CodeTheme {
#[cfg(not(feature = "syntect"))]
impl CodeTheme {
pub fn dark() -> Self {
let text_style = egui::TextStyle::Monospace;
let font_id = egui::FontId::monospace(12.0);
use egui::{Color32, TextFormat};
Self {
dark_mode: true,
formats: enum_map::enum_map![
TokenType::Comment => TextFormat::simple(text_style, Color32::from_gray(120)),
TokenType::Keyword => TextFormat::simple(text_style, Color32::from_rgb(255, 100, 100)),
TokenType::Literal => TextFormat::simple(text_style, Color32::from_rgb(87, 165, 171)),
TokenType::StringLiteral => TextFormat::simple(text_style, Color32::from_rgb(109, 147, 226)),
TokenType::Punctuation => TextFormat::simple(text_style, Color32::LIGHT_GRAY),
TokenType::Whitespace => TextFormat::simple(text_style, Color32::TRANSPARENT),
TokenType::Comment => TextFormat::simple(font_id.clone(), Color32::from_gray(120)),
TokenType::Keyword => TextFormat::simple(font_id.clone(), Color32::from_rgb(255, 100, 100)),
TokenType::Literal => TextFormat::simple(font_id.clone(), Color32::from_rgb(87, 165, 171)),
TokenType::StringLiteral => TextFormat::simple(font_id.clone(), Color32::from_rgb(109, 147, 226)),
TokenType::Punctuation => TextFormat::simple(font_id.clone(), Color32::LIGHT_GRAY),
TokenType::Whitespace => TextFormat::simple(font_id.clone(), Color32::TRANSPARENT),
],
}
}
pub fn light() -> Self {
let text_style = egui::TextStyle::Monospace;
let font_id = egui::FontId::monospace(12.0);
use egui::{Color32, TextFormat};
Self {
dark_mode: false,
#[cfg(not(feature = "syntect"))]
formats: enum_map::enum_map![
TokenType::Comment => TextFormat::simple(text_style, Color32::GRAY),
TokenType::Keyword => TextFormat::simple(text_style, Color32::from_rgb(235, 0, 0)),
TokenType::Literal => TextFormat::simple(text_style, Color32::from_rgb(153, 134, 255)),
TokenType::StringLiteral => TextFormat::simple(text_style, Color32::from_rgb(37, 203, 105)),
TokenType::Punctuation => TextFormat::simple(text_style, Color32::DARK_GRAY),
TokenType::Whitespace => TextFormat::simple(text_style, Color32::TRANSPARENT),
TokenType::Comment => TextFormat::simple(font_id.clone(), Color32::GRAY),
TokenType::Keyword => TextFormat::simple(font_id.clone(), Color32::from_rgb(235, 0, 0)),
TokenType::Literal => TextFormat::simple(font_id.clone(), Color32::from_rgb(153, 134, 255)),
TokenType::StringLiteral => TextFormat::simple(font_id.clone(), Color32::from_rgb(37, 203, 105)),
TokenType::Punctuation => TextFormat::simple(font_id.clone(), Color32::DARK_GRAY),
TokenType::Whitespace => TextFormat::simple(font_id.clone(), Color32::TRANSPARENT),
],
}
}
@@ -237,8 +231,7 @@ impl CodeTheme {
ui.horizontal_top(|ui| {
let selected_id = egui::Id::null();
let mut selected_tt: TokenType = *ui
.memory()
.data
.data()
.get_persisted_mut_or(selected_id, TokenType::Comment);
ui.vertical(|ui| {
@@ -259,7 +252,7 @@ impl CodeTheme {
// (TokenType::Whitespace, "whitespace"),
] {
let format = &mut self.formats[tt];
ui.style_mut().override_text_style = Some(format.style);
ui.style_mut().override_font_id = Some(format.font_id.clone());
ui.visuals_mut().override_text_color = Some(format.color);
ui.radio_value(&mut selected_tt, tt, tt_name);
}
@@ -281,7 +274,7 @@ impl CodeTheme {
ui.add_space(16.0);
ui.memory().data.insert_persisted(selected_id, selected_tt);
ui.data().insert_persisted(selected_id, selected_tt);
egui::Frame::group(ui.style())
.margin(egui::Vec2::splat(2.0))
@@ -325,7 +318,7 @@ impl Highligher {
// Fallback:
LayoutJob::simple(
code.into(),
egui::TextStyle::Monospace,
egui::FontId::monospace(14.0),
if theme.dark_mode {
egui::Color32::LIGHT_GRAY
} else {
@@ -371,7 +364,7 @@ impl Highligher {
leading_space: 0.0,
byte_range: as_byte_range(text, range),
format: TextFormat {
style: egui::TextStyle::Monospace,
font_id: egui::FontId::monospace(14.0),
color: text_color,
italics,
underline,
@@ -412,7 +405,7 @@ impl Highligher {
while !text.is_empty() {
if text.starts_with("//") {
let end = text.find('\n').unwrap_or_else(|| text.len());
job.append(&text[..end], 0.0, theme.formats[TokenType::Comment]);
job.append(&text[..end], 0.0, theme.formats[TokenType::Comment].clone());
text = &text[end..];
} else if text.starts_with('"') {
let end = text[1..]
@@ -420,7 +413,11 @@ impl Highligher {
.map(|i| i + 2)
.or_else(|| text.find('\n'))
.unwrap_or_else(|| text.len());
job.append(&text[..end], 0.0, theme.formats[TokenType::StringLiteral]);
job.append(
&text[..end],
0.0,
theme.formats[TokenType::StringLiteral].clone(),
);
text = &text[end..];
} else if text.starts_with(|c: char| c.is_ascii_alphanumeric()) {
let end = text[1..]
@@ -432,19 +429,27 @@ impl Highligher {
} else {
TokenType::Literal
};
job.append(word, 0.0, theme.formats[tt]);
job.append(word, 0.0, theme.formats[tt].clone());
text = &text[end..];
} else if text.starts_with(|c: char| c.is_ascii_whitespace()) {
let end = text[1..]
.find(|c: char| !c.is_ascii_whitespace())
.map_or_else(|| text.len(), |i| i + 1);
job.append(&text[..end], 0.0, theme.formats[TokenType::Whitespace]);
job.append(
&text[..end],
0.0,
theme.formats[TokenType::Whitespace].clone(),
);
text = &text[end..];
} else {
let mut it = text.char_indices();
it.next();
let end = it.next().map_or(text.len(), |(idx, _chr)| idx);
job.append(&text[..end], 0.0, theme.formats[TokenType::Punctuation]);
job.append(
&text[..end],
0.0,
theme.formats[TokenType::Punctuation].clone(),
);
text = &text[end..];
}
}

View File

@@ -44,7 +44,7 @@ impl epi::App for WrapApp {
fn setup(
&mut self,
_ctx: &egui::CtxRef,
_ctx: &egui::Context,
_frame: &epi::Frame,
_storage: Option<&dyn epi::Storage>,
) {
@@ -67,12 +67,7 @@ impl epi::App for WrapApp {
egui::Rgba::TRANSPARENT // we set a `CentralPanel` fill color in `demo_windows.rs`
}
fn warm_up_enabled(&self) -> bool {
// The example windows use a lot of emojis. Pre-cache them by running one frame where everything is open
cfg!(not(debug_assertions))
}
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
if let Some(web_info) = frame.info().web_info.as_ref() {
if let Some(anchor) = web_info.web_location_hash.strip_prefix('#') {
self.selected_anchor = anchor.to_owned();
@@ -165,7 +160,7 @@ impl WrapApp {
});
}
fn ui_file_drag_and_drop(&mut self, ctx: &egui::CtxRef) {
fn ui_file_drag_and_drop(&mut self, ctx: &egui::Context) {
use egui::*;
// Preview hovering files:
@@ -190,7 +185,7 @@ impl WrapApp {
screen_rect.center(),
Align2::CENTER_CENTER,
text,
TextStyle::Heading,
TextStyle::Heading.resolve(&ctx.style()),
Color32::WHITE,
);
}