mirror of
https://github.com/emilk/egui.git
synced 2026-09-03 15:20:05 -04:00
Add tests for StyleProvider and ensure every property works (#8477)
Follow up to #8455 Based on #8485 --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
use emath::Vec2;
|
use emath::{Align2, Vec2};
|
||||||
use epaint::{Color32, Margin};
|
use epaint::{Color32, Margin};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -154,6 +154,7 @@ impl StyleProvider<TextEditStyle> for DefaultStyle {
|
|||||||
.apply_stroke_and_expansion_without_layout_shift(stroke, widget_visuals.expansion),
|
.apply_stroke_and_expansion_without_layout_shift(stroke, widget_visuals.expansion),
|
||||||
gap: style.spacing.icon_spacing,
|
gap: style.spacing.icon_spacing,
|
||||||
text_style: text,
|
text_style: text,
|
||||||
|
align2: Some(Align2::LEFT_TOP),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
hint_text_color: style.visuals.weak_text_color(),
|
hint_text_color: style.visuals.weak_text_color(),
|
||||||
|
|||||||
@@ -155,6 +155,8 @@ impl WidgetStyle for TextEditStyle {}
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CheckboxStyle {
|
pub struct CheckboxStyle {
|
||||||
/// Style of the checkbox's atom layout.
|
/// Style of the checkbox's atom layout.
|
||||||
|
///
|
||||||
|
/// [`AtomLayoutStyle::align2`] vertical align has no effect; the checkbox is always centered.
|
||||||
pub atom_layout: AtomLayoutStyle,
|
pub atom_layout: AtomLayoutStyle,
|
||||||
|
|
||||||
/// Checkbox size
|
/// Checkbox size
|
||||||
|
|||||||
@@ -605,8 +605,7 @@ impl Widget for DragValue<'_> {
|
|||||||
TextEdit::singleline(&mut value_text)
|
TextEdit::singleline(&mut value_text)
|
||||||
.with_classes(classes)
|
.with_classes(classes)
|
||||||
.clip_text(false)
|
.clip_text(false)
|
||||||
.horizontal_align(ui.layout().horizontal_align())
|
.align(ui.layout().align2())
|
||||||
.vertical_align(ui.layout().vertical_align())
|
|
||||||
.min_size(min_size.unwrap_or_else(|| ui.spacing().interact_size))
|
.min_size(min_size.unwrap_or_else(|| ui.spacing().interact_size))
|
||||||
.id(id)
|
.id(id)
|
||||||
.desired_width(
|
.desired_width(
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ pub struct TextEdit<'t> {
|
|||||||
event_filter: EventFilter,
|
event_filter: EventFilter,
|
||||||
cursor_at_end: bool,
|
cursor_at_end: bool,
|
||||||
min_size: Vec2,
|
min_size: Vec2,
|
||||||
align: Align2,
|
align: Option<Align2>,
|
||||||
clip_text: bool,
|
clip_text: bool,
|
||||||
char_limit: usize,
|
char_limit: usize,
|
||||||
return_key: Option<KeyboardShortcut>,
|
return_key: Option<KeyboardShortcut>,
|
||||||
@@ -153,7 +153,7 @@ impl<'t> TextEdit<'t> {
|
|||||||
},
|
},
|
||||||
cursor_at_end: true,
|
cursor_at_end: true,
|
||||||
min_size: Vec2::ZERO,
|
min_size: Vec2::ZERO,
|
||||||
align: Align2::LEFT_TOP,
|
align: None,
|
||||||
clip_text: false,
|
clip_text: false,
|
||||||
char_limit: usize::MAX,
|
char_limit: usize::MAX,
|
||||||
return_key: Some(KeyboardShortcut::new(Modifiers::NONE, Key::Enter)),
|
return_key: Some(KeyboardShortcut::new(Modifiers::NONE, Key::Enter)),
|
||||||
@@ -382,17 +382,26 @@ impl<'t> TextEdit<'t> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the align of the inner text.
|
||||||
|
#[inline]
|
||||||
|
pub fn align(mut self, align: Align2) -> Self {
|
||||||
|
self.align = Some(align);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the horizontal align of the inner text.
|
/// Set the horizontal align of the inner text.
|
||||||
|
#[deprecated = "Use `align` instead"]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn horizontal_align(mut self, align: Align) -> Self {
|
pub fn horizontal_align(mut self, align: Align) -> Self {
|
||||||
self.align.0[0] = align;
|
self.align.get_or_insert(Align2::LEFT_TOP).set_x(align);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the vertical align of the inner text.
|
/// Set the vertical align of the inner text.
|
||||||
|
#[deprecated = "Use `align` instead"]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn vertical_align(mut self, align: Align) -> Self {
|
pub fn vertical_align(mut self, align: Align) -> Self {
|
||||||
self.align.0[1] = align;
|
self.align.get_or_insert(Align2::LEFT_TOP).set_y(align);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,6 +510,10 @@ impl TextEdit<'_> {
|
|||||||
// the same way it does for a button.
|
// the same way it does for a button.
|
||||||
let min_size = min_size.at_least(atom_layout_style.min_size);
|
let min_size = min_size.at_least(atom_layout_style.min_size);
|
||||||
|
|
||||||
|
let align = align
|
||||||
|
.or(atom_layout_style.align2)
|
||||||
|
.unwrap_or_else(|| ui.layout().align2());
|
||||||
|
|
||||||
let text_color = text_color
|
let text_color = text_color
|
||||||
.or_else(|| ui.visuals().override_text_color)
|
.or_else(|| ui.visuals().override_text_color)
|
||||||
.unwrap_or(atom_layout_style.text_style.color);
|
.unwrap_or(atom_layout_style.text_style.color);
|
||||||
@@ -722,7 +735,7 @@ impl TextEdit<'_> {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.atom_grow(true)
|
.atom_grow(true)
|
||||||
.atom_align(self.align)
|
.atom_align(align)
|
||||||
.atom_id(inner_rect_id)
|
.atom_id(inner_rect_id)
|
||||||
.atom_shrink(should_shrink),
|
.atom_shrink(should_shrink),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -80,8 +80,7 @@ impl crate::View for TextEditDemo {
|
|||||||
egui::Atom::custom(clear_id, clear_size)
|
egui::Atom::custom(clear_id, clear_size)
|
||||||
.atom_align(Align2::new(Align::RIGHT, *valign)),
|
.atom_align(Align2::new(Align::RIGHT, *valign)),
|
||||||
)
|
)
|
||||||
.horizontal_align(*halign)
|
.align(Align2::new(*halign, *valign))
|
||||||
.vertical_align(*valign)
|
|
||||||
.show(ui);
|
.show(ui);
|
||||||
|
|
||||||
if let Some(rect) = output.response.rect(clear_id)
|
if let Some(rect) = output.response.rect(clear_id)
|
||||||
|
|||||||
@@ -784,7 +784,7 @@ pub fn textedit_hint_text_should_follow_text_alignment() {
|
|||||||
egui::TextEdit::singleline(&mut input)
|
egui::TextEdit::singleline(&mut input)
|
||||||
.hint_text("Hint")
|
.hint_text("Hint")
|
||||||
.desired_width(200.0)
|
.desired_width(200.0)
|
||||||
.horizontal_align(egui::Align::Center),
|
.align(egui::Align2::CENTER_TOP),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
harness.run();
|
harness.run();
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ use egui::epaint::Shape;
|
|||||||
use egui::style::ScrollAnimation;
|
use egui::style::ScrollAnimation;
|
||||||
use egui::text::{LayoutJob, TextWrapping};
|
use egui::text::{LayoutJob, TextWrapping};
|
||||||
use egui::{
|
use egui::{
|
||||||
Align, Button, Color32, FontFamily, FontId, Image, Label, Layout, Rect, RichText, Sense,
|
Align, Align2, Button, Color32, FontFamily, FontId, Image, Label, Layout, Rect, RichText,
|
||||||
TextBuffer, TextFormat, TextWrapMode, Ui, Vec2, include_image, vec2,
|
Sense, TextBuffer, TextFormat, TextWrapMode, Ui, Vec2, include_image, vec2,
|
||||||
};
|
};
|
||||||
use egui::{Pos2, ScrollArea};
|
use egui::{Pos2, ScrollArea};
|
||||||
use egui_kittest::Harness;
|
use egui_kittest::Harness;
|
||||||
@@ -131,8 +131,7 @@ fn text_edit_halign() {
|
|||||||
"{widget_alignment:?}\n+\n{text_alignment:?}",
|
"{widget_alignment:?}\n+\n{text_alignment:?}",
|
||||||
))
|
))
|
||||||
.layouter(&mut layouter(text_alignment))
|
.layouter(&mut layouter(text_alignment))
|
||||||
.vertical_align(widget_alignment)
|
.align(Align2::new(widget_alignment, widget_alignment)),
|
||||||
.horizontal_align(widget_alignment),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
3
tests/egui_tests/tests/snapshots/text_edit_colors.png
Normal file
3
tests/egui_tests/tests/snapshots/text_edit_colors.png
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:36d4132bf9f65b57e8a87aea265e770ada3b1a9109c7e6902cf94b12054d013b
|
||||||
|
size 6831
|
||||||
332
tests/egui_tests/tests/test_style_provider.rs
Normal file
332
tests/egui_tests/tests/test_style_provider.rs
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
use core::fmt::Debug;
|
||||||
|
use egui::theme::{DefaultStyle, StyleProvider};
|
||||||
|
use egui::widget_style::{
|
||||||
|
AtomLayoutStyle, ButtonStyle, CheckboxStyle, SeparatorStyle, StyleArgs, TextEditStyle,
|
||||||
|
TextVisuals,
|
||||||
|
};
|
||||||
|
use egui::{
|
||||||
|
Align, Align2, Atom, AtomExt as _, Button, Checkbox, Color32, CornerRadius, FontFamily, FontId,
|
||||||
|
Frame, Margin, Separator, Stroke, TextEdit, Ui, Vec2, include_image,
|
||||||
|
};
|
||||||
|
use egui_kittest::Harness;
|
||||||
|
|
||||||
|
struct VariantHandle {
|
||||||
|
calls: usize,
|
||||||
|
return_b_for: Option<usize>,
|
||||||
|
fail_for: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VariantHandle {
|
||||||
|
#[track_caller]
|
||||||
|
fn get<I: Debug>(&mut self, a: I, b: I) -> I {
|
||||||
|
assert_ne!(
|
||||||
|
self.fail_for,
|
||||||
|
Some(self.calls),
|
||||||
|
"Switching from {a:?} to {b:?} lead to no meaningful change."
|
||||||
|
);
|
||||||
|
let res = if self.return_b_for == Some(self.calls) {
|
||||||
|
b
|
||||||
|
} else {
|
||||||
|
a
|
||||||
|
};
|
||||||
|
self.calls += 1;
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_variants<Variant: Clone, Comparison: PartialEq>(
|
||||||
|
make_variant: impl Fn(&mut VariantHandle) -> Variant,
|
||||||
|
mut render_variant: impl FnMut(Variant, bool) -> Comparison,
|
||||||
|
) {
|
||||||
|
let mut init_handle = VariantHandle {
|
||||||
|
calls: 0,
|
||||||
|
return_b_for: None,
|
||||||
|
fail_for: None,
|
||||||
|
};
|
||||||
|
let base_variant = make_variant(&mut init_handle);
|
||||||
|
let variant_count = init_handle.calls;
|
||||||
|
|
||||||
|
let base_image = render_variant(base_variant, false);
|
||||||
|
|
||||||
|
for i in 0..variant_count {
|
||||||
|
let mut test_handle = VariantHandle {
|
||||||
|
calls: 0,
|
||||||
|
fail_for: None,
|
||||||
|
return_b_for: Some(i),
|
||||||
|
};
|
||||||
|
let test_variant = make_variant(&mut test_handle);
|
||||||
|
let test_image = render_variant(test_variant.clone(), false);
|
||||||
|
if test_image == base_image {
|
||||||
|
// Open the snapshot so we can see what the variant looks like.
|
||||||
|
render_variant(test_variant, true);
|
||||||
|
|
||||||
|
let mut fail_handle = VariantHandle {
|
||||||
|
calls: 0,
|
||||||
|
fail_for: Some(i),
|
||||||
|
return_b_for: None,
|
||||||
|
};
|
||||||
|
make_variant(&mut fail_handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper that walks all fields of a struct, swapping each value one after the other, ensuring
|
||||||
|
/// that any value change also results in a visual change of the UI.
|
||||||
|
fn test_harness_variants<Variant: Clone>(
|
||||||
|
size: Vec2,
|
||||||
|
make_variant: impl Fn(&mut VariantHandle) -> Variant,
|
||||||
|
mut contents: impl FnMut(&mut Ui, Variant),
|
||||||
|
) {
|
||||||
|
test_variants(make_variant, |variant, failure| {
|
||||||
|
let mut harness = Harness::builder().with_size(size).build_ui(|ui| {
|
||||||
|
contents(ui, variant.clone());
|
||||||
|
});
|
||||||
|
// Run a few frames so images have time to load.
|
||||||
|
harness.run();
|
||||||
|
if failure {
|
||||||
|
// Helpful to see what's going on:
|
||||||
|
// harness.debug_open_snapshot();
|
||||||
|
}
|
||||||
|
harness.render().expect("Failed to render the harness")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FixedStyleProvider<T>(T);
|
||||||
|
|
||||||
|
impl<T: Clone> StyleProvider<T> for FixedStyleProvider<T> {
|
||||||
|
fn style(&mut self, _modifiers: &StyleArgs<'_>) -> T {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A small image atom, so [`AtomLayoutStyle::image_tint`] has something to tint.
|
||||||
|
fn image_atom() -> Atom<'static> {
|
||||||
|
include_image!("../../../crates/eframe/data/icon.png").atom_size(Vec2::splat(10.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame_variants(variant: &mut VariantHandle) -> Frame {
|
||||||
|
Frame {
|
||||||
|
inner_margin: Margin::same(variant.get(0, 4)),
|
||||||
|
fill: variant.get(Color32::GREEN, Color32::BLUE),
|
||||||
|
stroke: Stroke::new(
|
||||||
|
// Has to be more that 0.0 or color variant below will fail
|
||||||
|
variant.get(1.0, 2.0),
|
||||||
|
variant.get(Color32::RED, Color32::GREEN),
|
||||||
|
),
|
||||||
|
|
||||||
|
corner_radius: variant.get(CornerRadius::same(0), CornerRadius::same(4)),
|
||||||
|
outer_margin: Margin::same(variant.get(0, 4)),
|
||||||
|
shadow: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Which [`AtomLayoutStyle`] fields the widget under test honors.
|
||||||
|
///
|
||||||
|
/// A widget may ignore a field on purpose. Every `false` marks one of those, and says at
|
||||||
|
/// the call site why the widget ignores it.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct AtomLayoutFields {
|
||||||
|
align_y: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AtomLayoutFields {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { align_y: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `min_size` is deliberately bigger than the content of every widget here, so `align2` has
|
||||||
|
/// room to move that content around.
|
||||||
|
fn atom_layout_variants(
|
||||||
|
variant: &mut VariantHandle,
|
||||||
|
fields: AtomLayoutFields,
|
||||||
|
frame: Frame,
|
||||||
|
) -> AtomLayoutStyle {
|
||||||
|
AtomLayoutStyle {
|
||||||
|
align2: Some(Align2::new(
|
||||||
|
variant.get(Align::Min, Align::Max),
|
||||||
|
if fields.align_y {
|
||||||
|
variant.get(Align::Min, Align::Max)
|
||||||
|
} else {
|
||||||
|
Align::Min
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
min_size: Vec2::new(variant.get(100.0, 200.0), variant.get(40.0, 80.0)),
|
||||||
|
gap: variant.get(0.0, 10.0),
|
||||||
|
frame,
|
||||||
|
text_style: TextVisuals {
|
||||||
|
font_id: variant.get(
|
||||||
|
FontId::new(10.0, FontFamily::Proportional),
|
||||||
|
FontId::new(12.0, FontFamily::Monospace),
|
||||||
|
),
|
||||||
|
color: variant.get(Color32::WHITE, Color32::RED),
|
||||||
|
},
|
||||||
|
image_tint: variant.get(Color32::RED, Color32::GREEN),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ensure_all_button_style_args_used() {
|
||||||
|
test_harness_variants(
|
||||||
|
Vec2::new(300.0, 150.0),
|
||||||
|
|variant| {
|
||||||
|
let frame = frame_variants(variant);
|
||||||
|
ButtonStyle {
|
||||||
|
atom_layout: atom_layout_variants(variant, AtomLayoutFields::default(), frame),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|ui, variant| {
|
||||||
|
egui_extras::install_image_loaders(ui.ctx());
|
||||||
|
ui.replace_widget_theme(FixedStyleProvider(variant));
|
||||||
|
ui.add(Button::new((image_atom(), "Image Button")));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [`Checkbox`] paints its box as one rectangle, so only these [`Frame`] fields reach the
|
||||||
|
/// screen. `outer_margin`, `shadow` and three of the four `inner_margin` sides are dropped.
|
||||||
|
fn checkbox_frame_variants(variant: &mut VariantHandle) -> Frame {
|
||||||
|
Frame {
|
||||||
|
inner_margin: Margin::same(variant.get(0, 4)),
|
||||||
|
fill: variant.get(Color32::BLACK, Color32::YELLOW),
|
||||||
|
stroke: Stroke::new(
|
||||||
|
// Has to be more that 0.0 or the color variant below will fail
|
||||||
|
variant.get(1.0, 3.0),
|
||||||
|
variant.get(Color32::WHITE, Color32::RED),
|
||||||
|
),
|
||||||
|
corner_radius: variant.get(CornerRadius::same(0), CornerRadius::same(5)),
|
||||||
|
outer_margin: Margin::ZERO,
|
||||||
|
shadow: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ensure_all_checkbox_style_args_used() {
|
||||||
|
test_harness_variants(
|
||||||
|
Vec2::new(300.0, 150.0),
|
||||||
|
|variant| {
|
||||||
|
let frame = frame_variants(variant);
|
||||||
|
let fields = AtomLayoutFields {
|
||||||
|
// A `Checkbox` always centers its box and its label in the row, so the
|
||||||
|
// vertical align has no effect. This is by design, see `Checkbox::ui`.
|
||||||
|
align_y: false,
|
||||||
|
};
|
||||||
|
CheckboxStyle {
|
||||||
|
atom_layout: atom_layout_variants(variant, fields, frame),
|
||||||
|
checkbox_size: variant.get(14.0, 24.0),
|
||||||
|
check_size: variant.get(8.0, 13.0),
|
||||||
|
checkbox_frame: checkbox_frame_variants(variant),
|
||||||
|
check_stroke: Stroke::new(
|
||||||
|
// Has to be more that 0.0 or the color variant below will fail
|
||||||
|
variant.get(1.5, 3.0),
|
||||||
|
variant.get(Color32::WHITE, Color32::RED),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|ui, variant| {
|
||||||
|
egui_extras::install_image_loaders(ui.ctx());
|
||||||
|
ui.replace_widget_theme(FixedStyleProvider(variant));
|
||||||
|
// Checked, so `check_size` and `check_stroke` paint something.
|
||||||
|
let mut checked = true;
|
||||||
|
ui.add(Checkbox::new(&mut checked, (image_atom(), "On")));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ensure_all_separator_style_args_used() {
|
||||||
|
test_harness_variants(
|
||||||
|
Vec2::new(300.0, 100.0),
|
||||||
|
|variant| SeparatorStyle {
|
||||||
|
spacing: variant.get(6.0, 30.0),
|
||||||
|
stroke: Stroke::new(
|
||||||
|
// Has to be more than 0.0 or the color variant below will fail
|
||||||
|
variant.get(1.0, 4.0),
|
||||||
|
variant.get(Color32::RED, Color32::GREEN),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|ui, variant| {
|
||||||
|
ui.replace_widget_theme(FixedStyleProvider(variant));
|
||||||
|
ui.add(Separator::default());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ensure_all_text_edit_style_args_used() {
|
||||||
|
test_harness_variants(
|
||||||
|
Vec2::new(320.0, 260.0),
|
||||||
|
|variant| {
|
||||||
|
let frame = frame_variants(variant);
|
||||||
|
TextEditStyle {
|
||||||
|
atom_layout: atom_layout_variants(variant, AtomLayoutFields::default(), frame),
|
||||||
|
hint_text_color: variant.get(Color32::GRAY, Color32::YELLOW),
|
||||||
|
prefix_suffix_color: variant.get(Color32::BLACK, Color32::BLUE),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|ui, variant| {
|
||||||
|
egui_extras::install_image_loaders(ui.ctx());
|
||||||
|
ui.replace_widget_theme(FixedStyleProvider(variant));
|
||||||
|
|
||||||
|
// Make sure min_size can exceed this:
|
||||||
|
ui.spacing_mut().text_edit_width = 100.0;
|
||||||
|
|
||||||
|
// An empty field shows the hint text...
|
||||||
|
let mut empty = String::new();
|
||||||
|
ui.add(
|
||||||
|
TextEdit::singleline(&mut empty)
|
||||||
|
.hint_text("Hint")
|
||||||
|
.prefix((image_atom(), "$"))
|
||||||
|
.suffix(".00"),
|
||||||
|
);
|
||||||
|
|
||||||
|
// ...and a filled one shows `text_style.color`.
|
||||||
|
let mut filled = String::from("Text");
|
||||||
|
ui.add(
|
||||||
|
TextEdit::singleline(&mut filled)
|
||||||
|
.prefix((image_atom(), "$"))
|
||||||
|
.suffix(".00"),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CustomStyleProvider;
|
||||||
|
|
||||||
|
impl StyleProvider<TextEditStyle> for CustomStyleProvider {
|
||||||
|
fn style(&mut self, modifiers: &StyleArgs<'_>) -> TextEditStyle {
|
||||||
|
let mut default: TextEditStyle = DefaultStyle.style(modifiers);
|
||||||
|
|
||||||
|
default.hint_text_color = Color32::BLUE;
|
||||||
|
default.prefix_suffix_color = Color32::GREEN;
|
||||||
|
default.atom_layout.text_style.color = Color32::RED;
|
||||||
|
|
||||||
|
default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_edit_colors() {
|
||||||
|
let mut harness = Harness::new_ui(|ui| {
|
||||||
|
ui.add_widget_theme::<TextEditStyle>(CustomStyleProvider);
|
||||||
|
|
||||||
|
ui.label("The text should match the colors:");
|
||||||
|
|
||||||
|
ui.add(
|
||||||
|
TextEdit::singleline(&mut String::new())
|
||||||
|
.prefix("green")
|
||||||
|
.suffix("green")
|
||||||
|
.hint_text("blue"),
|
||||||
|
);
|
||||||
|
|
||||||
|
ui.add(
|
||||||
|
TextEdit::singleline(&mut "Red".to_owned())
|
||||||
|
.prefix("green")
|
||||||
|
.suffix("green")
|
||||||
|
.hint_text("blue"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
harness.fit_contents();
|
||||||
|
harness.snapshot("text_edit_colors");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user