Support images with rounded corners (#3257)

* Add `Rect::ZERO`

* Add `Rounding::ZERO`

* Add `RectShape::new`

* Add `Image::rounding` to support images with rounded corners
This commit is contained in:
Emil Ernerfeldt 2023-08-15 09:29:30 +02:00 committed by GitHub
parent 481f44828c
commit 3c4223c6b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 302 additions and 123 deletions

View File

@ -555,13 +555,12 @@ impl CollapsingHeader {
let visuals = ui.style().interact_selectable(&header_response, selected); let visuals = ui.style().interact_selectable(&header_response, selected);
if ui.visuals().collapsing_header_frame || show_background { if ui.visuals().collapsing_header_frame || show_background {
ui.painter().add(epaint::RectShape { ui.painter().add(epaint::RectShape::new(
rect: header_response.rect.expand(visuals.expansion), header_response.rect.expand(visuals.expansion),
rounding: visuals.rounding, visuals.rounding,
fill: visuals.weak_bg_fill, visuals.weak_bg_fill,
stroke: visuals.bg_stroke, visuals.bg_stroke,
// stroke: Default::default(), ));
});
} }
if selected || selectable && (header_response.hovered() || header_response.has_focus()) if selected || selectable && (header_response.hovered() || header_response.has_focus())

View File

@ -383,12 +383,12 @@ fn button_frame(
ui.painter().set( ui.painter().set(
where_to_put_background, where_to_put_background,
epaint::RectShape { epaint::RectShape::new(
rect: outer_rect.expand(visuals.expansion), outer_rect.expand(visuals.expansion),
rounding: visuals.rounding, visuals.rounding,
fill: visuals.weak_bg_fill, visuals.weak_bg_fill,
stroke: visuals.bg_stroke, visuals.bg_stroke,
}, ),
); );
} }

View File

@ -235,12 +235,7 @@ impl Frame {
stroke, stroke,
} = *self; } = *self;
let frame_shape = Shape::Rect(epaint::RectShape { let frame_shape = Shape::Rect(epaint::RectShape::new(outer_rect, rounding, fill, stroke));
rect: outer_rect,
rounding,
fill,
stroke,
});
if shadow == Default::default() { if shadow == Default::default() {
frame_shape frame_shape

View File

@ -311,12 +311,7 @@ impl Painter {
fill_color: impl Into<Color32>, fill_color: impl Into<Color32>,
stroke: impl Into<Stroke>, stroke: impl Into<Stroke>,
) { ) {
self.add(RectShape { self.add(RectShape::new(rect, rounding, fill_color, stroke));
rect,
rounding: rounding.into(),
fill: fill_color.into(),
stroke: stroke.into(),
});
} }
pub fn rect_filled( pub fn rect_filled(
@ -325,12 +320,7 @@ impl Painter {
rounding: impl Into<Rounding>, rounding: impl Into<Rounding>,
fill_color: impl Into<Color32>, fill_color: impl Into<Color32>,
) { ) {
self.add(RectShape { self.add(RectShape::filled(rect, rounding, fill_color));
rect,
rounding: rounding.into(),
fill: fill_color.into(),
stroke: Default::default(),
});
} }
pub fn rect_stroke( pub fn rect_stroke(
@ -339,12 +329,7 @@ impl Painter {
rounding: impl Into<Rounding>, rounding: impl Into<Rounding>,
stroke: impl Into<Stroke>, stroke: impl Into<Stroke>,
) { ) {
self.add(RectShape { self.add(RectShape::stroke(rect, rounding, stroke));
rect,
rounding: rounding.into(),
fill: Default::default(),
stroke: stroke.into(),
});
} }
/// Show an arrow starting at `origin` and going in the direction of `vec`, with the length `vec.length()`. /// Show an arrow starting at `origin` and going in the direction of `vec`, with the length `vec.length()`.

View File

@ -318,12 +318,12 @@ impl<'a> Widget for Checkbox<'a> {
// let visuals = ui.style().interact_selectable(&response, *checked); // too colorful // let visuals = ui.style().interact_selectable(&response, *checked); // too colorful
let visuals = ui.style().interact(&response); let visuals = ui.style().interact(&response);
let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect); let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect);
ui.painter().add(epaint::RectShape { ui.painter().add(epaint::RectShape::new(
rect: big_icon_rect.expand(visuals.expansion), big_icon_rect.expand(visuals.expansion),
rounding: visuals.rounding, visuals.rounding,
fill: visuals.bg_fill, visuals.bg_fill,
stroke: visuals.bg_stroke, visuals.bg_stroke,
}); ));
if *checked { if *checked {
// Check mark: // Check mark:
@ -535,7 +535,7 @@ impl Widget for ImageButton {
let selection = ui.visuals().selection; let selection = ui.visuals().selection;
( (
Vec2::ZERO, Vec2::ZERO,
Rounding::none(), Rounding::ZERO,
selection.bg_fill, selection.bg_fill,
selection.stroke, selection.stroke,
) )
@ -552,6 +552,8 @@ impl Widget for ImageButton {
Default::default() Default::default()
}; };
let image = image.rounding(rounding); // apply rounding to the image
// Draw frame background (for transparent images): // Draw frame background (for transparent images):
ui.painter() ui.painter()
.rect_filled(rect.expand2(expansion), rounding, fill); .rect_filled(rect.expand2(expansion), rounding, fill);

View File

@ -42,6 +42,7 @@ pub struct Image {
tint: Color32, tint: Color32,
sense: Sense, sense: Sense,
rotation: Option<(Rot2, Vec2)>, rotation: Option<(Rot2, Vec2)>,
rounding: Rounding,
} }
impl Image { impl Image {
@ -54,6 +55,7 @@ impl Image {
tint: Color32::WHITE, tint: Color32::WHITE,
sense: Sense::hover(), sense: Sense::hover(),
rotation: None, rotation: None,
rounding: Rounding::ZERO,
} }
} }
@ -89,8 +91,26 @@ impl Image {
/// Origin is a vector in normalized UV space ((0,0) in top-left, (1,1) bottom right). /// Origin is a vector in normalized UV space ((0,0) in top-left, (1,1) bottom right).
/// ///
/// To rotate about the center you can pass `Vec2::splat(0.5)` as the origin. /// To rotate about the center you can pass `Vec2::splat(0.5)` as the origin.
///
/// Due to limitations in the current implementation,
/// this will turn off rounding of the image.
pub fn rotate(mut self, angle: f32, origin: Vec2) -> Self { pub fn rotate(mut self, angle: f32, origin: Vec2) -> Self {
self.rotation = Some((Rot2::from_angle(angle), origin)); self.rotation = Some((Rot2::from_angle(angle), origin));
self.rounding = Rounding::ZERO; // incompatible with rotation
self
}
/// Round the corners of the image.
///
/// The default is no rounding ([`Rounding::ZERO`]).
///
/// Due to limitations in the current implementation,
/// this will turn off any rotation of the image.
pub fn rounding(mut self, rounding: impl Into<Rounding>) -> Self {
self.rounding = rounding.into();
if self.rounding != Rounding::ZERO {
self.rotation = None; // incompatible with rounding
}
self self
} }
} }
@ -111,6 +131,7 @@ impl Image {
tint, tint,
sense: _, sense: _,
rotation, rotation,
rounding,
} = self; } = self;
if *bg_fill != Default::default() { if *bg_fill != Default::default() {
@ -119,14 +140,27 @@ impl Image {
ui.painter().add(Shape::mesh(mesh)); ui.painter().add(Shape::mesh(mesh));
} }
{ if let Some((rot, origin)) = rotation {
// TODO(emilk): builder pattern for Mesh // TODO(emilk): implement this using `PathShape` (add texture support to it).
// This will also give us anti-aliasing of rotated images.
egui_assert!(
*rounding == Rounding::ZERO,
"Image had both rounding and rotation. Please pick only one"
);
let mut mesh = Mesh::with_texture(*texture_id); let mut mesh = Mesh::with_texture(*texture_id);
mesh.add_rect_with_uv(rect, *uv, *tint); mesh.add_rect_with_uv(rect, *uv, *tint);
if let Some((rot, origin)) = rotation { mesh.rotate(*rot, rect.min + *origin * *size);
mesh.rotate(*rot, rect.min + *origin * *size);
}
ui.painter().add(Shape::mesh(mesh)); ui.painter().add(Shape::mesh(mesh));
} else {
ui.painter().add(RectShape {
rect,
rounding: *rounding,
fill: *tint,
stroke: Stroke::NONE,
fill_texture_id: *texture_id,
uv: *uv,
});
} }
} }
} }

