mirror of
https://github.com/emilk/egui.git
synced 2026-08-31 13:50:04 -04:00
Merge branch 'lucas/atoms-preferred-size' into lucas/experiments/measure-widget-size
# Conflicts: # crates/egui/src/ui.rs # crates/egui/src/widgets/button.rs # crates/egui/src/widgets/label.rs # crates/egui_demo_lib/src/demo/popups.rs # crates/egui_extras/src/layout.rs # crates/epaint/src/text/text_layout_types.rs
This commit is contained in:
@@ -76,12 +76,12 @@ impl crate::View for CodeEditor {
|
||||
});
|
||||
});
|
||||
|
||||
let mut layouter = |ui: &egui::Ui, string: &str, wrap_width: f32| {
|
||||
let mut layouter = |ui: &egui::Ui, buf: &dyn egui::TextBuffer, wrap_width: f32| {
|
||||
let mut layout_job = egui_extras::syntax_highlighting::highlight(
|
||||
ui.ctx(),
|
||||
ui.style(),
|
||||
&theme,
|
||||
string,
|
||||
buf.as_str(),
|
||||
language,
|
||||
);
|
||||
layout_job.wrap.max_width = wrap_width;
|
||||
|
||||
@@ -105,7 +105,7 @@ impl crate::Demo for CodeExample {
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
use crate::View;
|
||||
use crate::View as _;
|
||||
egui::Window::new(self.name())
|
||||
.open(open)
|
||||
.min_width(375.0)
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::collections::BTreeSet;
|
||||
use super::About;
|
||||
use crate::is_mobile;
|
||||
use crate::Demo;
|
||||
use crate::View;
|
||||
use crate::View as _;
|
||||
use egui::containers::menu;
|
||||
use egui::style::StyleModifier;
|
||||
use egui::{Context, Modifiers, ScrollArea, Ui};
|
||||
@@ -13,6 +13,16 @@ struct DemoGroup {
|
||||
demos: Vec<Box<dyn Demo>>,
|
||||
}
|
||||
|
||||
impl std::ops::Add for DemoGroup {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
let mut demos = self.demos;
|
||||
demos.extend(other.demos);
|
||||
Self { demos }
|
||||
}
|
||||
}
|
||||
|
||||
impl DemoGroup {
|
||||
pub fn new(demos: Vec<Box<dyn Demo>>) -> Self {
|
||||
Self { demos }
|
||||
@@ -100,6 +110,7 @@ impl Default for DemoGroups {
|
||||
Box::<super::tests::InputTest>::default(),
|
||||
Box::<super::tests::LayoutTest>::default(),
|
||||
Box::<super::tests::ManualLayoutTest>::default(),
|
||||
Box::<super::tests::SvgTest>::default(),
|
||||
Box::<super::tests::TessellationTest>::default(),
|
||||
Box::<super::tests::WindowResizeTest>::default(),
|
||||
]),
|
||||
@@ -359,14 +370,19 @@ fn file_menu_button(ui: &mut Ui) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{demo::demo_app_windows::DemoGroups, Demo};
|
||||
use crate::{demo::demo_app_windows::DemoGroups, Demo as _};
|
||||
use egui::Vec2;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use egui_kittest::kittest::Queryable as _;
|
||||
use egui_kittest::{Harness, SnapshotOptions, SnapshotResults};
|
||||
|
||||
#[test]
|
||||
fn demos_should_match_snapshot() {
|
||||
let demos = DemoGroups::default().demos;
|
||||
let DemoGroups {
|
||||
demos,
|
||||
tests,
|
||||
about: _,
|
||||
} = DemoGroups::default();
|
||||
let demos = demos + tests;
|
||||
|
||||
let mut results = SnapshotResults::new();
|
||||
|
||||
@@ -376,13 +392,10 @@ mod tests {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove the emoji from the demo name
|
||||
let name = demo
|
||||
.name()
|
||||
.split_once(' ')
|
||||
.map_or(demo.name(), |(_, name)| name);
|
||||
let name = remove_leading_emoji(demo.name());
|
||||
|
||||
let mut harness = Harness::new(|ctx| {
|
||||
egui_extras::install_image_loaders(ctx);
|
||||
demo.show(ctx, &mut true);
|
||||
});
|
||||
|
||||
@@ -405,4 +418,13 @@ mod tests {
|
||||
results.add(harness.try_snapshot_options(&format!("demos/{name}"), &options));
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_leading_emoji(full_name: &str) -> &str {
|
||||
if let Some((start, name)) = full_name.split_once(' ') {
|
||||
if start.len() <= 4 && start.bytes().next().is_some_and(|byte| byte >= 128) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
full_name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ fn char_name(chr: char) -> String {
|
||||
}
|
||||
|
||||
fn special_char_name(chr: char) -> Option<&'static str> {
|
||||
#[allow(clippy::match_same_arms)] // many "flag"
|
||||
#[expect(clippy::match_same_arms)] // many "flag"
|
||||
match chr {
|
||||
// Special private-use-area extensions found in `emoji-icon-font.ttf`:
|
||||
// Private use area extensions:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use egui::{Frame, Label, RichText, Sense, UiBuilder, Widget};
|
||||
use egui::{Frame, Label, RichText, Sense, UiBuilder, Widget as _};
|
||||
|
||||
/// Showcase [`egui::Ui::response`].
|
||||
#[derive(PartialEq, Eq, Default)]
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::{Demo, View};
|
||||
|
||||
use egui::{
|
||||
vec2, Align, Checkbox, CollapsingHeader, Color32, Context, FontId, Resize, RichText, Sense,
|
||||
Slider, Stroke, TextFormat, TextStyle, Ui, Vec2, Window,
|
||||
vec2, Align, Align2, Checkbox, CollapsingHeader, Color32, ComboBox, Context, FontId, Resize,
|
||||
RichText, Sense, Slider, Stroke, TextFormat, TextStyle, Ui, Vec2, Window,
|
||||
};
|
||||
|
||||
/// Showcase some ui code
|
||||
@@ -16,6 +16,7 @@ pub struct MiscDemoWindow {
|
||||
custom_collapsing_header: CustomCollapsingHeader,
|
||||
tree: Tree,
|
||||
box_painting: BoxPainting,
|
||||
text_rotation: TextRotation,
|
||||
|
||||
dummy_bool: bool,
|
||||
dummy_usize: usize,
|
||||
@@ -32,6 +33,7 @@ impl Default for MiscDemoWindow {
|
||||
custom_collapsing_header: Default::default(),
|
||||
tree: Tree::demo(),
|
||||
box_painting: Default::default(),
|
||||
text_rotation: Default::default(),
|
||||
|
||||
dummy_bool: false,
|
||||
dummy_usize: 0,
|
||||
@@ -79,6 +81,10 @@ impl View for MiscDemoWindow {
|
||||
});
|
||||
});
|
||||
|
||||
CollapsingHeader::new("Text rotation")
|
||||
.default_open(false)
|
||||
.show(ui, |ui| self.text_rotation.ui(ui));
|
||||
|
||||
CollapsingHeader::new("Colors")
|
||||
.default_open(false)
|
||||
.show(ui, |ui| {
|
||||
@@ -730,3 +736,95 @@ fn text_layout_demo(ui: &mut Ui) {
|
||||
|
||||
ui.label(job);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
struct TextRotation {
|
||||
size: Vec2,
|
||||
angle: f32,
|
||||
align: egui::Align2,
|
||||
}
|
||||
|
||||
impl Default for TextRotation {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
size: vec2(200.0, 200.0),
|
||||
angle: 0.0,
|
||||
align: egui::Align2::LEFT_TOP,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextRotation {
|
||||
pub fn ui(&mut self, ui: &mut Ui) {
|
||||
ui.add(Slider::new(&mut self.angle, 0.0..=2.0 * std::f32::consts::PI).text("angle"));
|
||||
|
||||
let default_color = if ui.visuals().dark_mode {
|
||||
Color32::LIGHT_GRAY
|
||||
} else {
|
||||
Color32::DARK_GRAY
|
||||
};
|
||||
|
||||
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("anchor", "Anchor")
|
||||
.selected_text(aligns.iter().find(|(a, _)| *a == self.align).unwrap().1)
|
||||
.show_ui(ui, |ui| {
|
||||
for (align2, name) in &aligns {
|
||||
ui.selectable_value(&mut self.align, *align2, *name);
|
||||
}
|
||||
});
|
||||
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
let (response, painter) = ui.allocate_painter(self.size, Sense::empty());
|
||||
let rect = response.rect;
|
||||
|
||||
let start_pos = self.size / 2.0;
|
||||
|
||||
let s = ui.ctx().fonts(|f| {
|
||||
let mut t = egui::Shape::text(
|
||||
f,
|
||||
rect.min + start_pos,
|
||||
egui::Align2::LEFT_TOP,
|
||||
"sample_text",
|
||||
egui::FontId::new(12.0, egui::FontFamily::Proportional),
|
||||
default_color,
|
||||
);
|
||||
|
||||
if let egui::epaint::Shape::Text(ts) = &mut t {
|
||||
let new = ts.clone().with_angle_and_anchor(self.angle, self.align);
|
||||
*ts = new;
|
||||
};
|
||||
|
||||
t
|
||||
});
|
||||
|
||||
if let egui::epaint::Shape::Text(ts) = &s {
|
||||
let align_pt =
|
||||
rect.min + start_pos + self.align.pos_in_rect(&ts.galley.rect).to_vec2();
|
||||
painter.circle(align_pt, 2.0, Color32::RED, (0.0, Color32::RED));
|
||||
};
|
||||
|
||||
painter.rect(
|
||||
rect,
|
||||
0.0,
|
||||
default_color.gamma_multiply(0.3),
|
||||
(0.0, Color32::BLACK),
|
||||
egui::StrokeKind::Middle,
|
||||
);
|
||||
painter.add(s);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use egui::{ComboBox, Context, Id, Modal, ProgressBar, Ui, Widget, Window};
|
||||
use egui::{ComboBox, Context, Id, Modal, ProgressBar, Ui, Widget as _, Window};
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
@@ -163,10 +163,10 @@ impl crate::View for Modals {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::demo::modals::Modals;
|
||||
use crate::Demo;
|
||||
use crate::Demo as _;
|
||||
use egui::accesskit::Role;
|
||||
use egui::Key;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use egui_kittest::kittest::Queryable as _;
|
||||
use egui_kittest::{Harness, SnapshotResults};
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -2,7 +2,7 @@ use egui::{
|
||||
emath,
|
||||
epaint::{self, CubicBezierShape, PathShape, QuadraticBezierShape},
|
||||
pos2, Color32, Context, Frame, Grid, Pos2, Rect, Sense, Shape, Stroke, StrokeKind, Ui, Vec2,
|
||||
Widget, Window,
|
||||
Widget as _, Window,
|
||||
};
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
/// ``` ignore
|
||||
/// password_ui(ui, &mut my_password);
|
||||
/// ```
|
||||
#[allow(clippy::ptr_arg)] // false positive
|
||||
pub fn password_ui(ui: &mut egui::Ui, password: &mut String) -> egui::Response {
|
||||
// This widget has its own state — show or hide password characters (`show_plaintext`).
|
||||
// In this case we use a simple `bool`, but you can also declare your own type.
|
||||
@@ -62,5 +61,5 @@ pub fn password(password: &mut String) -> impl egui::Widget + '_ {
|
||||
}
|
||||
|
||||
pub fn url_to_file_source_code() -> String {
|
||||
format!("https://github.com/emilk/egui/blob/master/{}", file!())
|
||||
format!("https://github.com/emilk/egui/blob/main/{}", file!())
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
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, Tooltip, Ui, UiBuilder,
|
||||
RectAlign, RichText, Tooltip, Ui, UiBuilder,
|
||||
};
|
||||
|
||||
/// Showcase [`Popup`].
|
||||
@@ -16,6 +17,7 @@ pub struct PopupsDemo {
|
||||
close_behavior: PopupCloseBehavior,
|
||||
popup_open: bool,
|
||||
checked: bool,
|
||||
color: egui::Color32,
|
||||
}
|
||||
|
||||
impl PopupsDemo {
|
||||
@@ -25,51 +27,20 @@ impl PopupsDemo {
|
||||
.gap(self.gap)
|
||||
.close_behavior(self.close_behavior)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PopupsDemo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
align4: RectAlign::default(),
|
||||
gap: 4.0,
|
||||
close_behavior: PopupCloseBehavior::CloseOnClick,
|
||||
popup_open: false,
|
||||
checked: false,
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn nested_menus(ui: &mut egui::Ui, checked: &mut bool) {
|
||||
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| nested_menus(ui, checked));
|
||||
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));
|
||||
|
||||
// if ui.button(if *checked { "short" } else { "Very long text for this item that should be wrapped" }).clicked() {
|
||||
// *checked = !*checked;
|
||||
@@ -111,11 +82,62 @@ fn nested_menus(ui: &mut egui::Ui, checked: &mut bool) {
|
||||
SubMenuButton::new("Always CloseOnClickOutside")
|
||||
.config(MenuConfig::new().close_behavior(PopupCloseBehavior::CloseOnClickOutside))
|
||||
.ui(ui, |ui| {
|
||||
ui.checkbox(checked, "Checkbox");
|
||||
if ui.button("Open…").clicked() {
|
||||
ui.close();
|
||||
}
|
||||
});
|
||||
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 {
|
||||
@@ -129,10 +151,10 @@ impl crate::View for PopupsDemo {
|
||||
.inner;
|
||||
|
||||
self.apply_options(Popup::menu(&response).id(Id::new("menu")))
|
||||
.show(|ui| nested_menus(ui, &mut self.checked));
|
||||
.show(|ui| self.nested_menus(ui));
|
||||
|
||||
self.apply_options(Popup::context_menu(&response).id(Id::new("context_menu")))
|
||||
.show(|ui| nested_menus(ui, &mut self.checked));
|
||||
.show(|ui| self.nested_menus(ui));
|
||||
|
||||
if self.popup_open {
|
||||
self.apply_options(Popup::from_response(&response).id(Id::new("popup")))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use egui::{Image, UserData, ViewportCommand, Widget};
|
||||
use egui::{Image, UserData, ViewportCommand, Widget as _};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Showcase [`ViewportCommand::Screenshot`].
|
||||
@@ -58,7 +58,7 @@ impl crate::View for Screenshot {
|
||||
None
|
||||
}
|
||||
})
|
||||
.last()
|
||||
.next_back()
|
||||
});
|
||||
|
||||
if let Some(image) = image {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use egui::{
|
||||
pos2, scroll_area::ScrollBarVisibility, Align, Align2, Color32, DragValue, NumExt, Rect,
|
||||
ScrollArea, Sense, Slider, TextStyle, TextWrapMode, Ui, Vec2, Widget,
|
||||
pos2, scroll_area::ScrollBarVisibility, Align, Align2, Color32, DragValue, NumExt as _, Rect,
|
||||
ScrollArea, Sense, Slider, TextStyle, TextWrapMode, Ui, Vec2, Widget as _,
|
||||
};
|
||||
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
|
||||
@@ -13,6 +13,7 @@ enum DemoType {
|
||||
pub struct TableDemo {
|
||||
demo: DemoType,
|
||||
striped: bool,
|
||||
overline: bool,
|
||||
resizable: bool,
|
||||
clickable: bool,
|
||||
num_rows: usize,
|
||||
@@ -28,6 +29,7 @@ impl Default for TableDemo {
|
||||
Self {
|
||||
demo: DemoType::Manual,
|
||||
striped: true,
|
||||
overline: true,
|
||||
resizable: true,
|
||||
clickable: true,
|
||||
num_rows: 10_000,
|
||||
@@ -65,6 +67,7 @@ impl crate::View for TableDemo {
|
||||
ui.vertical(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.checkbox(&mut self.striped, "Striped");
|
||||
ui.checkbox(&mut self.overline, "Overline some rows");
|
||||
ui.checkbox(&mut self.resizable, "Resizable columns");
|
||||
ui.checkbox(&mut self.clickable, "Clickable rows");
|
||||
});
|
||||
@@ -212,6 +215,7 @@ impl TableDemo {
|
||||
let row_height = if is_thick { 30.0 } else { 18.0 };
|
||||
body.row(row_height, |mut row| {
|
||||
row.set_selected(self.selection.contains(&row_index));
|
||||
row.set_overline(self.overline && row_index % 7 == 3);
|
||||
|
||||
row.col(|ui| {
|
||||
ui.label(row_index.to_string());
|
||||
@@ -247,6 +251,7 @@ impl TableDemo {
|
||||
};
|
||||
|
||||
row.set_selected(self.selection.contains(&row_index));
|
||||
row.set_overline(self.overline && row_index % 7 == 3);
|
||||
|
||||
row.col(|ui| {
|
||||
ui.label(row_index.to_string());
|
||||
@@ -280,6 +285,7 @@ impl TableDemo {
|
||||
};
|
||||
|
||||
row.set_selected(self.selection.contains(&row_index));
|
||||
row.set_overline(self.overline && row_index % 7 == 3);
|
||||
|
||||
row.col(|ui| {
|
||||
ui.label(row_index.to_string());
|
||||
|
||||
@@ -110,7 +110,7 @@ impl crate::View for GridTest {
|
||||
ui.end_row();
|
||||
|
||||
let mut dyn_text = String::from("O");
|
||||
dyn_text.extend(std::iter::repeat('h').take(self.text_length));
|
||||
dyn_text.extend(std::iter::repeat_n('h', self.text_length));
|
||||
ui.label(dyn_text);
|
||||
ui.label("Fifth row, second column");
|
||||
ui.end_row();
|
||||
|
||||
@@ -6,6 +6,7 @@ mod input_event_history;
|
||||
mod input_test;
|
||||
mod layout_test;
|
||||
mod manual_layout_test;
|
||||
mod svg_test;
|
||||
mod tessellation_test;
|
||||
mod window_resize_test;
|
||||
|
||||
@@ -17,5 +18,6 @@ pub use input_event_history::InputEventHistory;
|
||||
pub use input_test::InputTest;
|
||||
pub use layout_test::LayoutTest;
|
||||
pub use manual_layout_test::ManualLayoutTest;
|
||||
pub use svg_test::SvgTest;
|
||||
pub use tessellation_test::TessellationTest;
|
||||
pub use window_resize_test::WindowResizeTest;
|
||||
|
||||
42
crates/egui_demo_lib/src/demo/tests/svg_test.rs
Normal file
42
crates/egui_demo_lib/src/demo/tests/svg_test.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
pub struct SvgTest {
|
||||
color: egui::Color32,
|
||||
}
|
||||
|
||||
impl Default for SvgTest {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
color: egui::Color32::LIGHT_RED,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Demo for SvgTest {
|
||||
fn name(&self) -> &'static str {
|
||||
"SVG Test"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
|
||||
egui::Window::new(self.name()).open(open).show(ctx, |ui| {
|
||||
use crate::View as _;
|
||||
self.ui(ui);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::View for SvgTest {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
let Self { color } = self;
|
||||
ui.color_edit_button_srgba(color);
|
||||
let img_src = egui::include_image!("../../../data/peace.svg");
|
||||
|
||||
// First paint a small version, sized the same as the source…
|
||||
ui.add(
|
||||
egui::Image::new(img_src.clone())
|
||||
.fit_to_original_size(1.0)
|
||||
.tint(*color),
|
||||
);
|
||||
|
||||
// …then a big one, to make sure they are both crisp
|
||||
ui.add(egui::Image::new(img_src).tint(*color));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use egui::{
|
||||
emath::{GuiRounding, TSTransform},
|
||||
emath::{GuiRounding as _, TSTransform},
|
||||
epaint::{self, RectShape},
|
||||
vec2, Color32, Pos2, Rect, Sense, StrokeKind, Vec2,
|
||||
};
|
||||
|
||||
@@ -114,7 +114,7 @@ impl crate::View for TextEditDemo {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use egui::{accesskit, CentralPanel};
|
||||
use egui_kittest::kittest::{Key, Queryable};
|
||||
use egui_kittest::kittest::{Key, Queryable as _};
|
||||
use egui_kittest::Harness;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -76,7 +76,7 @@ pub fn toggle_ui(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
||||
}
|
||||
|
||||
/// Here is the same code again, but a bit more compact:
|
||||
#[allow(dead_code)]
|
||||
#[expect(dead_code)]
|
||||
fn toggle_ui_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
||||
let desired_size = ui.spacing().interact_size.y * egui::vec2(2.0, 1.0);
|
||||
let (rect, mut response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
|
||||
@@ -121,5 +121,5 @@ pub fn toggle(on: &mut bool) -> impl egui::Widget + '_ {
|
||||
}
|
||||
|
||||
pub fn url_to_file_source_code() -> String {
|
||||
format!("https://github.com/emilk/egui/blob/master/{}", file!())
|
||||
format!("https://github.com/emilk/egui/blob/main/{}", file!())
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ impl Default for WidgetGallery {
|
||||
}
|
||||
|
||||
impl WidgetGallery {
|
||||
#[allow(unused_mut)] // if not chrono
|
||||
#[allow(unused_mut, clippy::allow_attributes)] // if not chrono
|
||||
#[inline]
|
||||
pub fn with_date_button(mut self, _with_date_button: bool) -> Self {
|
||||
#[cfg(feature = "chrono")]
|
||||
@@ -308,7 +308,7 @@ fn doc_link_label_with_crate<'a>(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::View;
|
||||
use crate::View as _;
|
||||
use egui::Vec2;
|
||||
use egui_kittest::Harness;
|
||||
|
||||
@@ -322,7 +322,10 @@ mod tests {
|
||||
let mut harness = Harness::builder()
|
||||
.with_pixels_per_point(2.0)
|
||||
.with_size(Vec2::new(380.0, 550.0))
|
||||
.build_ui(|ui| demo.ui(ui));
|
||||
.build_ui(|ui| {
|
||||
egui_extras::install_image_loaders(ui.ctx());
|
||||
demo.ui(ui);
|
||||
});
|
||||
|
||||
harness.fit_contents();
|
||||
|
||||
|
||||
@@ -80,8 +80,8 @@ impl EasyMarkEditor {
|
||||
} = self;
|
||||
|
||||
let response = if self.highlight_editor {
|
||||
let mut layouter = |ui: &egui::Ui, easymark: &str, wrap_width: f32| {
|
||||
let mut layout_job = highlighter.highlight(ui.style(), easymark);
|
||||
let mut layouter = |ui: &egui::Ui, easymark: &dyn TextBuffer, wrap_width: f32| {
|
||||
let mut layout_job = highlighter.highlight(ui.style(), easymark.as_str());
|
||||
layout_job.wrap.max_width = wrap_width;
|
||||
ui.fonts(|f| f.layout_job(layout_job))
|
||||
};
|
||||
@@ -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();
|
||||
|
||||
@@ -237,7 +237,7 @@ Goals:
|
||||
2. easy to learn
|
||||
3. similar to markdown
|
||||
|
||||
[The reference parser](https://github.com/emilk/egui/blob/master/crates/egui_demo_lib/src/easy_mark/easy_mark_parser.rs) is \~250 lines of code, using only the Rust standard library. The parser uses no look-ahead or recursion.
|
||||
[The reference parser](https://github.com/emilk/egui/blob/main/crates/egui_demo_lib/src/easy_mark/easy_mark_parser.rs) is \~250 lines of code, using only the Rust standard library. The parser uses no look-ahead or recursion.
|
||||
|
||||
There is never more than one way to accomplish the same thing, and each special character is only used for one thing. For instance `*` is used for *strong* and `-` is used for bullet lists. There is no alternative way to specify the *strong* style or getting a bullet list.
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ macro_rules! egui_github_link_file {
|
||||
};
|
||||
($label: expr) => {
|
||||
egui::github_link_file!(
|
||||
"https://github.com/emilk/egui/blob/master/",
|
||||
"https://github.com/emilk/egui/blob/main/",
|
||||
egui::RichText::new($label).small()
|
||||
)
|
||||
};
|
||||
@@ -49,7 +49,7 @@ macro_rules! egui_github_link_file_line {
|
||||
};
|
||||
($label: expr) => {
|
||||
egui::github_link_file_line!(
|
||||
"https://github.com/emilk/egui/blob/master/",
|
||||
"https://github.com/emilk/egui/blob/main/",
|
||||
egui::RichText::new($label).small()
|
||||
)
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
@@ -387,10 +419,7 @@ impl TextureManager {
|
||||
let height = 1;
|
||||
ctx.load_texture(
|
||||
"color_test_gradient",
|
||||
epaint::ColorImage {
|
||||
size: [width, height],
|
||||
pixels,
|
||||
},
|
||||
epaint::ColorImage::new([width, height], pixels),
|
||||
TextureOptions::LINEAR,
|
||||
)
|
||||
})
|
||||
@@ -437,7 +466,7 @@ fn pixel_test_strokes(ui: &mut Ui) {
|
||||
let thickness_points = thickness_pixels / pixels_per_point;
|
||||
let num_squares = (pixels_per_point * 10.0).round().max(10.0) as u32;
|
||||
let size_pixels = vec2(ui.min_size().x, num_squares as f32 + thickness_pixels * 2.0);
|
||||
let size_points = size_pixels / pixels_per_point + Vec2::splat(2.0);
|
||||
let size_points = size_pixels / pixels_per_point;
|
||||
let (response, painter) = ui.allocate_painter(size_points, Sense::hover());
|
||||
|
||||
let mut cursor_pixel = Pos2::new(
|
||||
@@ -565,8 +594,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(
|
||||
|
||||
Reference in New Issue
Block a user