mirror of
https://github.com/emilk/egui.git
synced 2026-08-31 22:00:03 -04:00
Merge branch 'master' into cache_galley_lines
This commit is contained in:
@@ -10,7 +10,7 @@ license.workspace = true
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/emilk/egui/tree/master/crates/egui_demo_lib"
|
||||
categories = ["gui", "graphics"]
|
||||
keywords = ["glium", "egui", "gui", "gamedev"]
|
||||
keywords = ["glow", "egui", "gui", "gamedev"]
|
||||
include = [
|
||||
"../LICENSE-APACHE",
|
||||
"../LICENSE-MIT",
|
||||
@@ -57,7 +57,6 @@ serde = { workspace = true, optional = true }
|
||||
[dev-dependencies]
|
||||
criterion.workspace = true
|
||||
egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] }
|
||||
wgpu = { workspace = true, features = ["metal"] }
|
||||
egui = { workspace = true, features = ["default_fonts"] }
|
||||
rand = "0.8"
|
||||
|
||||
|
||||
@@ -79,7 +79,13 @@ pub fn criterion_benchmark(c: &mut Criterion) {
|
||||
let painter = ui.painter();
|
||||
let rect = ui.max_rect();
|
||||
b.iter(|| {
|
||||
painter.rect(rect, 2.0, egui::Color32::RED, (1.0, egui::Color32::WHITE));
|
||||
painter.rect(
|
||||
rect,
|
||||
2.0,
|
||||
egui::Color32::RED,
|
||||
(1.0, egui::Color32::WHITE),
|
||||
egui::StrokeKind::Inside,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -98,14 +98,14 @@ fn about_immediate_mode(ui: &mut egui::Ui) {
|
||||
}
|
||||
|
||||
fn links(ui: &mut egui::Ui) {
|
||||
use egui::special_emojis::{GITHUB, TWITTER};
|
||||
use egui::special_emojis::GITHUB;
|
||||
ui.hyperlink_to(
|
||||
format!("{GITHUB} github.com/emilk/egui"),
|
||||
"https://github.com/emilk/egui",
|
||||
);
|
||||
ui.hyperlink_to(
|
||||
format!("{TWITTER} @ernerfeldt"),
|
||||
"https://twitter.com/ernerfeldt",
|
||||
"@ernerfeldt.bsky.social",
|
||||
"https://bsky.app/profile/ernerfeldt.bsky.social",
|
||||
);
|
||||
ui.hyperlink_to("📓 egui documentation", "https://docs.rs/egui/");
|
||||
}
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
#[derive(Clone, Default, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct ContextMenus {}
|
||||
|
||||
impl crate::Demo for ContextMenus {
|
||||
fn name(&self) -> &'static str {
|
||||
"☰ Context Menus"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
use crate::View;
|
||||
egui::Window::new(self.name())
|
||||
.vscroll(false)
|
||||
.resizable(false)
|
||||
.open(open)
|
||||
.show(ctx, |ui| self.ui(ui));
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::View for ContextMenus {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.menu_button("Click for menu", Self::nested_menus);
|
||||
|
||||
ui.button("Right-click for menu")
|
||||
.context_menu(Self::nested_menus);
|
||||
|
||||
if ui.ctx().is_context_menu_open() {
|
||||
ui.label("Context menu is open");
|
||||
} else {
|
||||
ui.label("Context menu is closed");
|
||||
}
|
||||
});
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add(crate::egui_github_link_file!());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextMenus {
|
||||
fn nested_menus(ui: &mut egui::Ui) {
|
||||
ui.set_max_width(200.0); // To make sure we wrap long text
|
||||
|
||||
if ui.button("Open…").clicked() {
|
||||
ui.close_menu();
|
||||
}
|
||||
ui.menu_button("SubMenu", |ui| {
|
||||
ui.menu_button("SubMenu", |ui| {
|
||||
if ui.button("Open…").clicked() {
|
||||
ui.close_menu();
|
||||
}
|
||||
let _ = ui.button("Item");
|
||||
});
|
||||
ui.menu_button("SubMenu", |ui| {
|
||||
if ui.button("Open…").clicked() {
|
||||
ui.close_menu();
|
||||
}
|
||||
let _ = ui.button("Item");
|
||||
});
|
||||
let _ = ui.button("Item");
|
||||
if ui.button("Open…").clicked() {
|
||||
ui.close_menu();
|
||||
}
|
||||
});
|
||||
ui.menu_button("SubMenu", |ui| {
|
||||
let _ = ui.button("Item1");
|
||||
let _ = ui.button("Item2");
|
||||
let _ = ui.button("Item3");
|
||||
let _ = ui.button("Item4");
|
||||
if ui.button("Open…").clicked() {
|
||||
ui.close_menu();
|
||||
}
|
||||
});
|
||||
let _ = ui.button("Very long text for this item that should be wrapped");
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use egui::{Context, Modifiers, ScrollArea, Ui};
|
||||
|
||||
use super::About;
|
||||
use crate::is_mobile;
|
||||
use crate::Demo;
|
||||
use crate::View;
|
||||
|
||||
use egui::containers::menu;
|
||||
use egui::style::StyleModifier;
|
||||
use egui::{Context, Modifiers, ScrollArea, Ui};
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
struct DemoGroup {
|
||||
@@ -65,7 +65,6 @@ impl Default for DemoGroups {
|
||||
Box::<super::paint_bezier::PaintBezier>::default(),
|
||||
Box::<super::code_editor::CodeEditor>::default(),
|
||||
Box::<super::code_example::CodeExample>::default(),
|
||||
Box::<super::context_menu::ContextMenus>::default(),
|
||||
Box::<super::dancing_strings::DancingStrings>::default(),
|
||||
Box::<super::drag_and_drop::DragAndDropDemo>::default(),
|
||||
Box::<super::extra_viewport::ExtraViewport>::default(),
|
||||
@@ -77,8 +76,9 @@ impl Default for DemoGroups {
|
||||
Box::<super::modals::Modals>::default(),
|
||||
Box::<super::multi_touch::MultiTouch>::default(),
|
||||
Box::<super::painting::Painting>::default(),
|
||||
Box::<super::pan_zoom::PanZoom>::default(),
|
||||
Box::<super::panels::Panels>::default(),
|
||||
Box::<super::popups::PopupsDemo>::default(),
|
||||
Box::<super::scene::SceneDemo>::default(),
|
||||
Box::<super::screenshot::Screenshot>::default(),
|
||||
Box::<super::scrolling::Scrolling>::default(),
|
||||
Box::<super::sliders::Sliders>::default(),
|
||||
@@ -100,6 +100,7 @@ impl Default for DemoGroups {
|
||||
Box::<super::tests::InputTest>::default(),
|
||||
Box::<super::tests::LayoutTest>::default(),
|
||||
Box::<super::tests::ManualLayoutTest>::default(),
|
||||
Box::<super::tests::TessellationTest>::default(),
|
||||
Box::<super::tests::WindowResizeTest>::default(),
|
||||
]),
|
||||
}
|
||||
@@ -225,29 +226,27 @@ impl DemoWindows {
|
||||
|
||||
fn mobile_top_bar(&mut self, ctx: &Context) {
|
||||
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
let font_size = 16.5;
|
||||
menu::Bar::new()
|
||||
.config(menu::MenuConfig::new().style(StyleModifier::default()))
|
||||
.ui(ui, |ui| {
|
||||
let font_size = 16.5;
|
||||
|
||||
ui.menu_button(egui::RichText::new("⏷ demos").size(font_size), |ui| {
|
||||
ui.set_style(ui.ctx().style()); // ignore the "menu" style set by `menu_button`.
|
||||
self.demo_list_ui(ui);
|
||||
if ui.ui_contains_pointer() && ui.input(|i| i.pointer.any_click()) {
|
||||
ui.close_menu();
|
||||
}
|
||||
});
|
||||
ui.menu_button(egui::RichText::new("⏷ demos").size(font_size), |ui| {
|
||||
self.demo_list_ui(ui);
|
||||
});
|
||||
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
use egui::special_emojis::{GITHUB, TWITTER};
|
||||
ui.hyperlink_to(
|
||||
egui::RichText::new(TWITTER).size(font_size),
|
||||
"https://twitter.com/ernerfeldt",
|
||||
);
|
||||
ui.hyperlink_to(
|
||||
egui::RichText::new(GITHUB).size(font_size),
|
||||
"https://github.com/emilk/egui",
|
||||
);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
use egui::special_emojis::GITHUB;
|
||||
ui.hyperlink_to(
|
||||
egui::RichText::new("🦋").size(font_size),
|
||||
"https://bsky.app/profile/ernerfeldt.bsky.social",
|
||||
);
|
||||
ui.hyperlink_to(
|
||||
egui::RichText::new(GITHUB).size(font_size),
|
||||
"https://github.com/emilk/egui",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -264,14 +263,14 @@ impl DemoWindows {
|
||||
|
||||
ui.separator();
|
||||
|
||||
use egui::special_emojis::{GITHUB, TWITTER};
|
||||
use egui::special_emojis::GITHUB;
|
||||
ui.hyperlink_to(
|
||||
format!("{GITHUB} egui on GitHub"),
|
||||
"https://github.com/emilk/egui",
|
||||
);
|
||||
ui.hyperlink_to(
|
||||
format!("{TWITTER} @ernerfeldt"),
|
||||
"https://twitter.com/ernerfeldt",
|
||||
"@ernerfeldt.bsky.social",
|
||||
"https://bsky.app/profile/ernerfeldt.bsky.social",
|
||||
);
|
||||
|
||||
ui.separator();
|
||||
@@ -280,7 +279,7 @@ impl DemoWindows {
|
||||
});
|
||||
|
||||
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
menu::Bar::new().ui(ui, |ui| {
|
||||
file_menu_button(ui);
|
||||
});
|
||||
});
|
||||
@@ -343,7 +342,6 @@ fn file_menu_button(ui: &mut Ui) {
|
||||
.clicked()
|
||||
{
|
||||
ui.ctx().memory_mut(|mem| mem.reset_areas());
|
||||
ui.close_menu();
|
||||
}
|
||||
|
||||
if ui
|
||||
@@ -355,7 +353,6 @@ fn file_menu_button(ui: &mut Ui) {
|
||||
.clicked()
|
||||
{
|
||||
ui.ctx().memory_mut(|mem| *mem = Default::default());
|
||||
ui.close_menu();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -365,13 +362,13 @@ mod tests {
|
||||
use crate::{demo::demo_app_windows::DemoGroups, Demo};
|
||||
use egui::Vec2;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use egui_kittest::{Harness, SnapshotOptions};
|
||||
use egui_kittest::{Harness, SnapshotOptions, SnapshotResults};
|
||||
|
||||
#[test]
|
||||
fn demos_should_match_snapshot() {
|
||||
let demos = DemoGroups::default().demos;
|
||||
|
||||
let mut errors = Vec::new();
|
||||
let mut results = SnapshotResults::new();
|
||||
|
||||
for mut demo in demos.demos {
|
||||
// Widget Gallery needs to be customized (to set a specific date) and has its own test
|
||||
@@ -397,7 +394,7 @@ mod tests {
|
||||
harness.set_size(Vec2::new(size.width as f32, size.height as f32));
|
||||
|
||||
// Run the app for some more frames...
|
||||
harness.run();
|
||||
harness.run_ok();
|
||||
|
||||
let mut options = SnapshotOptions::default();
|
||||
// The Bézier Curve demo needs a threshold of 2.1 to pass on linux
|
||||
@@ -405,12 +402,7 @@ mod tests {
|
||||
options.threshold = 2.1;
|
||||
}
|
||||
|
||||
let result = harness.try_snapshot_options(&format!("demos/{name}"), &options);
|
||||
if let Err(err) = result {
|
||||
errors.push(err.to_string());
|
||||
}
|
||||
results.add(harness.try_snapshot_options(&format!("demos/{name}"), &options));
|
||||
}
|
||||
|
||||
assert!(errors.is_empty(), "Errors: {errors:#?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +200,6 @@ fn special_char_name(chr: char) -> Option<&'static str> {
|
||||
'\u{E600}' => Some("web-dribbble"),
|
||||
'\u{E601}' => Some("web-stackoverflow"),
|
||||
'\u{E602}' => Some("web-vimeo"),
|
||||
'\u{E603}' => Some("web-twitter"),
|
||||
'\u{E604}' => Some("web-facebook"),
|
||||
'\u{E605}' => Some("web-googleplus"),
|
||||
'\u{E606}' => Some("web-pinterest"),
|
||||
|
||||
@@ -7,19 +7,18 @@ pub struct FrameDemo {
|
||||
impl Default for FrameDemo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
frame: egui::Frame {
|
||||
inner_margin: 12.0.into(),
|
||||
outer_margin: 24.0.into(),
|
||||
rounding: 14.0.into(),
|
||||
shadow: egui::Shadow {
|
||||
frame: egui::Frame::new()
|
||||
.inner_margin(12)
|
||||
.outer_margin(24)
|
||||
.corner_radius(14)
|
||||
.shadow(egui::Shadow {
|
||||
offset: [8, 12],
|
||||
blur: 16,
|
||||
spread: 0,
|
||||
color: egui::Color32::from_black_alpha(180),
|
||||
},
|
||||
fill: egui::Color32::from_rgba_unmultiplied(97, 0, 255, 128),
|
||||
stroke: egui::Stroke::new(1.0, egui::Color32::GRAY),
|
||||
},
|
||||
})
|
||||
.fill(egui::Color32::from_rgba_unmultiplied(97, 0, 255, 128))
|
||||
.stroke(egui::Stroke::new(1.0, egui::Color32::GRAY)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,7 +56,7 @@ impl crate::View for FrameDemo {
|
||||
// We want to paint a background around the outer margin of the demonstration frame, so we use another frame around it:
|
||||
egui::Frame::default()
|
||||
.stroke(ui.visuals().widgets.noninteractive.bg_stroke)
|
||||
.rounding(ui.visuals().widgets.noninteractive.rounding)
|
||||
.corner_radius(ui.visuals().widgets.noninteractive.corner_radius)
|
||||
.show(ui, |ui| {
|
||||
self.frame.show(ui, |ui| {
|
||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::{Demo, View};
|
||||
|
||||
use egui::{
|
||||
vec2, Align, Checkbox, CollapsingHeader, Color32, Context, FontId, Frame, Resize, RichText,
|
||||
Sense, Slider, Stroke, TextFormat, TextStyle, Ui, Vec2, Window,
|
||||
vec2, Align, Checkbox, CollapsingHeader, Color32, Context, FontId, Resize, RichText, Sense,
|
||||
Slider, Stroke, TextFormat, TextStyle, Ui, Vec2, Window,
|
||||
};
|
||||
|
||||
/// Showcase some ui code
|
||||
@@ -124,9 +124,9 @@ impl View for MiscDemoWindow {
|
||||
)
|
||||
.changed()
|
||||
{
|
||||
self.checklist
|
||||
.iter_mut()
|
||||
.for_each(|checked| *checked = all_checked);
|
||||
for check in &mut self.checklist {
|
||||
*check = all_checked;
|
||||
}
|
||||
}
|
||||
for (i, checked) in self.checklist.iter_mut().enumerate() {
|
||||
ui.checkbox(checked, format!("Item {}", i + 1));
|
||||
@@ -358,7 +358,7 @@ impl ColorWidgets {
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
struct BoxPainting {
|
||||
size: Vec2,
|
||||
rounding: f32,
|
||||
corner_radius: f32,
|
||||
stroke_width: f32,
|
||||
num_boxes: usize,
|
||||
}
|
||||
@@ -367,7 +367,7 @@ impl Default for BoxPainting {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
size: vec2(64.0, 32.0),
|
||||
rounding: 5.0,
|
||||
corner_radius: 5.0,
|
||||
stroke_width: 2.0,
|
||||
num_boxes: 1,
|
||||
}
|
||||
@@ -378,7 +378,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.rounding, 0.0..=50.0).text("rounding"));
|
||||
ui.add(Slider::new(&mut self.corner_radius, 0.0..=50.0).text("corner_radius"));
|
||||
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"));
|
||||
|
||||
@@ -387,9 +387,10 @@ impl BoxPainting {
|
||||
let (rect, _response) = ui.allocate_at_least(self.size, Sense::hover());
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
self.rounding,
|
||||
self.corner_radius,
|
||||
ui.visuals().text_color().gamma_multiply(0.5),
|
||||
Stroke::new(self.stroke_width, Color32::WHITE),
|
||||
egui::StrokeKind::Inside,
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -512,54 +513,52 @@ fn ui_stack_demo(ui: &mut Ui) {
|
||||
);
|
||||
});
|
||||
let stack = ui.stack().clone();
|
||||
Frame {
|
||||
inner_margin: ui.spacing().menu_margin,
|
||||
stroke: ui.visuals().widgets.noninteractive.bg_stroke,
|
||||
..Default::default()
|
||||
}
|
||||
.show(ui, |ui| {
|
||||
egui_extras::TableBuilder::new(ui)
|
||||
.column(egui_extras::Column::auto())
|
||||
.column(egui_extras::Column::auto())
|
||||
.header(18.0, |mut header| {
|
||||
header.col(|ui| {
|
||||
ui.strong("id");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.strong("kind");
|
||||
});
|
||||
})
|
||||
.body(|mut body| {
|
||||
for node in stack.iter() {
|
||||
body.row(18.0, |mut row| {
|
||||
row.col(|ui| {
|
||||
let response = ui.label(format!("{:?}", node.id));
|
||||
egui::Frame::new()
|
||||
.inner_margin(ui.spacing().menu_margin)
|
||||
.stroke(ui.visuals().widgets.noninteractive.bg_stroke)
|
||||
.show(ui, |ui| {
|
||||
egui_extras::TableBuilder::new(ui)
|
||||
.column(egui_extras::Column::auto())
|
||||
.column(egui_extras::Column::auto())
|
||||
.header(18.0, |mut header| {
|
||||
header.col(|ui| {
|
||||
ui.strong("id");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.strong("kind");
|
||||
});
|
||||
})
|
||||
.body(|mut body| {
|
||||
for node in stack.iter() {
|
||||
body.row(18.0, |mut row| {
|
||||
row.col(|ui| {
|
||||
let response = ui.label(format!("{:?}", node.id));
|
||||
|
||||
if response.hovered() {
|
||||
ui.ctx().debug_painter().debug_rect(
|
||||
node.max_rect,
|
||||
Color32::GREEN,
|
||||
"max_rect",
|
||||
);
|
||||
ui.ctx().debug_painter().circle_filled(
|
||||
node.min_rect.min,
|
||||
2.0,
|
||||
Color32::RED,
|
||||
);
|
||||
}
|
||||
});
|
||||
if response.hovered() {
|
||||
ui.ctx().debug_painter().debug_rect(
|
||||
node.max_rect,
|
||||
Color32::GREEN,
|
||||
"max_rect",
|
||||
);
|
||||
ui.ctx().debug_painter().circle_filled(
|
||||
node.min_rect.min,
|
||||
2.0,
|
||||
Color32::RED,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
row.col(|ui| {
|
||||
ui.label(if let Some(kind) = node.kind() {
|
||||
format!("{kind:?}")
|
||||
} else {
|
||||
"-".to_owned()
|
||||
row.col(|ui| {
|
||||
ui.label(if let Some(kind) = node.kind() {
|
||||
format!("{kind:?}")
|
||||
} else {
|
||||
"-".to_owned()
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ui.small("Hover on UI's ids to display their origin and max rect.");
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
pub mod about;
|
||||
pub mod code_editor;
|
||||
pub mod code_example;
|
||||
pub mod context_menu;
|
||||
pub mod dancing_strings;
|
||||
pub mod demo_app_windows;
|
||||
pub mod drag_and_drop;
|
||||
@@ -21,9 +20,10 @@ pub mod modals;
|
||||
pub mod multi_touch;
|
||||
pub mod paint_bezier;
|
||||
pub mod painting;
|
||||
pub mod pan_zoom;
|
||||
pub mod panels;
|
||||
pub mod password;
|
||||
mod popups;
|
||||
pub mod scene;
|
||||
pub mod screenshot;
|
||||
pub mod scrolling;
|
||||
pub mod sliders;
|
||||
|
||||
@@ -96,7 +96,9 @@ impl crate::View for Modals {
|
||||
*save_modal_open = true;
|
||||
}
|
||||
if ui.button("Cancel").clicked() {
|
||||
*user_modal_open = false;
|
||||
// You can call `ui.close()` to close the modal.
|
||||
// (This causes the current modals `should_close` to return true)
|
||||
ui.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -123,7 +125,7 @@ impl crate::View for Modals {
|
||||
}
|
||||
|
||||
if ui.button("No Thanks").clicked() {
|
||||
*save_modal_open = false;
|
||||
ui.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -165,7 +167,7 @@ mod tests {
|
||||
use egui::accesskit::Role;
|
||||
use egui::Key;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use egui_kittest::Harness;
|
||||
use egui_kittest::{Harness, SnapshotResults};
|
||||
|
||||
#[test]
|
||||
fn clicking_escape_when_popup_open_should_not_close_modal() {
|
||||
@@ -183,12 +185,13 @@ mod tests {
|
||||
|
||||
harness.get_by_role(Role::ComboBox).click();
|
||||
|
||||
harness.run();
|
||||
// Harness::run would fail because we keep requesting repaints to simulate progress.
|
||||
harness.run_ok();
|
||||
assert!(harness.ctx.memory(|mem| mem.any_popup_open()));
|
||||
assert!(harness.state().user_modal_open);
|
||||
|
||||
harness.press_key(Key::Escape);
|
||||
harness.run();
|
||||
harness.run_ok();
|
||||
assert!(!harness.ctx.memory(|mem| mem.any_popup_open()));
|
||||
assert!(harness.state().user_modal_open);
|
||||
}
|
||||
@@ -232,28 +235,18 @@ mod tests {
|
||||
initial_state,
|
||||
);
|
||||
|
||||
let mut results = Vec::new();
|
||||
let mut results = SnapshotResults::new();
|
||||
|
||||
harness.run();
|
||||
results.push(harness.try_snapshot("modals_1"));
|
||||
results.add(harness.try_snapshot("modals_1"));
|
||||
|
||||
harness.get_by_label("Save").click();
|
||||
// TODO(lucasmerlin): Remove these extra runs once run checks for repaint requests
|
||||
harness.run();
|
||||
harness.run();
|
||||
harness.run();
|
||||
results.push(harness.try_snapshot("modals_2"));
|
||||
harness.run_ok();
|
||||
results.add(harness.try_snapshot("modals_2"));
|
||||
|
||||
harness.get_by_label("Yes Please").click();
|
||||
// TODO(lucasmerlin): Remove these extra runs once run checks for repaint requests
|
||||
harness.run();
|
||||
harness.run();
|
||||
harness.run();
|
||||
results.push(harness.try_snapshot("modals_3"));
|
||||
|
||||
for result in results {
|
||||
result.unwrap();
|
||||
}
|
||||
harness.run_ok();
|
||||
results.add(harness.try_snapshot("modals_3"));
|
||||
}
|
||||
|
||||
// This tests whether the backdrop actually prevents interaction with lower layers.
|
||||
@@ -272,14 +265,11 @@ mod tests {
|
||||
initial_state,
|
||||
);
|
||||
|
||||
// TODO(lucasmerlin): Remove these extra runs once run checks for repaint requests
|
||||
harness.run();
|
||||
harness.run();
|
||||
harness.run();
|
||||
harness.run_ok();
|
||||
|
||||
harness.get_by_label("Yes Please").simulate_click();
|
||||
|
||||
harness.run();
|
||||
harness.run_ok();
|
||||
|
||||
// This snapshots should show the progress bar modal on top of the save modal.
|
||||
harness.snapshot("modals_backdrop_should_prevent_focusing_lower_area");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use egui::epaint::{CubicBezierShape, PathShape, QuadraticBezierShape};
|
||||
use egui::{
|
||||
emath, epaint, pos2, Color32, Context, Frame, Grid, Pos2, Rect, Sense, Shape, Stroke, Ui, Vec2,
|
||||
emath,
|
||||
epaint::{self, CubicBezierShape, PathShape, QuadraticBezierShape},
|
||||
pos2, Color32, Context, Frame, Grid, Pos2, Rect, Sense, Shape, Stroke, StrokeKind, Ui, Vec2,
|
||||
Widget, Window,
|
||||
};
|
||||
|
||||
@@ -132,6 +133,7 @@ impl PaintBezier {
|
||||
shape.visual_bounding_rect(),
|
||||
0.0,
|
||||
self.bounding_box_stroke,
|
||||
StrokeKind::Outside,
|
||||
));
|
||||
painter.add(shape);
|
||||
}
|
||||
@@ -143,6 +145,7 @@ impl PaintBezier {
|
||||
shape.visual_bounding_rect(),
|
||||
0.0,
|
||||
self.bounding_box_stroke,
|
||||
StrokeKind::Outside,
|
||||
));
|
||||
painter.add(shape);
|
||||
}
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
use egui::emath::TSTransform;
|
||||
use egui::TextWrapMode;
|
||||
|
||||
#[derive(Clone, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct PanZoom {
|
||||
transform: TSTransform,
|
||||
drag_value: f32,
|
||||
}
|
||||
|
||||
impl Eq for PanZoom {}
|
||||
|
||||
impl crate::Demo for PanZoom {
|
||||
fn name(&self) -> &'static str {
|
||||
"🔍 Pan Zoom"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
use crate::View as _;
|
||||
let window = egui::Window::new("Pan Zoom")
|
||||
.default_width(300.0)
|
||||
.default_height(300.0)
|
||||
.vscroll(false)
|
||||
.open(open);
|
||||
window.show(ctx, |ui| self.ui(ui));
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::View for PanZoom {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(
|
||||
"Pan, zoom in, and zoom out with scrolling (see the plot demo for more instructions). \
|
||||
Double click on the background to reset.",
|
||||
);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add(crate::egui_github_link_file!());
|
||||
});
|
||||
ui.separator();
|
||||
|
||||
let (id, rect) = ui.allocate_space(ui.available_size());
|
||||
let response = ui.interact(rect, id, egui::Sense::click_and_drag());
|
||||
// Allow dragging the background as well.
|
||||
if response.dragged() {
|
||||
self.transform.translation += response.drag_delta();
|
||||
}
|
||||
|
||||
// Plot-like reset
|
||||
if response.double_clicked() {
|
||||
self.transform = TSTransform::default();
|
||||
}
|
||||
|
||||
let transform =
|
||||
TSTransform::from_translation(ui.min_rect().left_top().to_vec2()) * self.transform;
|
||||
|
||||
if let Some(pointer) = ui.ctx().input(|i| i.pointer.hover_pos()) {
|
||||
// Note: doesn't catch zooming / panning if a button in this PanZoom container is hovered.
|
||||
if response.hovered() {
|
||||
let pointer_in_layer = transform.inverse() * pointer;
|
||||
let zoom_delta = ui.ctx().input(|i| i.zoom_delta());
|
||||
let pan_delta = ui.ctx().input(|i| i.smooth_scroll_delta);
|
||||
|
||||
// Zoom in on pointer:
|
||||
self.transform = self.transform
|
||||
* TSTransform::from_translation(pointer_in_layer.to_vec2())
|
||||
* TSTransform::from_scaling(zoom_delta)
|
||||
* TSTransform::from_translation(-pointer_in_layer.to_vec2());
|
||||
|
||||
// Pan:
|
||||
self.transform = TSTransform::from_translation(pan_delta) * self.transform;
|
||||
}
|
||||
}
|
||||
|
||||
for (i, (pos, callback)) in [
|
||||
(
|
||||
egui::Pos2::new(0.0, 0.0),
|
||||
Box::new(|ui: &mut egui::Ui, _: &mut Self| {
|
||||
ui.button("top left").on_hover_text("Normal tooltip")
|
||||
}) as Box<dyn Fn(&mut egui::Ui, &mut Self) -> egui::Response>,
|
||||
),
|
||||
(
|
||||
egui::Pos2::new(0.0, 120.0),
|
||||
Box::new(|ui: &mut egui::Ui, _| {
|
||||
ui.button("bottom left").on_hover_text("Normal tooltip")
|
||||
}),
|
||||
),
|
||||
(
|
||||
egui::Pos2::new(120.0, 120.0),
|
||||
Box::new(|ui: &mut egui::Ui, _| {
|
||||
ui.button("right bottom")
|
||||
.on_hover_text_at_pointer("Tooltip at pointer")
|
||||
}),
|
||||
),
|
||||
(
|
||||
egui::Pos2::new(120.0, 0.0),
|
||||
Box::new(|ui: &mut egui::Ui, _| {
|
||||
ui.button("right top")
|
||||
.on_hover_text_at_pointer("Tooltip at pointer")
|
||||
}),
|
||||
),
|
||||
(
|
||||
egui::Pos2::new(60.0, 60.0),
|
||||
Box::new(|ui, state| {
|
||||
use egui::epaint::{pos2, CircleShape, Color32, QuadraticBezierShape, Stroke};
|
||||
// Smiley face.
|
||||
let painter = ui.painter();
|
||||
painter.add(CircleShape::filled(pos2(0.0, -10.0), 1.0, Color32::YELLOW));
|
||||
painter.add(CircleShape::filled(pos2(10.0, -10.0), 1.0, Color32::YELLOW));
|
||||
painter.add(QuadraticBezierShape::from_points_stroke(
|
||||
[pos2(0.0, 0.0), pos2(5.0, 3.0), pos2(10.0, 0.0)],
|
||||
false,
|
||||
Color32::TRANSPARENT,
|
||||
Stroke::new(1.0, Color32::YELLOW),
|
||||
));
|
||||
|
||||
ui.add(egui::Slider::new(&mut state.drag_value, 0.0..=100.0).text("My value"))
|
||||
}),
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
let window_layer = ui.layer_id();
|
||||
let id = egui::Area::new(id.with(("subarea", i)))
|
||||
.default_pos(pos)
|
||||
.order(egui::Order::Middle)
|
||||
.constrain(false)
|
||||
.show(ui.ctx(), |ui| {
|
||||
ui.set_clip_rect(transform.inverse() * rect);
|
||||
egui::Frame::default()
|
||||
.rounding(egui::Rounding::same(4))
|
||||
.inner_margin(egui::Margin::same(8))
|
||||
.stroke(ui.ctx().style().visuals.window_stroke)
|
||||
.fill(ui.style().visuals.panel_fill)
|
||||
.show(ui, |ui| {
|
||||
ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
|
||||
callback(ui, self)
|
||||
});
|
||||
})
|
||||
.response
|
||||
.layer_id;
|
||||
ui.ctx().set_transform_layer(id, transform);
|
||||
ui.ctx().set_sublayer(window_layer, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
306
crates/egui_demo_lib/src/demo/popups.rs
Normal file
306
crates/egui_demo_lib/src/demo/popups.rs
Normal file
@@ -0,0 +1,306 @@
|
||||
use crate::rust_view_ui;
|
||||
use egui::color_picker::{color_picker_color32, Alpha};
|
||||
use egui::containers::menu::{MenuConfig, SubMenuButton};
|
||||
use egui::{
|
||||
include_image, Align, Align2, ComboBox, Frame, Id, Layout, Popup, PopupCloseBehavior,
|
||||
RectAlign, RichText, Tooltip, Ui, UiBuilder,
|
||||
};
|
||||
|
||||
/// Showcase [`Popup`].
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub struct PopupsDemo {
|
||||
align4: RectAlign,
|
||||
gap: f32,
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
close_behavior: PopupCloseBehavior,
|
||||
popup_open: bool,
|
||||
checked: bool,
|
||||
color: egui::Color32,
|
||||
}
|
||||
|
||||
impl PopupsDemo {
|
||||
fn apply_options<'a>(&self, popup: Popup<'a>) -> Popup<'a> {
|
||||
popup
|
||||
.align(self.align4)
|
||||
.gap(self.gap)
|
||||
.close_behavior(self.close_behavior)
|
||||
}
|
||||
|
||||
fn nested_menus(&mut self, ui: &mut Ui) {
|
||||
ui.set_max_width(200.0); // To make sure we wrap long text
|
||||
|
||||
if ui.button("Open…").clicked() {
|
||||
ui.close();
|
||||
}
|
||||
ui.menu_button("Popups can have submenus", |ui| {
|
||||
ui.menu_button("SubMenu", |ui| {
|
||||
if ui.button("Open…").clicked() {
|
||||
ui.close();
|
||||
}
|
||||
let _ = ui.button("Item");
|
||||
ui.menu_button("Recursive", |ui| self.nested_menus(ui));
|
||||
});
|
||||
ui.menu_button("SubMenu", |ui| {
|
||||
if ui.button("Open…").clicked() {
|
||||
ui.close();
|
||||
}
|
||||
let _ = ui.button("Item");
|
||||
});
|
||||
let _ = ui.button("Item");
|
||||
if ui.button("Open…").clicked() {
|
||||
ui.close();
|
||||
}
|
||||
});
|
||||
ui.menu_image_text_button(
|
||||
include_image!("../../data/icon.png"),
|
||||
"I have an icon!",
|
||||
|ui| {
|
||||
let _ = ui.button("Item1");
|
||||
let _ = ui.button("Item2");
|
||||
let _ = ui.button("Item3");
|
||||
let _ = ui.button("Item4");
|
||||
if ui.button("Open…").clicked() {
|
||||
ui.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
let _ = ui.button("Very long text for this item that should be wrapped");
|
||||
SubMenuButton::new("Always CloseOnClickOutside")
|
||||
.config(MenuConfig::new().close_behavior(PopupCloseBehavior::CloseOnClickOutside))
|
||||
.ui(ui, |ui| {
|
||||
ui.checkbox(&mut self.checked, "Checkbox");
|
||||
|
||||
// Customized color SubMenuButton
|
||||
let is_bright = self.color.intensity() > 0.5;
|
||||
let text_color = if is_bright {
|
||||
egui::Color32::BLACK
|
||||
} else {
|
||||
egui::Color32::WHITE
|
||||
};
|
||||
let mut color_button =
|
||||
SubMenuButton::new(RichText::new("Background").color(text_color));
|
||||
color_button.button = color_button.button.fill(self.color);
|
||||
color_button.button = color_button
|
||||
.button
|
||||
.right_text(RichText::new(SubMenuButton::RIGHT_ARROW).color(text_color));
|
||||
color_button.ui(ui, |ui| {
|
||||
ui.spacing_mut().slider_width = 200.0;
|
||||
color_picker_color32(ui, &mut self.color, Alpha::Opaque);
|
||||
});
|
||||
|
||||
if ui.button("Open…").clicked() {
|
||||
ui.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PopupsDemo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
align4: RectAlign::default(),
|
||||
gap: 4.0,
|
||||
close_behavior: PopupCloseBehavior::CloseOnClick,
|
||||
popup_open: false,
|
||||
checked: false,
|
||||
color: egui::Color32::RED,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Demo for PopupsDemo {
|
||||
fn name(&self) -> &'static str {
|
||||
"\u{2755} Popups"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
egui::Window::new(self.name())
|
||||
.open(open)
|
||||
.resizable(false)
|
||||
.default_width(250.0)
|
||||
.constrain(false)
|
||||
.show(ctx, |ui| {
|
||||
use crate::View as _;
|
||||
self.ui(ui);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::View for PopupsDemo {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
let response = Frame::group(ui.style())
|
||||
.show(ui, |ui| {
|
||||
ui.set_width(ui.available_width());
|
||||
ui.vertical_centered(|ui| ui.button("Click, right-click and hover me!"))
|
||||
.inner
|
||||
})
|
||||
.inner;
|
||||
|
||||
self.apply_options(Popup::menu(&response).id(Id::new("menu")))
|
||||
.show(|ui| self.nested_menus(ui));
|
||||
|
||||
self.apply_options(Popup::context_menu(&response).id(Id::new("context_menu")))
|
||||
.show(|ui| self.nested_menus(ui));
|
||||
|
||||
if self.popup_open {
|
||||
self.apply_options(Popup::from_response(&response).id(Id::new("popup")))
|
||||
.show(|ui| {
|
||||
ui.label("Popup contents");
|
||||
});
|
||||
}
|
||||
|
||||
let mut tooltip = Tooltip::for_enabled(&response);
|
||||
tooltip.popup = self.apply_options(tooltip.popup);
|
||||
tooltip.show(|ui| {
|
||||
ui.label("Tooltips are popups, too!");
|
||||
});
|
||||
|
||||
Frame::canvas(ui.style()).show(ui, |ui| {
|
||||
let mut reset_btn_ui = ui.new_child(
|
||||
UiBuilder::new()
|
||||
.max_rect(ui.max_rect())
|
||||
.layout(Layout::right_to_left(Align::Min)),
|
||||
);
|
||||
if reset_btn_ui
|
||||
.button("⟲")
|
||||
.on_hover_text("Reset to defaults")
|
||||
.clicked()
|
||||
{
|
||||
*self = Self::default();
|
||||
}
|
||||
|
||||
ui.set_width(ui.available_width());
|
||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
let align_combobox = |ui: &mut Ui, label: &str, align: &mut Align2| {
|
||||
let aligns = [
|
||||
(Align2::LEFT_TOP, "LEFT_TOP"),
|
||||
(Align2::LEFT_CENTER, "LEFT_CENTER"),
|
||||
(Align2::LEFT_BOTTOM, "LEFT_BOTTOM"),
|
||||
(Align2::CENTER_TOP, "CENTER_TOP"),
|
||||
(Align2::CENTER_CENTER, "CENTER_CENTER"),
|
||||
(Align2::CENTER_BOTTOM, "CENTER_BOTTOM"),
|
||||
(Align2::RIGHT_TOP, "RIGHT_TOP"),
|
||||
(Align2::RIGHT_CENTER, "RIGHT_CENTER"),
|
||||
(Align2::RIGHT_BOTTOM, "RIGHT_BOTTOM"),
|
||||
];
|
||||
|
||||
ComboBox::new(label, "")
|
||||
.selected_text(aligns.iter().find(|(a, _)| a == align).unwrap().1)
|
||||
.show_ui(ui, |ui| {
|
||||
for (align2, name) in &aligns {
|
||||
ui.selectable_value(align, *align2, *name);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
rust_view_ui(ui, "let align = RectAlign {");
|
||||
ui.horizontal(|ui| {
|
||||
rust_view_ui(ui, " parent: Align2::");
|
||||
align_combobox(ui, "parent", &mut self.align4.parent);
|
||||
rust_view_ui(ui, ",");
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
rust_view_ui(ui, " child: Align2::");
|
||||
align_combobox(ui, "child", &mut self.align4.child);
|
||||
rust_view_ui(ui, ",");
|
||||
});
|
||||
rust_view_ui(ui, "};");
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
rust_view_ui(ui, "let align = RectAlign::");
|
||||
|
||||
let presets = [
|
||||
(RectAlign::TOP_START, "TOP_START"),
|
||||
(RectAlign::TOP, "TOP"),
|
||||
(RectAlign::TOP_END, "TOP_END"),
|
||||
(RectAlign::RIGHT_START, "RIGHT_START"),
|
||||
(RectAlign::RIGHT, "RIGHT"),
|
||||
(RectAlign::RIGHT_END, "RIGHT_END"),
|
||||
(RectAlign::BOTTOM_START, "BOTTOM_START"),
|
||||
(RectAlign::BOTTOM, "BOTTOM"),
|
||||
(RectAlign::BOTTOM_END, "BOTTOM_END"),
|
||||
(RectAlign::LEFT_START, "LEFT_START"),
|
||||
(RectAlign::LEFT, "LEFT"),
|
||||
(RectAlign::LEFT_END, "LEFT_END"),
|
||||
];
|
||||
|
||||
ComboBox::new("Preset", "")
|
||||
.selected_text(
|
||||
presets
|
||||
.iter()
|
||||
.find(|(a, _)| a == &self.align4)
|
||||
.map_or("<Select Preset>", |(_, name)| *name),
|
||||
)
|
||||
.show_ui(ui, |ui| {
|
||||
for (align4, name) in &presets {
|
||||
ui.selectable_value(&mut self.align4, *align4, *name);
|
||||
}
|
||||
});
|
||||
rust_view_ui(ui, ";");
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
rust_view_ui(ui, "let gap = ");
|
||||
ui.add(egui::DragValue::new(&mut self.gap));
|
||||
rust_view_ui(ui, ";");
|
||||
});
|
||||
|
||||
rust_view_ui(ui, "let close_behavior");
|
||||
ui.horizontal(|ui| {
|
||||
rust_view_ui(ui, " = PopupCloseBehavior::");
|
||||
let close_behaviors = [
|
||||
(
|
||||
PopupCloseBehavior::CloseOnClick,
|
||||
"CloseOnClick",
|
||||
"Closes when the user clicks anywhere (inside or outside)",
|
||||
),
|
||||
(
|
||||
PopupCloseBehavior::CloseOnClickOutside,
|
||||
"CloseOnClickOutside",
|
||||
"Closes when the user clicks outside the popup",
|
||||
),
|
||||
(
|
||||
PopupCloseBehavior::IgnoreClicks,
|
||||
"IgnoreClicks",
|
||||
"Close only when the button is clicked again",
|
||||
),
|
||||
];
|
||||
ComboBox::new("Close behavior", "")
|
||||
.selected_text(
|
||||
close_behaviors
|
||||
.iter()
|
||||
.find_map(|(behavior, text, _)| {
|
||||
(behavior == &self.close_behavior).then_some(*text)
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
.show_ui(ui, |ui| {
|
||||
for (close_behavior, name, tooltip) in &close_behaviors {
|
||||
ui.selectable_value(&mut self.close_behavior, *close_behavior, *name)
|
||||
.on_hover_text(*tooltip);
|
||||
}
|
||||
});
|
||||
rust_view_ui(ui, ";");
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
rust_view_ui(ui, "let popup_open = ");
|
||||
ui.checkbox(&mut self.popup_open, "");
|
||||
rust_view_ui(ui, ";");
|
||||
});
|
||||
ui.monospace("");
|
||||
rust_view_ui(ui, "let response = ui.button(\"Click me!\");");
|
||||
rust_view_ui(ui, "Popup::menu(&response)");
|
||||
rust_view_ui(ui, " .gap(gap).align(align)");
|
||||
rust_view_ui(ui, " .close_behavior(close_behavior)");
|
||||
rust_view_ui(ui, " .show(|ui| { /* menu contents */ });");
|
||||
});
|
||||
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add(crate::egui_github_link_file!());
|
||||
});
|
||||
}
|
||||
}
|
||||
82
crates/egui_demo_lib/src/demo/scene.rs
Normal file
82
crates/egui_demo_lib/src/demo/scene.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use egui::{Pos2, Rect, Scene, Vec2};
|
||||
|
||||
use super::widget_gallery;
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct SceneDemo {
|
||||
widget_gallery: widget_gallery::WidgetGallery,
|
||||
scene_rect: Rect,
|
||||
}
|
||||
|
||||
impl Default for SceneDemo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
widget_gallery: widget_gallery::WidgetGallery::default().with_date_button(false), // disable date button so that we don't fail the snapshot test
|
||||
scene_rect: Rect::ZERO, // `egui::Scene` will initialize this to something valid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Demo for SceneDemo {
|
||||
fn name(&self) -> &'static str {
|
||||
"🔍 Scene"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
use crate::View as _;
|
||||
let window = egui::Window::new("Scene")
|
||||
.default_width(300.0)
|
||||
.default_height(300.0)
|
||||
.scroll(false)
|
||||
.open(open);
|
||||
window.show(ctx, |ui| self.ui(ui));
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::View for SceneDemo {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(
|
||||
"You can pan by scrolling, and zoom using cmd-scroll. \
|
||||
Double click on the background to reset view.",
|
||||
);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add(crate::egui_github_link_file!());
|
||||
});
|
||||
ui.separator();
|
||||
|
||||
ui.label(format!("Scene rect: {:#?}", &mut self.scene_rect));
|
||||
|
||||
ui.separator();
|
||||
|
||||
egui::Frame::group(ui.style())
|
||||
.inner_margin(0.0)
|
||||
.show(ui, |ui| {
|
||||
let scene = Scene::new()
|
||||
.max_inner_size([350.0, 1000.0])
|
||||
.zoom_range(0.1..=2.0);
|
||||
|
||||
let mut reset_view = false;
|
||||
let mut inner_rect = Rect::NAN;
|
||||
let response = scene
|
||||
.show(ui, &mut self.scene_rect, |ui| {
|
||||
reset_view = ui.button("Reset view").clicked();
|
||||
|
||||
ui.add_space(16.0);
|
||||
|
||||
self.widget_gallery.ui(ui);
|
||||
|
||||
ui.put(
|
||||
Rect::from_min_size(Pos2::new(0.0, -64.0), Vec2::new(200.0, 16.0)),
|
||||
egui::Label::new("You can put a widget anywhere").selectable(false),
|
||||
);
|
||||
|
||||
inner_rect = ui.min_rect();
|
||||
})
|
||||
.response;
|
||||
|
||||
if reset_view || response.double_clicked() {
|
||||
self.scene_rect = inner_rect;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,14 @@ impl crate::Demo for CursorTest {
|
||||
|
||||
impl crate::View for CursorTest {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
if ui
|
||||
.button("Center pointer in window")
|
||||
.on_hover_text("The platform may not support this.")
|
||||
.clicked()
|
||||
{
|
||||
let position = ui.ctx().available_rect().center();
|
||||
ui.ctx().set_pointer_position(position);
|
||||
}
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
ui.heading("Hover to switch cursor icon:");
|
||||
for &cursor_icon in &egui::CursorIcon::ALL {
|
||||
|
||||
@@ -6,6 +6,7 @@ mod input_event_history;
|
||||
mod input_test;
|
||||
mod layout_test;
|
||||
mod manual_layout_test;
|
||||
mod tessellation_test;
|
||||
mod window_resize_test;
|
||||
|
||||
pub use clipboard_test::ClipboardTest;
|
||||
@@ -16,4 +17,5 @@ pub use input_event_history::InputEventHistory;
|
||||
pub use input_test::InputTest;
|
||||
pub use layout_test::LayoutTest;
|
||||
pub use manual_layout_test::ManualLayoutTest;
|
||||
pub use tessellation_test::TessellationTest;
|
||||
pub use window_resize_test::WindowResizeTest;
|
||||
|
||||
379
crates/egui_demo_lib/src/demo/tests/tessellation_test.rs
Normal file
379
crates/egui_demo_lib/src/demo/tests/tessellation_test.rs
Normal file
@@ -0,0 +1,379 @@
|
||||
use egui::{
|
||||
emath::{GuiRounding, TSTransform},
|
||||
epaint::{self, RectShape},
|
||||
vec2, Color32, Pos2, Rect, Sense, StrokeKind, Vec2,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TessellationTest {
|
||||
shape: RectShape,
|
||||
|
||||
magnification_pixel_size: f32,
|
||||
tessellation_options: epaint::TessellationOptions,
|
||||
paint_edges: bool,
|
||||
}
|
||||
|
||||
impl Default for TessellationTest {
|
||||
fn default() -> Self {
|
||||
let shape = Self::interesting_shapes()[0].1.clone();
|
||||
Self {
|
||||
shape,
|
||||
magnification_pixel_size: 12.0,
|
||||
tessellation_options: Default::default(),
|
||||
paint_edges: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TessellationTest {
|
||||
fn interesting_shapes() -> Vec<(&'static str, RectShape)> {
|
||||
fn sized(size: impl Into<Vec2>) -> Rect {
|
||||
Rect::from_center_size(Pos2::ZERO, size.into())
|
||||
}
|
||||
|
||||
let baby_blue = Color32::from_rgb(0, 181, 255);
|
||||
|
||||
let mut shapes = vec![
|
||||
(
|
||||
"Normal",
|
||||
RectShape::new(
|
||||
sized([20.0, 16.0]),
|
||||
2.0,
|
||||
baby_blue,
|
||||
(1.0, Color32::WHITE),
|
||||
StrokeKind::Inside,
|
||||
),
|
||||
),
|
||||
(
|
||||
"Minimal rounding",
|
||||
RectShape::new(
|
||||
sized([20.0, 16.0]),
|
||||
1.0,
|
||||
baby_blue,
|
||||
(1.0, Color32::WHITE),
|
||||
StrokeKind::Inside,
|
||||
),
|
||||
),
|
||||
(
|
||||
"Thin filled",
|
||||
RectShape::filled(sized([20.0, 0.5]), 2.0, baby_blue),
|
||||
),
|
||||
(
|
||||
"Thin stroked",
|
||||
RectShape::new(
|
||||
sized([20.0, 0.5]),
|
||||
2.0,
|
||||
baby_blue,
|
||||
(0.5, Color32::WHITE),
|
||||
StrokeKind::Inside,
|
||||
),
|
||||
),
|
||||
(
|
||||
"Blurred",
|
||||
RectShape::filled(sized([20.0, 16.0]), 2.0, baby_blue).with_blur_width(50.0),
|
||||
),
|
||||
(
|
||||
"Thick stroke, minimal rounding",
|
||||
RectShape::new(
|
||||
sized([20.0, 16.0]),
|
||||
1.0,
|
||||
baby_blue,
|
||||
(3.0, Color32::WHITE),
|
||||
StrokeKind::Inside,
|
||||
),
|
||||
),
|
||||
(
|
||||
"Blurred stroke",
|
||||
RectShape::new(
|
||||
sized([20.0, 16.0]),
|
||||
0.0,
|
||||
baby_blue,
|
||||
(5.0, Color32::WHITE),
|
||||
StrokeKind::Inside,
|
||||
)
|
||||
.with_blur_width(5.0),
|
||||
),
|
||||
(
|
||||
"Additive rectangle",
|
||||
RectShape::new(
|
||||
sized([24.0, 12.0]),
|
||||
0.0,
|
||||
egui::Color32::LIGHT_RED.additive().linear_multiply(0.025),
|
||||
(
|
||||
1.0,
|
||||
egui::Color32::LIGHT_BLUE.additive().linear_multiply(0.1),
|
||||
),
|
||||
StrokeKind::Outside,
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
for (_name, shape) in &mut shapes {
|
||||
shape.round_to_pixels = Some(true);
|
||||
}
|
||||
|
||||
shapes
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Demo for TessellationTest {
|
||||
fn name(&self) -> &'static str {
|
||||
"Tessellation Test"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
egui::Window::new(self.name())
|
||||
.resizable(false)
|
||||
.open(open)
|
||||
.show(ctx, |ui| {
|
||||
use crate::View as _;
|
||||
self.ui(ui);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::View for TessellationTest {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.add(crate::egui_github_link_file!());
|
||||
egui::reset_button(ui, self, "Reset");
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.group(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
rect_shape_ui(ui, &mut self.shape);
|
||||
});
|
||||
});
|
||||
|
||||
ui.group(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
ui.heading("Real size");
|
||||
egui::Frame::dark_canvas(ui.style()).show(ui, |ui| {
|
||||
let (resp, painter) =
|
||||
ui.allocate_painter(Vec2::splat(128.0), Sense::hover());
|
||||
let canvas = resp.rect;
|
||||
|
||||
let pixels_per_point = ui.pixels_per_point();
|
||||
let pixel_size = 1.0 / pixels_per_point;
|
||||
let mut shape = self.shape.clone();
|
||||
shape.rect = Rect::from_center_size(canvas.center(), shape.rect.size())
|
||||
.round_to_pixel_center(pixels_per_point)
|
||||
.translate(Vec2::new(pixel_size / 3.0, pixel_size / 5.0)); // Intentionally offset to test the effect of rounding
|
||||
painter.add(shape);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
ui.group(|ui| {
|
||||
ui.heading("Zoomed in");
|
||||
let magnification_pixel_size = &mut self.magnification_pixel_size;
|
||||
let tessellation_options = &mut self.tessellation_options;
|
||||
|
||||
egui::Grid::new("TessellationOptions")
|
||||
.num_columns(2)
|
||||
.spacing([12.0, 8.0])
|
||||
.striped(true)
|
||||
.show(ui, |ui| {
|
||||
ui.label("Magnification");
|
||||
ui.add(
|
||||
egui::DragValue::new(magnification_pixel_size)
|
||||
.speed(0.5)
|
||||
.range(1.0..=32.0),
|
||||
);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Feathering width");
|
||||
ui.horizontal(|ui| {
|
||||
ui.checkbox(&mut tessellation_options.feathering, "");
|
||||
ui.add_enabled(
|
||||
tessellation_options.feathering,
|
||||
egui::DragValue::new(
|
||||
&mut tessellation_options.feathering_size_in_pixels,
|
||||
)
|
||||
.speed(0.1)
|
||||
.range(0.0..=4.0)
|
||||
.suffix(" px"),
|
||||
);
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Paint edges");
|
||||
ui.checkbox(&mut self.paint_edges, "");
|
||||
ui.end_row();
|
||||
});
|
||||
|
||||
let magnification_pixel_size = *magnification_pixel_size;
|
||||
|
||||
egui::Frame::dark_canvas(ui.style()).show(ui, |ui| {
|
||||
let (resp, painter) = ui.allocate_painter(
|
||||
magnification_pixel_size * (self.shape.rect.size() + Vec2::splat(8.0)),
|
||||
Sense::hover(),
|
||||
);
|
||||
let canvas = resp.rect;
|
||||
|
||||
let mut shape = self.shape.clone();
|
||||
shape.rect = shape.rect.translate(Vec2::new(1.0 / 3.0, 1.0 / 5.0)); // Intentionally offset to test the effect of rounding
|
||||
|
||||
let mut mesh = epaint::Mesh::default();
|
||||
let mut tessellator = epaint::Tessellator::new(
|
||||
1.0,
|
||||
*tessellation_options,
|
||||
ui.fonts(|f| f.font_image_size()),
|
||||
vec![],
|
||||
);
|
||||
tessellator.tessellate_rect(&shape, &mut mesh);
|
||||
|
||||
// Scale and position the mesh:
|
||||
mesh.transform(
|
||||
TSTransform::from_translation(canvas.center().to_vec2())
|
||||
* TSTransform::from_scaling(magnification_pixel_size),
|
||||
);
|
||||
let mesh = std::sync::Arc::new(mesh);
|
||||
painter.add(epaint::Shape::mesh(mesh.clone()));
|
||||
|
||||
if self.paint_edges {
|
||||
let stroke = epaint::Stroke::new(0.5, Color32::MAGENTA);
|
||||
for triangle in mesh.triangles() {
|
||||
let a = mesh.vertices[triangle[0] as usize];
|
||||
let b = mesh.vertices[triangle[1] as usize];
|
||||
let c = mesh.vertices[triangle[2] as usize];
|
||||
|
||||
painter.line_segment([a.pos, b.pos], stroke);
|
||||
painter.line_segment([b.pos, c.pos], stroke);
|
||||
painter.line_segment([c.pos, a.pos], stroke);
|
||||
}
|
||||
}
|
||||
|
||||
if 3.0 < magnification_pixel_size {
|
||||
// Draw pixel centers:
|
||||
let pixel_radius = 0.75;
|
||||
let pixel_color = Color32::GRAY;
|
||||
for yi in 0.. {
|
||||
let y = (yi as f32 + 0.5) * magnification_pixel_size;
|
||||
if y > canvas.height() / 2.0 {
|
||||
break;
|
||||
}
|
||||
for xi in 0.. {
|
||||
let x = (xi as f32 + 0.5) * magnification_pixel_size;
|
||||
if x > canvas.width() / 2.0 {
|
||||
break;
|
||||
}
|
||||
for offset in [vec2(x, y), vec2(x, -y), vec2(-x, y), vec2(-x, -y)] {
|
||||
painter.circle_filled(
|
||||
canvas.center() + offset,
|
||||
pixel_radius,
|
||||
pixel_color,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn rect_shape_ui(ui: &mut egui::Ui, shape: &mut RectShape) {
|
||||
egui::ComboBox::from_id_salt("prefabs")
|
||||
.selected_text("Prefabs")
|
||||
.show_ui(ui, |ui| {
|
||||
for (name, prefab) in TessellationTest::interesting_shapes() {
|
||||
ui.selectable_value(shape, prefab, name);
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(4.0);
|
||||
|
||||
let RectShape {
|
||||
rect,
|
||||
corner_radius,
|
||||
fill,
|
||||
stroke,
|
||||
stroke_kind,
|
||||
blur_width,
|
||||
round_to_pixels,
|
||||
brush: _,
|
||||
} = shape;
|
||||
|
||||
let round_to_pixels = round_to_pixels.get_or_insert(true);
|
||||
|
||||
egui::Grid::new("RectShape")
|
||||
.num_columns(2)
|
||||
.spacing([12.0, 8.0])
|
||||
.striped(true)
|
||||
.show(ui, |ui| {
|
||||
ui.label("Size");
|
||||
ui.horizontal(|ui| {
|
||||
let mut size = rect.size();
|
||||
ui.add(
|
||||
egui::DragValue::new(&mut size.x)
|
||||
.speed(0.2)
|
||||
.range(0.0..=64.0),
|
||||
);
|
||||
ui.add(
|
||||
egui::DragValue::new(&mut size.y)
|
||||
.speed(0.2)
|
||||
.range(0.0..=64.0),
|
||||
);
|
||||
*rect = Rect::from_center_size(Pos2::ZERO, size);
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Corner radius");
|
||||
ui.add(corner_radius);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Fill");
|
||||
ui.color_edit_button_srgba(fill);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Stroke");
|
||||
ui.add(stroke);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Stroke kind");
|
||||
ui.horizontal(|ui| {
|
||||
ui.selectable_value(stroke_kind, StrokeKind::Inside, "Inside");
|
||||
ui.selectable_value(stroke_kind, StrokeKind::Middle, "Middle");
|
||||
ui.selectable_value(stroke_kind, StrokeKind::Outside, "Outside");
|
||||
});
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Blur width");
|
||||
ui.add(
|
||||
egui::DragValue::new(blur_width)
|
||||
.speed(0.5)
|
||||
.range(0.0..=20.0),
|
||||
);
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Round to pixels");
|
||||
ui.checkbox(round_to_pixels, "");
|
||||
ui.end_row();
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::View as _;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn snapshot_tessellation_test() {
|
||||
for (name, shape) in TessellationTest::interesting_shapes() {
|
||||
let mut test = TessellationTest {
|
||||
shape,
|
||||
..Default::default()
|
||||
};
|
||||
let mut harness = egui_kittest::Harness::new_ui(|ui| {
|
||||
test.ui(ui);
|
||||
});
|
||||
|
||||
harness.fit_contents();
|
||||
harness.run();
|
||||
|
||||
harness.snapshot(&format!("tessellation_test/{name}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,9 +58,7 @@ impl crate::View for TextEditDemo {
|
||||
}
|
||||
});
|
||||
|
||||
let anything_selected = output
|
||||
.cursor_range
|
||||
.map_or(false, |cursor| !cursor.is_empty());
|
||||
let anything_selected = output.cursor_range.is_some_and(|cursor| !cursor.is_empty());
|
||||
|
||||
ui.add_enabled(
|
||||
anything_selected,
|
||||
|
||||
@@ -56,8 +56,13 @@ pub fn toggle_ui(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
||||
// All coordinates are in absolute screen coordinates so we use `rect` to place the elements.
|
||||
let rect = rect.expand(visuals.expansion);
|
||||
let radius = 0.5 * rect.height();
|
||||
ui.painter()
|
||||
.rect(rect, radius, visuals.bg_fill, visuals.bg_stroke);
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
radius,
|
||||
visuals.bg_fill,
|
||||
visuals.bg_stroke,
|
||||
egui::StrokeKind::Inside,
|
||||
);
|
||||
// Paint the circle, animating it from left to right with `how_on`:
|
||||
let circle_x = egui::lerp((rect.left() + radius)..=(rect.right() - radius), how_on);
|
||||
let center = egui::pos2(circle_x, rect.center().y);
|
||||
@@ -88,8 +93,13 @@ fn toggle_ui_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
||||
let visuals = ui.style().interact_selectable(&response, *on);
|
||||
let rect = rect.expand(visuals.expansion);
|
||||
let radius = 0.5 * rect.height();
|
||||
ui.painter()
|
||||
.rect(rect, radius, visuals.bg_fill, visuals.bg_stroke);
|
||||
ui.painter().rect(
|
||||
rect,
|
||||
radius,
|
||||
visuals.bg_fill,
|
||||
visuals.bg_stroke,
|
||||
egui::StrokeKind::Inside,
|
||||
);
|
||||
let circle_x = egui::lerp((rect.left() + radius)..=(rect.right() - radius), how_on);
|
||||
let center = egui::pos2(circle_x, rect.center().y);
|
||||
ui.painter()
|
||||
|
||||
@@ -83,6 +83,9 @@ impl Tooltips {
|
||||
ui.label("You can select this text.");
|
||||
});
|
||||
|
||||
ui.label("This tooltip shows at the mouse cursor.")
|
||||
.on_hover_text_at_pointer("Move me around!!");
|
||||
|
||||
ui.separator(); // ---------------------------------------------------------
|
||||
|
||||
let tooltip_ui = |ui: &mut egui::Ui| {
|
||||
|
||||
@@ -22,6 +22,9 @@ pub struct WidgetGallery {
|
||||
#[cfg(feature = "chrono")]
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
date: Option<chrono::NaiveDate>,
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
with_date_button: bool,
|
||||
}
|
||||
|
||||
impl Default for WidgetGallery {
|
||||
@@ -38,10 +41,24 @@ impl Default for WidgetGallery {
|
||||
animate_progress_bar: false,
|
||||
#[cfg(feature = "chrono")]
|
||||
date: None,
|
||||
#[cfg(feature = "chrono")]
|
||||
with_date_button: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetGallery {
|
||||
#[allow(unused_mut)] // if not chrono
|
||||
#[inline]
|
||||
pub fn with_date_button(mut self, _with_date_button: bool) -> Self {
|
||||
#[cfg(feature = "chrono")]
|
||||
{
|
||||
self.with_date_button = _with_date_button;
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Demo for WidgetGallery {
|
||||
fn name(&self) -> &'static str {
|
||||
"🗄 Widget Gallery"
|
||||
@@ -124,6 +141,8 @@ impl WidgetGallery {
|
||||
animate_progress_bar,
|
||||
#[cfg(feature = "chrono")]
|
||||
date,
|
||||
#[cfg(feature = "chrono")]
|
||||
with_date_button,
|
||||
} = self;
|
||||
|
||||
ui.add(doc_link_label("Label", "label"));
|
||||
@@ -226,7 +245,7 @@ impl WidgetGallery {
|
||||
ui.end_row();
|
||||
|
||||
#[cfg(feature = "chrono")]
|
||||
{
|
||||
if *with_date_button {
|
||||
let date = date.get_or_insert_with(|| chrono::offset::Utc::now().date_naive());
|
||||
ui.add(doc_link_label_with_crate(
|
||||
"egui_extras",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use egui::Vec2b;
|
||||
use egui::{UiKind, Vec2b};
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
@@ -149,6 +149,16 @@ impl crate::View for WindowOptions {
|
||||
self.disabled_time = ui.input(|i| i.time);
|
||||
}
|
||||
egui::reset_button(ui, self, "Reset");
|
||||
if ui
|
||||
.button("Close")
|
||||
.on_hover_text("You can collapse / close Windows via Ui::close")
|
||||
.clicked()
|
||||
{
|
||||
// Calling close would close the collapsible within the window
|
||||
// ui.close();
|
||||
// Instead, we close the window itself
|
||||
ui.close_kind(UiKind::Window);
|
||||
}
|
||||
ui.add(crate::egui_github_link_file!());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ fn shortcuts(ui: &Ui, code: &mut dyn TextBuffer, ccursor_range: &mut CCursorRang
|
||||
if ui.input_mut(|i| i.consume_shortcut(&SHORTCUT_INDENT)) {
|
||||
// This is a placeholder till we can indent the active line
|
||||
any_change = true;
|
||||
let [primary, _secondary] = ccursor_range.sorted();
|
||||
let [primary, _secondary] = ccursor_range.sorted_cursors();
|
||||
|
||||
let advance = code.insert_text(" ", primary.index);
|
||||
ccursor_range.primary.index += advance;
|
||||
@@ -177,7 +177,7 @@ fn toggle_surrounding(
|
||||
ccursor_range: &mut CCursorRange,
|
||||
surrounding: &str,
|
||||
) {
|
||||
let [primary, secondary] = ccursor_range.sorted();
|
||||
let [primary, secondary] = ccursor_range.sorted_cursors();
|
||||
|
||||
let surrounding_ccount = surrounding.chars().count();
|
||||
|
||||
|
||||
@@ -92,8 +92,6 @@ impl ColorTest {
|
||||
|
||||
ui.label("Test that vertex color times texture color is done in gamma space:");
|
||||
ui.scope(|ui| {
|
||||
ui.spacing_mut().item_spacing.y = 0.0; // No spacing between gradients
|
||||
|
||||
let tex_color = Color32::from_rgb(64, 128, 255);
|
||||
let vertex_color = Color32::from_rgb(128, 196, 196);
|
||||
let ground_truth = mul_color_gamma(tex_color, vertex_color);
|
||||
@@ -106,6 +104,9 @@ impl ColorTest {
|
||||
show_color(ui, vertex_color, color_size);
|
||||
ui.label(" vertex color =");
|
||||
});
|
||||
|
||||
ui.spacing_mut().item_spacing.y = 0.0; // No spacing between gradients
|
||||
|
||||
{
|
||||
let g = Gradient::one_color(ground_truth);
|
||||
self.vertex_gradient(ui, "Ground truth (vertices)", WHITE, &g);
|
||||
@@ -129,6 +130,34 @@ impl ColorTest {
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.label("Test that blending is done in gamma space:");
|
||||
ui.scope(|ui| {
|
||||
let background = Color32::from_rgb(200, 60, 10);
|
||||
let foreground = Color32::from_rgba_unmultiplied(108, 65, 200, 82);
|
||||
let ground_truth = background.blend(foreground);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let color_size = ui.spacing().interact_size;
|
||||
ui.label("Background:");
|
||||
show_color(ui, background, color_size);
|
||||
ui.label(", foreground: ");
|
||||
show_color(ui, foreground, color_size);
|
||||
});
|
||||
ui.spacing_mut().item_spacing.y = 0.0; // No spacing between gradients
|
||||
{
|
||||
let g = Gradient::one_color(ground_truth);
|
||||
self.vertex_gradient(ui, "Ground truth (vertices)", WHITE, &g);
|
||||
self.tex_gradient(ui, "Ground truth (texture)", WHITE, &g);
|
||||
}
|
||||
{
|
||||
let g = Gradient::one_color(foreground);
|
||||
self.vertex_gradient(ui, "Vertex blending", background, &g);
|
||||
self.tex_gradient(ui, "Texture blending", background, &g);
|
||||
}
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
// TODO(emilk): test color multiplication (image tint),
|
||||
// to make sure vertex and texture color multiplication is done in linear space.
|
||||
|
||||
@@ -278,7 +307,10 @@ fn vertex_gradient(ui: &mut Ui, bg_fill: Color32, gradient: &Gradient) -> Respon
|
||||
}
|
||||
{
|
||||
let n = gradient.0.len();
|
||||
assert!(n >= 2);
|
||||
assert!(
|
||||
n >= 2,
|
||||
"A gradient must have at least two colors, but this had {n}"
|
||||
);
|
||||
let mut mesh = Mesh::default();
|
||||
for (i, &color) in gradient.0.iter().enumerate() {
|
||||
let t = i as f32 / (n as f32 - 1.0);
|
||||
@@ -452,7 +484,12 @@ fn pixel_test_strokes(ui: &mut Ui) {
|
||||
Pos2::new(cursor_pixel.x, cursor_pixel.y),
|
||||
Vec2::splat(size as f32),
|
||||
);
|
||||
painter.rect_stroke(rect_points / pixels_per_point, 0.0, stroke);
|
||||
painter.rect_stroke(
|
||||
rect_points / pixels_per_point,
|
||||
0.0,
|
||||
stroke,
|
||||
egui::StrokeKind::Outside,
|
||||
);
|
||||
cursor_pixel.x += (1 + size) as f32 + thickness_pixels * 2.0;
|
||||
}
|
||||
}
|
||||
@@ -560,8 +597,14 @@ fn blending_and_feathering_test(ui: &mut Ui) {
|
||||
}
|
||||
|
||||
fn text_on_bg(ui: &mut egui::Ui, fg: Color32, bg: Color32) {
|
||||
assert!(fg.is_opaque());
|
||||
assert!(bg.is_opaque());
|
||||
assert!(
|
||||
fg.is_opaque(),
|
||||
"Foreground color must be opaque, but was: {fg:?}",
|
||||
);
|
||||
assert!(
|
||||
bg.is_opaque(),
|
||||
"Background color must be opaque, but was: {bg:?}",
|
||||
);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(
|
||||
@@ -683,10 +726,11 @@ fn mul_color_gamma(left: Color32, right: Color32) -> Color32 {
|
||||
mod tests {
|
||||
use crate::ColorTest;
|
||||
use egui_kittest::kittest::Queryable as _;
|
||||
use egui_kittest::SnapshotResults;
|
||||
|
||||
#[test]
|
||||
pub fn rendering_test() {
|
||||
let mut errors = vec![];
|
||||
let mut results = SnapshotResults::new();
|
||||
for dpi in [1.0, 1.25, 1.5, 1.75, 1.6666667, 2.0] {
|
||||
let mut color_test = ColorTest::default();
|
||||
let mut harness = egui_kittest::Harness::builder()
|
||||
@@ -703,12 +747,7 @@ mod tests {
|
||||
|
||||
harness.fit_contents();
|
||||
|
||||
let result = harness.try_snapshot(&format!("rendering_test/dpi_{dpi:.2}"));
|
||||
if let Err(err) = result {
|
||||
errors.push(err);
|
||||
}
|
||||
results.add(harness.try_snapshot(&format!("rendering_test/dpi_{dpi:.2}")));
|
||||
}
|
||||
|
||||
assert!(errors.is_empty(), "Errors: {errors:#?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cf83bead834ec8f88d74b32ae6331715e8c6df183e007e2a16004c019534a30f
|
||||
size 31810
|
||||
oid sha256:cbe9f58cce2466360b4b93b03afaaee36711b3017ddff1b2b56bfe49ea91a076
|
||||
size 31306
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0a1099b85a1aaf20f3f1e091bc68259f811737feaefdfcc12acd067eca8f9117
|
||||
size 27083
|
||||
oid sha256:7224afc6e728f60c28c027bf4be03d1f598dc70977274bcd32b7398d11dd36c7
|
||||
size 26416
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6969c6da67ea6cc7ebbbd7a2cc1cb13d4720befe28126367cbf2b2679d037674
|
||||
size 82363
|
||||
oid sha256:5cfc3ee54a0e64fb8b72d55e9fc2079aa2517b200665684076d63b87c381cdb9
|
||||
size 78704
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:332c2af36873d8ccccb36c08fd2e475dc1f18454a3090a851c0889395d4f364f
|
||||
size 11518
|
||||
oid sha256:eb2bc4a38f20ed0f5fced36e8e56936bee328b24a0a45127d5d3739d40331cb7
|
||||
size 15514
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d0d0b1b4d2c4b624904250bc8d6870559f0179e3f7f2d6dc4a4ff256df356237
|
||||
size 20626
|
||||
oid sha256:49b08c1fb7878d8670d96de9f9791e2db5cf7206812da1d9102c4dd1758cb803
|
||||
size 25833
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d5fe6166bb8cd5fae0899957e968312b9229a79e423a5d154eda483a149b264d
|
||||
size 20831
|
||||
oid sha256:1e3e0330de3f68593329d2f36649127d5ac70109232c68f5c7ce310fa919fda5
|
||||
size 20348
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b71e1d109f90e017644dd20b9d84d81e3a6d5195afbd01ba86c85fa248c8b5c5
|
||||
size 10703
|
||||
oid sha256:2882a9842f51a7c3e9642a9a3d260407e1194648f47574608822a293bd3b1d56
|
||||
size 10465
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e3c9ba9064f44a4a14405f53316c1c602184caf16cb584d7c1f1912fe59f85ab
|
||||
size 135712
|
||||
oid sha256:af99bd49ceee6bbd96cc813cd96ac01f5c135ed7d94b8ff4010fa45feac5359a
|
||||
size 127703
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:097bd512dd71c17370f6e374312c19e7ab6845f151b3c3565f2a0790b61ee7ba
|
||||
size 24413
|
||||
oid sha256:a0b999914adab3d44c614bdf3b28abd268a4ff6162c5680b43035b3f71cb69bb
|
||||
size 23999
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2bdf54573a6b0d2fedd90314f91dd7de22dd13709e8dd699b37ef8935b6adda5
|
||||
size 17785
|
||||
oid sha256:57e09bcf48541af11e44ff07122f09640e0329db0c2bc7a6ecb406a3ece572ac
|
||||
size 17608
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d6328c86b70f3e24aaf87db200d13dfd0baa787dd8431e47621457937f8f5021
|
||||
size 22552
|
||||
oid sha256:641e5c7d4deccc8eb0374db4707dc356285a5c72186f9021d0d601c22bc5115f
|
||||
size 21894
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:afb57dc0bb8ff839138e36b8780136e0c8da55ff380832538fae0394143807c0
|
||||
size 65321
|
||||
oid sha256:880344367ed65f83898ceca4843b1b6259d1690242ced0d29ac8dc48100a8faa
|
||||
size 62956
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:403fc1d08d79168bc9f7a08ac7f363f2df916547f08311838edfda8a499a9b2d
|
||||
size 32879
|
||||
oid sha256:2f467edf4a84c8a98d96f168d843edb201ad2ee067dcd9d8d9ea214a02a41b1f
|
||||
size 32182
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cbd490a15377bdd4fd3272f3cd126cbc4fb9ed2f6f84edcbba03dd30dc3f3d99
|
||||
size 36780
|
||||
oid sha256:01705a1a49350278f524bbc5dbd47ae9da4b57ee7f6f34fb20186e1aa9b9f1d4
|
||||
size 35714
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c31b3998817e345b12b037d7f8bec7641f77d0c7eab7da9a746b7b51c9efc8fb
|
||||
size 17531
|
||||
oid sha256:03cb424100e99a141daeacc78036c4334d74cace3fae19bb878565ccda68457d
|
||||
size 17448
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4e8963c3ecd0e74fe9641d87101742a0d45c82a582d70e30eb36bc835f5aac06
|
||||
size 25330
|
||||
oid sha256:dfa05d7f8c36b51c054253fbe8483c38637a12dde4a9d6051b226680820db319
|
||||
size 25097
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:88b3a50b481942630b5804c60227f23b719dc7e3eb6dbe432c2448cb69332def
|
||||
size 262141
|
||||
oid sha256:df1e4a1e355100056713e751a8979d4201d0e4aab5513ba2f7a3e4852e1347dd
|
||||
size 264340
|
||||
|
||||
3
crates/egui_demo_lib/tests/snapshots/demos/Popups.png
Normal file
3
crates/egui_demo_lib/tests/snapshots/demos/Popups.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6ed78a559488474487c0a434a941e434b22354e4374d13059076d76da93bc609
|
||||
size 57051
|
||||
3
crates/egui_demo_lib/tests/snapshots/demos/Scene.png
Normal file
3
crates/egui_demo_lib/tests/snapshots/demos/Scene.png
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b4bf35ad4ce01122de5bc0830018044fd70f116938293fbeb72a2278de0bbb22
|
||||
size 35068
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4d5d628b54b28eccac0d9ef21bbdabace0cdf507898707956943e2259df846ca
|
||||
size 23741
|
||||
oid sha256:63f5c3be15164e6f008fb09b4ff37eff2af0ab361de28d1994d595789c379df5
|
||||
size 23205
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:8763a8f8b4242cbe589cd3282dc5f4b32a84b4e0416fb8072dfefb7879a5d4f6
|
||||
size 187982
|
||||
oid sha256:b236fe02f6cd52041359cf4b1a00e9812b95560353ce5df4fa6cb20fdbb45307
|
||||
size 179653
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3d61088bf1f992467e8396ac89407fa158d6f44e2d2d196778247f3ff18cb893
|
||||
size 119759
|
||||
oid sha256:1579351658875af48ad9aafeb08d928d83f1bda42bf092fdcceecd0aa6730e26
|
||||
size 115313
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:79ebaf9cccd19da2923507b5a553a23edc67382ef59e4b71f01d3bd6cc913539
|
||||
size 25829
|
||||
oid sha256:eb7c844f6b745f66304ad036790a5121e4827fa91569b28ffa301794aecd0c66
|
||||
size 25592
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:06cbf13841e6ac5fbc57bdae6f5ad9d19718193c344420dedcc0e9d7ed2b8ba9
|
||||
size 71590
|
||||
oid sha256:13115759157beb57febcff4be6f1710340736108b520e9ad3efb04be3cedcf7b
|
||||
size 68767
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:35e66f211c0b30a684371b520c46dbe4f9d5b6835e053a4eb65f492dd66a9e6c
|
||||
size 67288
|
||||
oid sha256:e177888e10f357f1be8ad80f7a0a33c93798c1e7c43cfe382119eeb12f21279f
|
||||
size 64732
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fa9ee8631bfe433ee6fad1fb1651fd6b63e2fb3fbc5f321a5410f7266dc16d09
|
||||
size 21296
|
||||
oid sha256:e177e2631414784161a5556bdd1420ce8432f9859faede1a2e6f791a02814412
|
||||
size 20918
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6475702b1bf2c65fb5196928a8934ade647a6053d6902a836e3d69cb7920091e
|
||||
size 59874
|
||||
oid sha256:c49c489fe1bb00512c9d08e8d8454fce786744f4ebff0bfd27dac68b7e67b815
|
||||
size 62317
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9d8daaec0c58709298a4594b7b2aa935aa2de327e6b71bd7417c2ba3a6eb060c
|
||||
size 13020
|
||||
oid sha256:c4d6a15094eee5d96a8af5c44ea9d0c962d650ee9b867344c86d1229e526dcb5
|
||||
size 12822
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:29d8d859a8cb11e4b390d45c658ed8ff191c2e542909e12f2385c0cba62baa2d
|
||||
size 35109
|
||||
oid sha256:02abc0cbab97e572218f422f4b167957869d4e2b4b388355444c20148d998015
|
||||
size 35200
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:17217e600d8a85ec00ecb11f0e5fe69496b30bbf715cc86785cec6e18b8c2fa1
|
||||
size 48158
|
||||
oid sha256:e954bf915d562abc69269cd10a4df8fbd0e5603929e6446fefa694099e2494a4
|
||||
size 47542
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fac50d2327a9e5e99989dd81d5644db86031b91b9c5c68fc17b5ef53ae655048
|
||||
size 47970
|
||||
oid sha256:1c7bd1a65b6c33eff2fe17f7af2dd731a03658abc2419f8722c0e9395b26fdef
|
||||
size 47515
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e5e829257b742194742429be0efd326be17ff7f5b9b16f9df58df21e899320bd
|
||||
size 43963
|
||||
oid sha256:e89cd220a925150384b9f9987b178036ffacfe29cdb36ed688205524dbb731fd
|
||||
size 43803
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5da064332c669a860a92a34b101f23e28026d4f07948f7c3e9a40e611f5e284f
|
||||
size 43986
|
||||
oid sha256:b71da58f5c0178517f9e0cc97753a0a5d1653cc5d094b5a35ffe050499bcd569
|
||||
size 43679
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ed2a356452d792e32bea57f044da9d86da27fd8504826dd6b87618a53519ea6a
|
||||
size 522556
|
||||
oid sha256:5a1f0d0759458017127d93278b89278af20fdc57c7747652ac6554f24cc708f2
|
||||
size 552939
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0d04a5854528c6141f7def6f9229c51c6d2d4c87e2f656be4d149e7b2b852976
|
||||
size 729056
|
||||
oid sha256:7948ced20e3f62b8356fc978ca7b12f8522b7c15716409cc661c0ebd2f12047f
|
||||
size 770062
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e6e5c1a745e357faa7b98f7a2cd1ca139c4a14be154b9d21feb8030933acfdb7
|
||||
size 867552
|
||||
oid sha256:e6f394c2beb51d95edaf8c7ddc9ff62d3f95913ea88a3840245b6bacf8b850cc
|
||||
size 907997
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7d9f4a37541fd1a0754c1cb1f3a2d4a76f03d67ca4e5596c8e6982d691d29dea
|
||||
size 980286
|
||||
oid sha256:d31f018cdabf92966b5636d9aef7f11ef1a0383884867e819e7ec99c0474e872
|
||||
size 1025013
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4d1c99867202e16500146b7146a32fd83d70d60f5ac94aae4ca405a6377e4625
|
||||
size 1066559
|
||||
oid sha256:e18f7ddadd53e16d04f191268747f244c98d0ea0f4cb9c0ea299f5da7affbc58
|
||||
size 1139723
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:08fc1c89fd2d04aa12c75a1829dacfff090e322c65ad969648799833e1b072eb
|
||||
size 1235574
|
||||
oid sha256:09a49cb9da7269bec6eef30c46a0bc85df6538d6e31dc0d0ff2758dbee45f3d8
|
||||
size 1291804
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:aa7d25b097911f6b18308bab56d302e3dae9f8f9916f563d5703632a26eda260
|
||||
size 72501
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4ac90da596084a880487035b276177e98d711854143373d59860f01733b1c0cd
|
||||
size 45592
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e412d424aac7b9cbdfdb8e36bd598e6cbc77183da7733c94c5f20e70699b8b4a
|
||||
size 87263
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:222a32da21c69ee46e847e29fb05fd5e1d2de6bb7a22358549bc426f8243fdcb
|
||||
size 119671
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d42e11f50a9522dd5ae73e8f8336bfb01493751705055a63abea3f5258f7c9c1
|
||||
size 51626
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b567d4038fd73986c80d2bd12197a6df037fde043545993fa9fe4160d0af446c
|
||||
size 54829
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fbf40a1f56a6e280002719c6556fe477c93fa7fe88d398372ed36efaa1b83a62
|
||||
size 55282
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:33621731155ebb463fb01ea41ab20272885250efcd7d5c7683c10936b296e14d
|
||||
size 36446
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:186bd8a3146ad8f1977955e3f7fa593877ad1bf1e8376d32f446c67f36a2aafe
|
||||
size 36493
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b3dc1bf9a59007a6ad0fb66a345d6cf272bd8bdcd26b10dbf411c1280e62b6fc
|
||||
size 158285
|
||||
oid sha256:af75f773e9e4ad2615893babce5b99e7fd127c76dd0976ac8dc95307f38a59dc
|
||||
size 152854
|
||||
|
||||
Reference in New Issue
Block a user