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:
parent
481f44828c
commit
3c4223c6b1
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()`.
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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| {
|
||||||
|
|
|
||||||
|
|
@ -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 ---
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -1 +0,0 @@
|
||||||
Rust logo by Mozilla, from https://github.com/rust-lang/rust-artwork
|
|
||||||
Loading…
Reference in New Issue