View File

@ -127,12 +127,7 @@ impl Bar {
}; };
let rect = transform.rect_from_values(&self.bounds_min(), &self.bounds_max()); let rect = transform.rect_from_values(&self.bounds_min(), &self.bounds_max());
let rect = Shape::Rect(RectShape { let rect = Shape::Rect(RectShape::new(rect, Rounding::ZERO, fill, stroke));
rect,
rounding: Rounding::none(),
fill,
stroke,
});
shapes.push(rect); shapes.push(rect);
} }

View File

@ -150,12 +150,7 @@ impl BoxElem {
&self.point_at(self.argument - self.box_width / 2.0, self.spread.quartile1), &self.point_at(self.argument - self.box_width / 2.0, self.spread.quartile1),
&self.point_at(self.argument + self.box_width / 2.0, self.spread.quartile3), &self.point_at(self.argument + self.box_width / 2.0, self.spread.quartile3),
); );
let rect = Shape::Rect(RectShape { let rect = Shape::Rect(RectShape::new(rect, Rounding::ZERO, fill, stroke));
rect,
rounding: Rounding::none(),
fill,
stroke,
});
shapes.push(rect); shapes.push(rect);
let line_between = |v1, v2| { let line_between = |v1, v2| {

View File

@ -864,12 +864,14 @@ impl Plot {
// Background // Background
if show_background { if show_background {
ui.painter().with_clip_rect(rect).add(epaint::RectShape { ui.painter()
rect, .with_clip_rect(rect)
rounding: Rounding::same(2.0), .add(epaint::RectShape::new(
fill: ui.visuals().extreme_bg_color, rect,
stroke: ui.visuals().widgets.noninteractive.bg_stroke, Rounding::same(2.0),
}); ui.visuals().extreme_bg_color,
ui.visuals().widgets.noninteractive.bg_stroke,
));
} }
// --- Legend --- // --- Legend ---

View File

@ -368,31 +368,27 @@ impl<'t> TextEdit<'t> {
let frame_rect = frame_rect.expand(visuals.expansion); let frame_rect = frame_rect.expand(visuals.expansion);
let shape = if is_mutable { let shape = if is_mutable {
if output.response.has_focus() { if output.response.has_focus() {
epaint::RectShape { epaint::RectShape::new(
rect: frame_rect, frame_rect,
rounding: visuals.rounding, visuals.rounding,
// fill: ui.visuals().selection.bg_fill, ui.visuals().extreme_bg_color,
fill: ui.visuals().extreme_bg_color, ui.visuals().selection.stroke,
stroke: ui.visuals().selection.stroke, )
}
} else { } else {
epaint::RectShape { epaint::RectShape::new(
rect: frame_rect, frame_rect,
rounding: visuals.rounding, visuals.rounding,
fill: ui.visuals().extreme_bg_color, ui.visuals().extreme_bg_color,
stroke: visuals.bg_stroke, // TODO(emilk): we want to show something here, or a text-edit field doesn't "pop". visuals.bg_stroke, // TODO(emilk): we want to show something here, or a text-edit field doesn't "pop".
} )
} }
} else { } else {
let visuals = &ui.style().visuals.widgets.inactive; let visuals = &ui.style().visuals.widgets.inactive;
epaint::RectShape { epaint::RectShape::stroke(
rect: frame_rect, frame_rect,
rounding: visuals.rounding, visuals.rounding,
// fill: ui.visuals().extreme_bg_color, visuals.bg_stroke, // TODO(emilk): we want to show something here, or a text-edit field doesn't "pop".
// fill: visuals.bg_fill, )
fill: Color32::TRANSPARENT,
stroke: visuals.bg_stroke, // TODO(emilk): we want to show something here, or a text-edit field doesn't "pop".
}
}; };
ui.painter().set(where_to_put_background, shape); ui.painter().set(where_to_put_background, shape);

View File

@ -70,12 +70,12 @@ impl FrameHistory {
let to_screen = emath::RectTransform::from_to(graph_rect, rect); let to_screen = emath::RectTransform::from_to(graph_rect, rect);
let mut shapes = Vec::with_capacity(3 + 2 * history.len()); let mut shapes = Vec::with_capacity(3 + 2 * history.len());
shapes.push(Shape::Rect(epaint::RectShape { shapes.push(Shape::Rect(epaint::RectShape::new(
rect, rect,
rounding: style.rounding, style.rounding,
fill: ui.visuals().extreme_bg_color, ui.visuals().extreme_bg_color,
stroke: ui.style().noninteractive().bg_stroke, ui.style().noninteractive().bg_stroke,
})); )));
let rect = rect.shrink(4.0); let rect = rect.shrink(4.0);
let color = ui.visuals().text_color(); let color = ui.visuals().text_color();

View File

@ -64,12 +64,7 @@ pub fn drop_target<R>(
ui.painter().set( ui.painter().set(
where_to_put_background, where_to_put_background,
epaint::RectShape { epaint::RectShape::new(rect, style.rounding, fill, stroke),
rounding: style.rounding,
fill,
stroke,
rect,
},
); );
InnerResponse::new(ret, response) InnerResponse::new(ret, response)

View File

@ -8,7 +8,7 @@ fn main() {
let mut egui_glium = egui_glium::EguiGlium::new(&display, &event_loop); let mut egui_glium = egui_glium::EguiGlium::new(&display, &event_loop);
let png_data = include_bytes!("../../../examples/retained_image/src/rust-logo-256x256.png"); let png_data = include_bytes!("../../../examples/retained_image/src/crab.png");
let image = load_glium_image(png_data); let image = load_glium_image(png_data);
let image_size = egui::vec2(image.width as f32, image.height as f32); let image_size = egui::vec2(image.width as f32, image.height as f32);
// Load to gpu memory // Load to gpu memory

View File

@ -58,6 +58,12 @@ impl Rect {
max: pos2(f32::NAN, f32::NAN), max: pos2(f32::NAN, f32::NAN),
}; };
/// A [`Rect`] filled with zeroes.
pub const ZERO: Self = Self {
min: Pos2::ZERO,
max: Pos2::ZERO,
};
#[inline(always)] #[inline(always)]
pub const fn from_min_max(min: Pos2, max: Pos2) -> Self { pub const fn from_min_max(min: Pos2, max: Pos2) -> Self {
Rect { min, max } Rect { min, max }

View File

@ -120,13 +120,13 @@ impl Mesh {
pub fn append_ref(&mut self, other: &Mesh) { pub fn append_ref(&mut self, other: &Mesh) {
crate::epaint_assert!(other.is_valid()); crate::epaint_assert!(other.is_valid());
if !self.is_empty() { if self.is_empty() {
self.texture_id = other.texture_id;
} else {
assert_eq!( assert_eq!(
self.texture_id, other.texture_id, self.texture_id, other.texture_id,
"Can't merge Mesh using different textures" "Can't merge Mesh using different textures"
); );
} else {
self.texture_id = other.texture_id;
} }
let index_offset = self.vertices.len() as u32; let index_offset = self.vertices.len() as u32;

View File

@ -282,6 +282,8 @@ impl Shape {
pub fn texture_id(&self) -> super::TextureId { pub fn texture_id(&self) -> super::TextureId {
if let Shape::Mesh(mesh) = self { if let Shape::Mesh(mesh) = self {
mesh.texture_id mesh.texture_id
} else if let Shape::Rect(rect_shape) = self {
rect_shape.fill_texture_id
} else { } else {
super::TextureId::default() super::TextureId::default()
} }
@ -406,6 +408,8 @@ pub struct PathShape {
/// Color and thickness of the line. /// Color and thickness of the line.
pub stroke: Stroke, pub stroke: Stroke,
// TODO(emilk): Add texture support either by supplying uv for each point,
// or by some transform from points to uv (e.g. a callback or a linear transform matrix).
} }
impl PathShape { impl PathShape {
@ -476,7 +480,7 @@ impl From<PathShape> for Shape {
pub struct RectShape { pub struct RectShape {
pub rect: Rect, pub rect: Rect,
/// How rounded the corners are. Use `Rounding::none()` for no rounding. /// How rounded the corners are. Use `Rounding::ZERO` for no rounding.
pub rounding: Rounding, pub rounding: Rounding,
/// How to fill the rectangle. /// How to fill the rectangle.
@ -484,9 +488,37 @@ pub struct RectShape {
/// The thickness and color of the outline. /// The thickness and color of the outline.
pub stroke: Stroke, pub stroke: Stroke,
/// If the rect should be filled with a texture, which one?
///
/// The texture is multiplied with [`Self::fill`].
pub fill_texture_id: TextureId,
/// What UV coordinates to use for the texture?
///
/// To display a texture, set [`Self::fill_texture_id`],
/// and set this to `Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0))`.
pub uv: Rect,
} }
impl RectShape { impl RectShape {
#[inline]
pub fn new(
rect: Rect,
rounding: impl Into<Rounding>,
fill_color: impl Into<Color32>,
stroke: impl Into<Stroke>,
) -> Self {
Self {
rect,
rounding: rounding.into(),
fill: fill_color.into(),
stroke: stroke.into(),
fill_texture_id: Default::default(),
uv: Rect::ZERO,
}
}
#[inline] #[inline]
pub fn filled( pub fn filled(
rect: Rect, rect: Rect,
@ -498,6 +530,8 @@ impl RectShape {
rounding: rounding.into(), rounding: rounding.into(),
fill: fill_color.into(), fill: fill_color.into(),
stroke: Default::default(), stroke: Default::default(),
fill_texture_id: Default::default(),
uv: Rect::ZERO,
} }
} }
@ -508,6 +542,8 @@ impl RectShape {
rounding: rounding.into(), rounding: rounding.into(),
fill: Default::default(), fill: Default::default(),
stroke: stroke.into(), stroke: stroke.into(),
fill_texture_id: Default::default(),
uv: Rect::ZERO,
} }
} }
@ -549,7 +585,7 @@ pub struct Rounding {
impl Default for Rounding { impl Default for Rounding {
#[inline] #[inline]
fn default() -> Self { fn default() -> Self {
Self::none() Self::ZERO
} }
} }
@ -566,6 +602,14 @@ impl From<f32> for Rounding {
} }
impl Rounding { impl Rounding {
/// No rounding on any corner.
pub const ZERO: Self = Self {
nw: 0.0,
ne: 0.0,
sw: 0.0,
se: 0.0,
};
#[inline] #[inline]
pub fn same(radius: f32) -> Self { pub fn same(radius: f32) -> Self {
Self { Self {
@ -577,6 +621,7 @@ impl Rounding {
} }
#[inline] #[inline]
#[deprecated = "Use Rounding::ZERO"]
pub fn none() -> Self { pub fn none() -> Self {
Self { Self {
nw: 0.0, nw: 0.0,

View File

@ -492,6 +492,20 @@ impl Path {
pub fn fill(&mut self, feathering: f32, color: Color32, out: &mut Mesh) { pub fn fill(&mut self, feathering: f32, color: Color32, out: &mut Mesh) {
fill_closed_path(feathering, &mut self.0, color, out); fill_closed_path(feathering, &mut self.0, color, out);
} }
/// Like [`Self::fill`] but with texturing.
///
/// The `uv_from_pos` is called for each vertex position.
pub fn fill_with_uv(
&mut self,
feathering: f32,
color: Color32,
texture_id: TextureId,
uv_from_pos: impl Fn(Pos2) -> Pos2,
out: &mut Mesh,
) {
fill_closed_path_with_uv(feathering, &mut self.0, color, texture_id, uv_from_pos, out);
}
} }
pub mod path { pub mod path {
@ -508,7 +522,7 @@ pub mod path {
let r = clamp_radius(rounding, rect); let r = clamp_radius(rounding, rect);
if r == Rounding::none() { if r == Rounding::ZERO {
let min = rect.min; let min = rect.min;
let max = rect.max; let max = rect.max;
path.reserve(4); path.reserve(4);
@ -728,6 +742,89 @@ fn fill_closed_path(feathering: f32, path: &mut [PathPoint], color: Color32, out
} }
} }
/// Like [`fill_closed_path`] but with texturing.
///
/// The `uv_from_pos` is called for each vertex position.
fn fill_closed_path_with_uv(
feathering: f32,
path: &mut [PathPoint],
color: Color32,
texture_id: TextureId,
uv_from_pos: impl Fn(Pos2) -> Pos2,
out: &mut Mesh,
) {
if color == Color32::TRANSPARENT {
return;
}
if out.is_empty() {
out.texture_id = texture_id;
} else {
assert_eq!(
out.texture_id, texture_id,
"Mixing different `texture_id` in the same "
);
}
let n = path.len() as u32;
if feathering > 0.0 {
if cw_signed_area(path) < 0.0 {
// Wrong winding order - fix:
path.reverse();
for point in path.iter_mut() {
point.normal = -point.normal;
}
}
out.reserve_triangles(3 * n as usize);
out.reserve_vertices(2 * n as usize);
let color_outer = Color32::TRANSPARENT;
let idx_inner = out.vertices.len() as u32;
let idx_outer = idx_inner + 1;
// The fill:
for i in 2..n {
out.add_triangle(idx_inner + 2 * (i - 1), idx_inner, idx_inner + 2 * i);
}
// The feathering:
let mut i0 = n - 1;
for i1 in 0..n {
let p1 = &path[i1 as usize];
let dm = 0.5 * feathering * p1.normal;
let pos = p1.pos - dm;
out.vertices.push(Vertex {
pos,
uv: uv_from_pos(pos),
color,
});
let pos = p1.pos + dm;
out.vertices.push(Vertex {
pos,
uv: uv_from_pos(pos),
color: color_outer,
});
out.add_triangle(idx_inner + i1 * 2, idx_inner + i0 * 2, idx_outer + 2 * i0);
out.add_triangle(idx_outer + i0 * 2, idx_outer + i1 * 2, idx_inner + 2 * i1);
i0 = i1;
}
} else {
out.reserve_triangles(n as usize);
let idx = out.vertices.len() as u32;
out.vertices.extend(path.iter().map(|p| Vertex {
pos: p.pos,
uv: uv_from_pos(p.pos),
color,
}));
for i in 2..n {
out.add_triangle(idx, idx + i - 1, idx + i);
}
}
}
/// Tessellate the given path as a stroke with thickness. /// Tessellate the given path as a stroke with thickness.
fn stroke_path( fn stroke_path(
feathering: f32, feathering: f32,
@ -1304,6 +1401,8 @@ impl Tessellator {
rounding, rounding,
fill, fill,
stroke, stroke,
fill_texture_id,
uv,
} = *rect; } = *rect;
if self.options.coarse_tessellation_culling if self.options.coarse_tessellation_culling
@ -1345,7 +1444,21 @@ impl Tessellator {
path.clear(); path.clear();
path::rounded_rectangle(&mut self.scratchpad_points, rect, rounding); path::rounded_rectangle(&mut self.scratchpad_points, rect, rounding);
path.add_line_loop(&self.scratchpad_points); path.add_line_loop(&self.scratchpad_points);
path.fill(self.feathering, fill, out);
if uv.is_positive() {
// Textured
let uv_from_pos = |p: Pos2| {
pos2(
remap(p.x, rect.x_range(), uv.x_range()),
remap(p.y, rect.y_range(), uv.y_range()),
)
};
path.fill_with_uv(self.feathering, fill, fill_texture_id, uv_from_pos, out);
} else {
// Untextured
path.fill(self.feathering, fill, out);
}
path.stroke_closed(self.feathering, stroke, out); path.stroke_closed(self.feathering, stroke, out);
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

View File

@ -6,7 +6,7 @@ use egui_extras::RetainedImage;
fn main() -> Result<(), eframe::Error> { fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let options = eframe::NativeOptions { let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(300.0, 900.0)), initial_window_size: Some(egui::vec2(400.0, 1000.0)),
..Default::default() ..Default::default()
}; };
eframe::run_native( eframe::run_native(
@ -18,48 +18,66 @@ fn main() -> Result<(), eframe::Error> {
struct MyApp { struct MyApp {
image: RetainedImage, image: RetainedImage,
rounding: f32,
tint: egui::Color32, tint: egui::Color32,
} }
impl Default for MyApp { impl Default for MyApp {
fn default() -> Self { fn default() -> Self {
Self { Self {
image: RetainedImage::from_image_bytes( // crab image is CC0, found on https://stocksnap.io/search/crab
"rust-logo-256x256.png", image: RetainedImage::from_image_bytes("crab.png", include_bytes!("crab.png")).unwrap(),
include_bytes!("rust-logo-256x256.png"), rounding: 32.0,
) tint: egui::Color32::from_rgb(100, 200, 200),
.unwrap(),
tint: egui::Color32::from_rgb(255, 0, 255),
} }
} }
} }
impl eframe::App for MyApp { impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
let Self {
image,
rounding,
tint,
} = self;
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("This is an image:"); ui.heading("This is an image:");
self.image.show(ui); image.show(ui);
ui.heading("This is a rotated image with a tint:"); ui.add_space(32.0);
ui.heading("This is a tinted image with rounded corners:");
ui.add( ui.add(
egui::Image::new(self.image.texture_id(ctx), self.image.size_vec2()) egui::Image::new(image.texture_id(ctx), image.size_vec2())
.rotate(45.0_f32.to_radians(), egui::Vec2::splat(0.5)) .tint(*tint)
.tint(self.tint), .rounding(*rounding),
); );
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("Tint:"); ui.label("Tint:");
egui::color_picker::color_edit_button_srgba( egui::color_picker::color_edit_button_srgba(
ui, ui,
&mut self.tint, tint,
egui::color_picker::Alpha::BlendOrAdditive, egui::color_picker::Alpha::BlendOrAdditive,
); );
ui.add_space(16.0);
ui.label("Rounding:");
ui.add(
egui::DragValue::new(rounding)
.speed(1.0)
.clamp_range(0.0..=0.5 * image.size_vec2().min_elem()),
);
}); });
ui.add_space(32.0);
ui.heading("This is an image you can click:"); ui.heading("This is an image you can click:");
ui.add(egui::ImageButton::new( ui.add(egui::ImageButton::new(
self.image.texture_id(ctx), image.texture_id(ctx),
self.image.size_vec2(), image.size_vec2(),
)); ));
}); });
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View File

@ -1 +0,0 @@
Rust logo by Mozilla, from https://github.com/rust-lang/rust-artwork