1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-30 13:20:05 -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

@@ -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(),
))
}