diff --git a/CHANGELOG.md b/CHANGELOG.md index 713a2011fda..d06d5b67606 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w * Replaced `Style::body_text_style` with more generic `Style::text_styles` ([#1154](https://github.com/emilk/egui/pull/1154)). * `TextStyle` is no longer `Copy` ([#1154](https://github.com/emilk/egui/pull/1154)). * Replaced `TextEdit::text_style` with `TextEdit::font` ([#1154](https://github.com/emilk/egui/pull/1154)). +* Replaced `corner_radius: f32` with `rounding: Rounding`, allowing per-corner rounding settings ([#1206](https://github.com/emilk/egui/pull/1206)). * `Plot::highlight` now takes a `bool` argument ([#1159](https://github.com/emilk/egui/pull/1159)). * `ScrollArea::show` now returns a `ScrollAreaOutput`, so you might need to add `.inner` after the call to it ([#1166](https://github.com/emilk/egui/pull/1166)). diff --git a/egui/src/containers/collapsing_header.rs b/egui/src/containers/collapsing_header.rs index f922cf8d205..d0df08956e1 100644 --- a/egui/src/containers/collapsing_header.rs +++ b/egui/src/containers/collapsing_header.rs @@ -338,7 +338,7 @@ impl CollapsingHeader { if ui.visuals().collapsing_header_frame || self.show_background { ui.painter().add(epaint::RectShape { rect: header_response.rect.expand(visuals.expansion), - corner_radius: visuals.corner_radius, + rounding: visuals.rounding, fill: visuals.bg_fill, stroke: visuals.bg_stroke, // stroke: Default::default(), @@ -350,12 +350,8 @@ impl CollapsingHeader { { let rect = rect.expand(visuals.expansion); - ui.painter().rect( - rect, - visuals.corner_radius, - visuals.bg_fill, - visuals.bg_stroke, - ); + ui.painter() + .rect(rect, visuals.rounding, visuals.bg_fill, visuals.bg_stroke); } { diff --git a/egui/src/containers/combo_box.rs b/egui/src/containers/combo_box.rs index 8ec4d7aa404..14feb02c8fd 100644 --- a/egui/src/containers/combo_box.rs +++ b/egui/src/containers/combo_box.rs @@ -250,7 +250,7 @@ fn button_frame( where_to_put_background, epaint::RectShape { rect: outer_rect.expand(visuals.expansion), - corner_radius: visuals.corner_radius, + rounding: visuals.rounding, fill: visuals.bg_fill, stroke: visuals.bg_stroke, }, diff --git a/egui/src/containers/frame.rs b/egui/src/containers/frame.rs index 26dcf34e077..c5c5a61a047 100644 --- a/egui/src/containers/frame.rs +++ b/egui/src/containers/frame.rs @@ -9,7 +9,7 @@ use epaint::*; pub struct Frame { /// On each side pub margin: Vec2, - pub corner_radius: Rounding, + pub rounding: Rounding, pub shadow: Shadow, pub fill: Color32, pub stroke: Stroke, @@ -24,7 +24,7 @@ impl Frame { pub fn group(style: &Style) -> Self { Self { margin: Vec2::splat(6.0), // symmetric looks best in corners when nesting - corner_radius: style.visuals.widgets.noninteractive.corner_radius, + rounding: style.visuals.widgets.noninteractive.rounding, stroke: style.visuals.widgets.noninteractive.bg_stroke, ..Default::default() } @@ -33,7 +33,7 @@ impl Frame { pub(crate) fn side_top_panel(style: &Style) -> Self { Self { margin: Vec2::new(8.0, 2.0), - corner_radius: Rounding::none(), + rounding: Rounding::none(), fill: style.visuals.window_fill(), stroke: style.visuals.window_stroke(), ..Default::default() @@ -43,7 +43,7 @@ impl Frame { pub(crate) fn central_panel(style: &Style) -> Self { Self { margin: Vec2::new(8.0, 8.0), - corner_radius: Rounding::none(), + rounding: Rounding::none(), fill: style.visuals.window_fill(), stroke: Default::default(), ..Default::default() @@ -53,7 +53,7 @@ impl Frame { pub fn window(style: &Style) -> Self { Self { margin: style.spacing.window_padding, - corner_radius: style.visuals.window_corner_radius, + rounding: style.visuals.window_rounding, shadow: style.visuals.window_shadow, fill: style.visuals.window_fill(), stroke: style.visuals.window_stroke(), @@ -63,7 +63,7 @@ impl Frame { pub fn menu(style: &Style) -> Self { Self { margin: Vec2::splat(1.0), - corner_radius: style.visuals.widgets.noninteractive.corner_radius, + rounding: style.visuals.widgets.noninteractive.rounding, shadow: style.visuals.popup_shadow, fill: style.visuals.window_fill(), stroke: style.visuals.window_stroke(), @@ -73,7 +73,7 @@ impl Frame { pub fn popup(style: &Style) -> Self { Self { margin: style.spacing.window_padding, - corner_radius: style.visuals.widgets.noninteractive.corner_radius, + rounding: style.visuals.widgets.noninteractive.rounding, shadow: style.visuals.popup_shadow, fill: style.visuals.window_fill(), stroke: style.visuals.window_stroke(), @@ -84,7 +84,7 @@ impl Frame { pub fn dark_canvas(style: &Style) -> Self { Self { margin: Vec2::new(10.0, 10.0), - corner_radius: style.visuals.widgets.noninteractive.corner_radius, + rounding: style.visuals.widgets.noninteractive.rounding, fill: Color32::from_black_alpha(250), stroke: style.visuals.window_stroke(), ..Default::default() @@ -103,8 +103,8 @@ impl Frame { self } - pub fn corner_radius(mut self, corner_radius: impl Into) -> Self { - self.corner_radius = corner_radius.into(); + pub fn rounding(mut self, rounding: impl Into) -> Self { + self.rounding = rounding.into(); self } @@ -172,7 +172,7 @@ impl Frame { pub fn paint(&self, outer_rect: Rect) -> Shape { let Self { margin: _, - corner_radius, + rounding, shadow, fill, stroke, @@ -180,7 +180,7 @@ impl Frame { let frame_shape = Shape::Rect(epaint::RectShape { rect: outer_rect, - corner_radius, + rounding, fill, stroke, }); @@ -188,7 +188,7 @@ impl Frame { if shadow == Default::default() { frame_shape } else { - let shadow = shadow.tessellate(outer_rect, corner_radius); + let shadow = shadow.tessellate(outer_rect, rounding); let shadow = Shape::Mesh(shadow); Shape::Vec(vec![shadow, frame_shape]) } diff --git a/egui/src/containers/scroll_area.rs b/egui/src/containers/scroll_area.rs index fc2eaea26d7..d5c9e712c84 100644 --- a/egui/src/containers/scroll_area.rs +++ b/egui/src/containers/scroll_area.rs @@ -740,13 +740,13 @@ impl Prepared { ui.painter().add(epaint::Shape::rect_filled( outer_scroll_rect, - visuals.corner_radius, + visuals.rounding, ui.visuals().extreme_bg_color, )); ui.painter().add(epaint::Shape::rect_filled( handle_rect, - visuals.corner_radius, + visuals.rounding, visuals.bg_fill, )); } diff --git a/egui/src/containers/window.rs b/egui/src/containers/window.rs index c2794e7f1c1..9c439b26e5f 100644 --- a/egui/src/containers/window.rs +++ b/egui/src/containers/window.rs @@ -699,42 +699,62 @@ fn paint_frame_interaction( ) { use epaint::tessellator::path::add_circle_quadrant; - let cr = ui.visuals().window_corner_radius; + let rounding = ui.visuals().window_rounding; let Rect { min, max } = rect; let mut points = Vec::new(); if interaction.right && !interaction.bottom && !interaction.top { - points.push(pos2(max.x, min.y + cr.ne)); - points.push(pos2(max.x, max.y - cr.se)); + points.push(pos2(max.x, min.y + rounding.ne)); + points.push(pos2(max.x, max.y - rounding.se)); } if interaction.right && interaction.bottom { - points.push(pos2(max.x, min.y + cr.ne)); - points.push(pos2(max.x, max.y - cr.se)); - add_circle_quadrant(&mut points, pos2(max.x - cr.se, max.y - cr.se), cr.se, 0.0); + points.push(pos2(max.x, min.y + rounding.ne)); + points.push(pos2(max.x, max.y - rounding.se)); + add_circle_quadrant( + &mut points, + pos2(max.x - rounding.se, max.y - rounding.se), + rounding.se, + 0.0, + ); } if interaction.bottom { - points.push(pos2(max.x - cr.se, max.y)); - points.push(pos2(min.x + cr.sw, max.y)); + points.push(pos2(max.x - rounding.se, max.y)); + points.push(pos2(min.x + rounding.sw, max.y)); } if interaction.left && interaction.bottom { - add_circle_quadrant(&mut points, pos2(min.x + cr.sw, max.y - cr.sw), cr.sw, 1.0); + add_circle_quadrant( + &mut points, + pos2(min.x + rounding.sw, max.y - rounding.sw), + rounding.sw, + 1.0, + ); } if interaction.left { - points.push(pos2(min.x, max.y - cr.sw)); - points.push(pos2(min.x, min.y + cr.nw)); + points.push(pos2(min.x, max.y - rounding.sw)); + points.push(pos2(min.x, min.y + rounding.nw)); } if interaction.left && interaction.top { - add_circle_quadrant(&mut points, pos2(min.x + cr.nw, min.y + cr.nw), cr.nw, 2.0); + add_circle_quadrant( + &mut points, + pos2(min.x + rounding.nw, min.y + rounding.nw), + rounding.nw, + 2.0, + ); } if interaction.top { - points.push(pos2(min.x + cr.nw, min.y)); - points.push(pos2(max.x - cr.ne, min.y)); + points.push(pos2(min.x + rounding.nw, min.y)); + points.push(pos2(max.x - rounding.ne, min.y)); } if interaction.right && interaction.top { - add_circle_quadrant(&mut points, pos2(max.x - cr.ne, min.y + cr.ne), cr.ne, 3.0); - points.push(pos2(max.x, min.y + cr.ne)); - points.push(pos2(max.x, max.y - cr.se)); + add_circle_quadrant( + &mut points, + pos2(max.x - rounding.ne, min.y + rounding.ne), + rounding.ne, + 3.0, + ); + points.push(pos2(max.x, min.y + rounding.ne)); + points.push(pos2(max.x, max.y - rounding.se)); } ui.painter().add(Shape::line(points, visuals.bg_stroke)); } diff --git a/egui/src/menu.rs b/egui/src/menu.rs index 8e9b415b34b..d2b87216b95 100644 --- a/egui/src/menu.rs +++ b/egui/src/menu.rs @@ -438,7 +438,7 @@ impl SubMenuButton { ui.painter().rect_filled( rect.expand(visuals.expansion), - visuals.corner_radius, + visuals.rounding, visuals.bg_fill, ); diff --git a/egui/src/painter.rs b/egui/src/painter.rs index 119903d20cf..e2adf8da7c0 100644 --- a/egui/src/painter.rs +++ b/egui/src/painter.rs @@ -278,13 +278,13 @@ impl Painter { pub fn rect( &self, rect: Rect, - corner_radius: impl Into, + rounding: impl Into, fill_color: impl Into, stroke: impl Into, ) { self.add(RectShape { rect, - corner_radius: corner_radius.into(), + rounding: rounding.into(), fill: fill_color.into(), stroke: stroke.into(), }); @@ -293,12 +293,12 @@ impl Painter { pub fn rect_filled( &self, rect: Rect, - corner_radius: impl Into, + rounding: impl Into, fill_color: impl Into, ) { self.add(RectShape { rect, - corner_radius: corner_radius.into(), + rounding: rounding.into(), fill: fill_color.into(), stroke: Default::default(), }); @@ -307,12 +307,12 @@ impl Painter { pub fn rect_stroke( &self, rect: Rect, - corner_radius: impl Into, + rounding: impl Into, stroke: impl Into, ) { self.add(RectShape { rect, - corner_radius: corner_radius.into(), + rounding: rounding.into(), fill: Default::default(), stroke: stroke.into(), }); diff --git a/egui/src/style.rs b/egui/src/style.rs index fa678efcc51..f75d664d626 100644 --- a/egui/src/style.rs +++ b/egui/src/style.rs @@ -344,7 +344,7 @@ pub struct Visuals { /// Background color behind code-styled monospaced labels. pub code_bg_color: Color32, - pub window_corner_radius: Rounding, + pub window_rounding: Rounding, pub window_shadow: Shadow, pub popup_shadow: Shadow, @@ -453,7 +453,7 @@ pub struct WidgetVisuals { pub bg_stroke: Stroke, /// Button frames etc. - pub corner_radius: Rounding, + pub rounding: Rounding, /// Stroke and text color of the interactive part of a component (button text, slider grab, check-mark, …). pub fg_stroke: Stroke, @@ -566,7 +566,7 @@ impl Visuals { faint_bg_color: Color32::from_gray(24), extreme_bg_color: Color32::from_gray(10), code_bg_color: Color32::from_gray(64), - window_corner_radius: Rounding::same(6.0), + window_rounding: Rounding::same(6.0), window_shadow: Shadow::big_dark(), popup_shadow: Shadow::small_dark(), resize_corner_size: 12.0, @@ -629,35 +629,35 @@ impl Widgets { bg_fill: Color32::from_gray(27), // window background bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // separators, indentation lines, windows outlines fg_stroke: Stroke::new(1.0, Color32::from_gray(140)), // normal text color - corner_radius: Rounding::same(2.0), + rounding: Rounding::same(2.0), expansion: 0.0, }, inactive: WidgetVisuals { bg_fill: Color32::from_gray(60), // button background bg_stroke: Default::default(), fg_stroke: Stroke::new(1.0, Color32::from_gray(180)), // button text - corner_radius: Rounding::same(2.0), + rounding: Rounding::same(2.0), expansion: 0.0, }, hovered: WidgetVisuals { bg_fill: Color32::from_gray(70), bg_stroke: Stroke::new(1.0, Color32::from_gray(150)), // e.g. hover over window edge or button fg_stroke: Stroke::new(1.5, Color32::from_gray(240)), - corner_radius: Rounding::same(3.0), + rounding: Rounding::same(3.0), expansion: 1.0, }, active: WidgetVisuals { bg_fill: Color32::from_gray(55), bg_stroke: Stroke::new(1.0, Color32::WHITE), fg_stroke: Stroke::new(2.0, Color32::WHITE), - corner_radius: Rounding::same(2.0), + rounding: Rounding::same(2.0), expansion: 1.0, }, open: WidgetVisuals { bg_fill: Color32::from_gray(27), bg_stroke: Stroke::new(1.0, Color32::from_gray(60)), fg_stroke: Stroke::new(1.0, Color32::from_gray(210)), - corner_radius: Rounding::same(2.0), + rounding: Rounding::same(2.0), expansion: 0.0, }, } @@ -669,35 +669,35 @@ impl Widgets { bg_fill: Color32::from_gray(235), // window background bg_stroke: Stroke::new(1.0, Color32::from_gray(190)), // separators, indentation lines, windows outlines fg_stroke: Stroke::new(1.0, Color32::from_gray(100)), // normal text color - corner_radius: Rounding::same(2.0), + rounding: Rounding::same(2.0), expansion: 0.0, }, inactive: WidgetVisuals { bg_fill: Color32::from_gray(215), // button background bg_stroke: Default::default(), fg_stroke: Stroke::new(1.0, Color32::from_gray(80)), // button text - corner_radius: Rounding::same(2.0), + rounding: Rounding::same(2.0), expansion: 0.0, }, hovered: WidgetVisuals { bg_fill: Color32::from_gray(210), bg_stroke: Stroke::new(1.0, Color32::from_gray(105)), // e.g. hover over window edge or button fg_stroke: Stroke::new(1.5, Color32::BLACK), - corner_radius: Rounding::same(3.0), + rounding: Rounding::same(3.0), expansion: 1.0, }, active: WidgetVisuals { bg_fill: Color32::from_gray(165), bg_stroke: Stroke::new(1.0, Color32::BLACK), fg_stroke: Stroke::new(2.0, Color32::BLACK), - corner_radius: Rounding::same(2.0), + rounding: Rounding::same(2.0), expansion: 1.0, }, open: WidgetVisuals { bg_fill: Color32::from_gray(220), bg_stroke: Stroke::new(1.0, Color32::from_gray(160)), fg_stroke: Stroke::new(1.0, Color32::BLACK), - corner_radius: Rounding::same(2.0), + rounding: Rounding::same(2.0), expansion: 0.0, }, } @@ -933,7 +933,7 @@ impl Selection { pub fn ui(&mut self, ui: &mut crate::Ui) { let Self { bg_fill, stroke } = self; ui.label("Selectable labels"); - ui_color(ui, bg_fill, "bg_fill"); + ui_color(ui, bg_fill, "background fill"); stroke_ui(ui, stroke, "stroke"); } } @@ -943,19 +943,16 @@ impl WidgetVisuals { let Self { bg_fill, bg_stroke, - corner_radius, + rounding, fg_stroke, expansion, } = self; - ui_color(ui, bg_fill, "bg_fill"); - stroke_ui(ui, bg_stroke, "bg_stroke"); + ui_color(ui, bg_fill, "background fill"); + stroke_ui(ui, bg_stroke, "background stroke"); - ui.add(Slider::new(&mut corner_radius.nw, 0.0..=10.0).text("corner_radius_nw")); - ui.add(Slider::new(&mut corner_radius.ne, 0.0..=10.0).text("corner_radius_ne")); - ui.add(Slider::new(&mut corner_radius.sw, 0.0..=10.0).text("corner_radius_sw")); - ui.add(Slider::new(&mut corner_radius.se, 0.0..=10.0).text("corner_radius_se")); + rounding_ui(ui, rounding); - stroke_ui(ui, fg_stroke, "fg_stroke (text)"); + stroke_ui(ui, fg_stroke, "foreground stroke (text)"); ui.add(Slider::new(expansion, -5.0..=5.0).text("expansion")) .on_hover_text("make shapes this much larger"); } @@ -1004,7 +1001,7 @@ impl Visuals { faint_bg_color, extreme_bg_color, code_bg_color, - window_corner_radius, + window_rounding, window_shadow, popup_shadow, resize_corner_size, @@ -1030,11 +1027,7 @@ impl Visuals { ui_color(ui, &mut widgets.noninteractive.bg_fill, "Fill"); stroke_ui(ui, &mut widgets.noninteractive.bg_stroke, "Outline"); - ui.label("Rounding"); - ui.add(Slider::new(&mut window_corner_radius.nw, 0.0..=20.0).text("Top Left")); - ui.add(Slider::new(&mut window_corner_radius.ne, 0.0..=20.0).text("Top Right")); - ui.add(Slider::new(&mut window_corner_radius.sw, 0.0..=20.0).text("Bottom Left")); - ui.add(Slider::new(&mut window_corner_radius.se, 0.0..=20.0).text("Bottom Right")); + rounding_ui(ui, window_rounding); shadow_ui(ui, window_shadow, "Shadow"); shadow_ui(ui, popup_shadow, "Shadow (small menus and popups)"); @@ -1125,3 +1118,29 @@ fn ui_color(ui: &mut Ui, srgba: &mut Color32, label: impl Into) -> R }) .response } + +fn rounding_ui(ui: &mut Ui, rounding: &mut Rounding) { + const MAX: f32 = 20.0; + let mut same = rounding.is_same(); + ui.group(|ui| { + ui.horizontal(|ui| { + ui.label("Rounding: "); + ui.radio_value(&mut same, true, "Same"); + ui.radio_value(&mut same, false, "Separate"); + }); + + if same { + let mut cr = rounding.nw; + ui.add(Slider::new(&mut cr, 0.0..=MAX)); + *rounding = Rounding::same(cr); + } else { + ui.add(Slider::new(&mut rounding.nw, 0.0..=MAX).text("North-West")); + ui.add(Slider::new(&mut rounding.ne, 0.0..=MAX).text("North-East")); + ui.add(Slider::new(&mut rounding.sw, 0.0..=MAX).text("South-West")); + ui.add(Slider::new(&mut rounding.se, 0.0..=MAX).text("South-East")); + if rounding.is_same() { + rounding.se *= 1.00001; + } + } + }); +} diff --git a/egui/src/widgets/button.rs b/egui/src/widgets/button.rs index f1373a60f37..f8fbe359e19 100644 --- a/egui/src/widgets/button.rs +++ b/egui/src/widgets/button.rs @@ -180,7 +180,7 @@ impl Widget for Button { let stroke = stroke.unwrap_or(visuals.bg_stroke); ui.painter().rect( rect.expand(visuals.expansion), - visuals.corner_radius, + visuals.rounding, fill, stroke, ); @@ -265,7 +265,7 @@ impl<'a> Widget for Checkbox<'a> { let (small_icon_rect, big_icon_rect) = ui.spacing().icon_rectangles(rect); ui.painter().add(epaint::RectShape { rect: big_icon_rect.expand(visuals.expansion), - corner_radius: visuals.corner_radius, + rounding: visuals.rounding, fill: visuals.bg_fill, stroke: visuals.bg_stroke, }); @@ -455,7 +455,7 @@ impl Widget for ImageButton { response.widget_info(|| WidgetInfo::new(WidgetType::ImageButton)); if ui.is_rect_visible(rect) { - let (expansion, corner_radius, fill, stroke) = if selected { + let (expansion, rounding, fill, stroke) = if selected { let selection = ui.visuals().selection; ( -padding, @@ -472,7 +472,7 @@ impl Widget for ImageButton { }; ( expansion, - visuals.corner_radius, + visuals.rounding, visuals.bg_fill, visuals.bg_stroke, ) @@ -482,7 +482,7 @@ impl Widget for ImageButton { // Draw frame background (for transparent images): ui.painter() - .rect_filled(rect.expand2(expansion), corner_radius, fill); + .rect_filled(rect.expand2(expansion), rounding, fill); let image_rect = ui .layout() @@ -492,7 +492,7 @@ impl Widget for ImageButton { // Draw frame outline: ui.painter() - .rect_stroke(rect.expand2(expansion), corner_radius, stroke); + .rect_stroke(rect.expand2(expansion), rounding, stroke); } response diff --git a/egui/src/widgets/color_picker.rs b/egui/src/widgets/color_picker.rs index 4afa47c43e6..6ed70d60d8b 100644 --- a/egui/src/widgets/color_picker.rs +++ b/egui/src/widgets/color_picker.rs @@ -61,7 +61,7 @@ fn show_hsva(ui: &mut Ui, color: Hsva, desired_size: Vec2) -> Response { } else { ui.painter().add(RectShape { rect, - corner_radius: Rounding::same(2.0), + rounding: Rounding::same(2.0), fill: color.into(), stroke: Stroke::new(3.0, color.to_opaque()), }); @@ -90,15 +90,15 @@ fn color_button(ui: &mut Ui, color: Color32, open: bool) -> Response { ui.painter().rect_filled(left_half, 0.0, color); ui.painter().rect_filled(right_half, 0.0, color.to_opaque()); - let corner_radius = Rounding { - nw: visuals.corner_radius.nw.at_most(2.0), - ne: visuals.corner_radius.ne.at_most(2.0), - sw: visuals.corner_radius.sw.at_most(2.0), - se: visuals.corner_radius.se.at_most(2.0), + let rounding = Rounding { + nw: visuals.rounding.nw.at_most(2.0), + ne: visuals.rounding.ne.at_most(2.0), + sw: visuals.rounding.sw.at_most(2.0), + se: visuals.rounding.se.at_most(2.0), }; ui.painter() - .rect_stroke(rect, corner_radius, (2.0, visuals.bg_fill)); // fill is intentional, because default style has no border + .rect_stroke(rect, rounding, (2.0, visuals.bg_fill)); // fill is intentional, because default style has no border } response diff --git a/egui/src/widgets/plot/items/bar.rs b/egui/src/widgets/plot/items/bar.rs index 5c0546ae3a3..39f3a91fbce 100644 --- a/egui/src/widgets/plot/items/bar.rs +++ b/egui/src/widgets/plot/items/bar.rs @@ -129,7 +129,7 @@ impl Bar { let rect = transform.rect_from_values(&self.bounds_min(), &self.bounds_max()); let rect = Shape::Rect(RectShape { rect, - corner_radius: Rounding::none(), + rounding: Rounding::none(), fill, stroke, }); diff --git a/egui/src/widgets/plot/items/box_elem.rs b/egui/src/widgets/plot/items/box_elem.rs index 8a9fe54e34d..7cda17c8533 100644 --- a/egui/src/widgets/plot/items/box_elem.rs +++ b/egui/src/widgets/plot/items/box_elem.rs @@ -152,7 +152,7 @@ impl BoxElem { ); let rect = Shape::Rect(RectShape { rect, - corner_radius: Rounding::none(), + rounding: Rounding::none(), fill, stroke, }); diff --git a/egui/src/widgets/plot/legend.rs b/egui/src/widgets/plot/legend.rs index cbb5a676af9..71f3dfdcbef 100644 --- a/egui/src/widgets/plot/legend.rs +++ b/egui/src/widgets/plot/legend.rs @@ -240,7 +240,7 @@ impl Widget for &mut LegendWidget { .scope(|ui| { let background_frame = Frame { margin: vec2(8.0, 4.0), - corner_radius: ui.style().visuals.window_corner_radius, + rounding: ui.style().visuals.window_rounding, shadow: epaint::Shadow::default(), fill: ui.style().visuals.extreme_bg_color, stroke: ui.style().visuals.window_stroke(), diff --git a/egui/src/widgets/plot/mod.rs b/egui/src/widgets/plot/mod.rs index d3aae10c459..647c1b4152a 100644 --- a/egui/src/widgets/plot/mod.rs +++ b/egui/src/widgets/plot/mod.rs @@ -479,7 +479,7 @@ impl Plot { if show_background { ui.painter().sub_region(rect).add(epaint::RectShape { rect, - corner_radius: Rounding::same(2.0), + rounding: Rounding::same(2.0), fill: ui.visuals().extreme_bg_color, stroke: ui.visuals().widgets.noninteractive.bg_stroke, }); diff --git a/egui/src/widgets/progress_bar.rs b/egui/src/widgets/progress_bar.rs index 9c74f473fe7..c89f1002201 100644 --- a/egui/src/widgets/progress_bar.rs +++ b/egui/src/widgets/progress_bar.rs @@ -77,10 +77,10 @@ impl Widget for ProgressBar { } let visuals = ui.style().visuals.clone(); - let corner_radius = outer_rect.height() / 2.0; + let rounding = outer_rect.height() / 2.0; ui.painter().rect( outer_rect, - corner_radius, + rounding, visuals.extreme_bg_color, Stroke::none(), ); @@ -101,7 +101,7 @@ impl Widget for ProgressBar { ui.painter().rect( inner_rect, - corner_radius, + rounding, Color32::from(Rgba::from(visuals.selection.bg_fill) * color_factor as f32), Stroke::none(), ); @@ -110,14 +110,14 @@ impl Widget for ProgressBar { let n_points = 20; let start_angle = ui.input().time as f64 * 360f64.to_radians(); let end_angle = start_angle + 240f64.to_radians() * ui.input().time.sin(); - let circle_radius = corner_radius - 2.0; + let circle_radius = rounding - 2.0; let points: Vec = (0..n_points) .map(|i| { let angle = lerp(start_angle..=end_angle, i as f64 / n_points as f64); let (sin, cos) = angle.sin_cos(); inner_rect.right_center() + circle_radius * vec2(cos as f32, sin as f32) - + vec2(-corner_radius, 0.0) + + vec2(-rounding, 0.0) }) .collect(); ui.painter().add(Shape::line( diff --git a/egui/src/widgets/selected_label.rs b/egui/src/widgets/selected_label.rs index 8ddb5c2e286..2f2d49d23d7 100644 --- a/egui/src/widgets/selected_label.rs +++ b/egui/src/widgets/selected_label.rs @@ -64,12 +64,8 @@ impl Widget for SelectableLabel { if selected || response.hovered() || response.has_focus() { let rect = rect.expand(visuals.expansion); - ui.painter().rect( - rect, - visuals.corner_radius, - visuals.bg_fill, - visuals.bg_stroke, - ); + ui.painter() + .rect(rect, visuals.rounding, visuals.bg_fill, visuals.bg_stroke); } text.paint_with_visuals(ui.painter(), text_pos, &visuals); diff --git a/egui/src/widgets/slider.rs b/egui/src/widgets/slider.rs index e3906259c92..dd32fccca5b 100644 --- a/egui/src/widgets/slider.rs +++ b/egui/src/widgets/slider.rs @@ -355,7 +355,7 @@ impl<'a> Slider<'a> { let visuals = ui.style().interact(response); ui.painter().add(epaint::RectShape { rect: rail_rect, - corner_radius: ui.visuals().widgets.inactive.corner_radius, + rounding: ui.visuals().widgets.inactive.rounding, fill: ui.visuals().widgets.inactive.bg_fill, // fill: visuals.bg_fill, // fill: ui.visuals().extreme_bg_color, diff --git a/egui/src/widgets/text_edit/builder.rs b/egui/src/widgets/text_edit/builder.rs index 89d0052a998..1c95663380e 100644 --- a/egui/src/widgets/text_edit/builder.rs +++ b/egui/src/widgets/text_edit/builder.rs @@ -299,7 +299,7 @@ impl<'t> TextEdit<'t> { if output.response.has_focus() { epaint::RectShape { rect: frame_rect, - corner_radius: visuals.corner_radius, + rounding: visuals.rounding, // fill: ui.visuals().selection.bg_fill, fill: ui.visuals().extreme_bg_color, stroke: ui.visuals().selection.stroke, @@ -307,7 +307,7 @@ impl<'t> TextEdit<'t> { } else { epaint::RectShape { rect: frame_rect, - corner_radius: visuals.corner_radius, + rounding: visuals.rounding, fill: ui.visuals().extreme_bg_color, stroke: visuals.bg_stroke, // TODO: we want to show something here, or a text-edit field doesn't "pop". } @@ -316,7 +316,7 @@ impl<'t> TextEdit<'t> { let visuals = &ui.style().visuals.widgets.inactive; epaint::RectShape { rect: frame_rect, - corner_radius: visuals.corner_radius, + rounding: visuals.rounding, // fill: ui.visuals().extreme_bg_color, // fill: visuals.bg_fill, fill: Color32::TRANSPARENT, diff --git a/egui_demo_lib/src/apps/demo/drag_and_drop.rs b/egui_demo_lib/src/apps/demo/drag_and_drop.rs index 4acd22b1066..a44c48beaf5 100644 --- a/egui_demo_lib/src/apps/demo/drag_and_drop.rs +++ b/egui_demo_lib/src/apps/demo/drag_and_drop.rs @@ -66,7 +66,7 @@ pub fn drop_target( ui.painter().set( where_to_put_background, epaint::RectShape { - corner_radius: style.corner_radius, + rounding: style.rounding, fill, stroke, rect, diff --git a/egui_demo_lib/src/apps/demo/misc_demo_window.rs b/egui_demo_lib/src/apps/demo/misc_demo_window.rs index 785a9473243..f4e672ed6f5 100644 --- a/egui_demo_lib/src/apps/demo/misc_demo_window.rs +++ b/egui_demo_lib/src/apps/demo/misc_demo_window.rs @@ -269,7 +269,7 @@ impl ColorWidgets { #[cfg_attr(feature = "serde", serde(default))] struct BoxPainting { size: Vec2, - corner_radius: f32, + rounding: f32, stroke_width: f32, num_boxes: usize, } @@ -278,7 +278,7 @@ impl Default for BoxPainting { fn default() -> Self { Self { size: vec2(64.0, 32.0), - corner_radius: 5.0, + rounding: 5.0, stroke_width: 2.0, num_boxes: 1, } @@ -289,7 +289,7 @@ impl BoxPainting { pub fn ui(&mut self, ui: &mut Ui) { ui.add(Slider::new(&mut self.size.x, 0.0..=500.0).text("width")); ui.add(Slider::new(&mut self.size.y, 0.0..=500.0).text("height")); - ui.add(Slider::new(&mut self.corner_radius, 0.0..=50.0).text("corner_radius")); + ui.add(Slider::new(&mut self.rounding, 0.0..=50.0).text("rounding")); ui.add(Slider::new(&mut self.stroke_width, 0.0..=10.0).text("stroke_width")); ui.add(Slider::new(&mut self.num_boxes, 0..=8).text("num_boxes")); @@ -298,7 +298,7 @@ impl BoxPainting { let (rect, _response) = ui.allocate_at_least(self.size, Sense::hover()); ui.painter().rect( rect, - self.corner_radius, + self.rounding, Color32::from_gray(64), Stroke::new(self.stroke_width, Color32::WHITE), ); diff --git a/egui_demo_lib/src/frame_history.rs b/egui_demo_lib/src/frame_history.rs index 5af9d24cedd..556d37548a9 100644 --- a/egui_demo_lib/src/frame_history.rs +++ b/egui_demo_lib/src/frame_history.rs @@ -76,7 +76,7 @@ impl FrameHistory { let mut shapes = Vec::with_capacity(3 + 2 * history.len()); shapes.push(Shape::Rect(epaint::RectShape { rect, - corner_radius: style.corner_radius, + rounding: style.rounding, fill: ui.visuals().extreme_bg_color, stroke: ui.style().noninteractive().bg_stroke, })); diff --git a/epaint/CHANGELOG.md b/epaint/CHANGELOG.md index 503a2128139..4395c203160 100644 --- a/epaint/CHANGELOG.md +++ b/epaint/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to the epaint crate will be documented in this file. * Replaced `Fonts::font_image` with `font_image_delta` for partial font atlas updates. * Added `ImageData` and `TextureManager` for loading images into textures ([#1110](https://github.com/emilk/egui/pull/1110)). * Added `Shape::dashed_line_many` ([#1027](https://github.com/emilk/egui/pull/1027)). +* Replaced `corner_radius: f32` with `rounding: Rounding`, allowing per-corner rounding settings ([#1206](https://github.com/emilk/egui/pull/1206)). ## 0.16.0 - 2021-12-29 diff --git a/epaint/src/shadow.rs b/epaint/src/shadow.rs index 90987ea35ae..d8895fee34c 100644 --- a/epaint/src/shadow.rs +++ b/epaint/src/shadow.rs @@ -46,23 +46,23 @@ impl Shadow { } } - pub fn tessellate(&self, rect: emath::Rect, corner_radius: impl Into) -> Mesh { + pub fn tessellate(&self, rect: emath::Rect, rounding: impl Into) -> Mesh { // tessellator.clip_rect = clip_rect; // TODO: culling let Self { extrusion, color } = *self; - let cr: Rounding = corner_radius.into(); + let rounding: Rounding = rounding.into(); let half_ext = 0.5 * extrusion; - let ext_corner_radius = Rounding { - nw: cr.nw + half_ext, - ne: cr.ne + half_ext, - sw: cr.sw + half_ext, - se: cr.se + half_ext, + let ext_rounding = Rounding { + nw: rounding.nw + half_ext, + ne: rounding.ne + half_ext, + sw: rounding.sw + half_ext, + se: rounding.se + half_ext, }; use crate::tessellator::*; - let rect = RectShape::filled(rect.expand(half_ext), ext_corner_radius, color); + let rect = RectShape::filled(rect.expand(half_ext), ext_rounding, color); let mut tessellator = Tessellator::from_options(TessellationOptions { aa_size: extrusion, anti_alias: true, diff --git a/epaint/src/shape.rs b/epaint/src/shape.rs index 33d4ad1f7e8..7ac5e828ea1 100644 --- a/epaint/src/shape.rs +++ b/epaint/src/shape.rs @@ -116,19 +116,19 @@ impl Shape { #[inline] pub fn rect_filled( rect: Rect, - corner_radius: impl Into, + rounding: impl Into, fill_color: impl Into, ) -> Self { - Self::Rect(RectShape::filled(rect, corner_radius, fill_color)) + Self::Rect(RectShape::filled(rect, rounding, fill_color)) } #[inline] pub fn rect_stroke( rect: Rect, - corner_radius: impl Into, + rounding: impl Into, stroke: impl Into, ) -> Self { - Self::Rect(RectShape::stroke(rect, corner_radius, stroke)) + Self::Rect(RectShape::stroke(rect, rounding, stroke)) } #[allow(clippy::needless_pass_by_value)] @@ -330,7 +330,7 @@ impl From for Shape { pub struct RectShape { pub rect: Rect, /// How rounded the corners are. Use `Rounding::none()` for no rounding. - pub corner_radius: Rounding, + pub rounding: Rounding, pub fill: Color32, pub stroke: Stroke, } @@ -339,26 +339,22 @@ impl RectShape { #[inline] pub fn filled( rect: Rect, - corner_radius: impl Into, + rounding: impl Into, fill_color: impl Into, ) -> Self { Self { rect, - corner_radius: corner_radius.into(), + rounding: rounding.into(), fill: fill_color.into(), stroke: Default::default(), } } #[inline] - pub fn stroke( - rect: Rect, - corner_radius: impl Into, - stroke: impl Into, - ) -> Self { + pub fn stroke(rect: Rect, rounding: impl Into, stroke: impl Into) -> Self { Self { rect, - corner_radius: corner_radius.into(), + rounding: rounding.into(), fill: Default::default(), stroke: stroke.into(), } @@ -382,9 +378,13 @@ impl From for Shape { #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] /// How rounded the corners of things should be pub struct Rounding { + /// Radius of the rounding of the North-West (left top) corner. pub nw: f32, + /// Radius of the rounding of the North-East (right top) corner. pub ne: f32, + /// Radius of the rounding of the South-West (left bottom) corner. pub sw: f32, + /// Radius of the rounding of the South-East (right bottom) corner. pub se: f32, } @@ -427,6 +427,12 @@ impl Rounding { se: 0.0, } } + + /// Do all corners have the same rounding? + #[inline] + pub fn is_same(&self) -> bool { + self.nw == self.ne && self.nw == self.sw && self.nw == self.se + } } // ---------------------------------------------------------------------------- diff --git a/epaint/src/tessellator.rs b/epaint/src/tessellator.rs index 7eac496b6cc..359e501fc16 100644 --- a/epaint/src/tessellator.rs +++ b/epaint/src/tessellator.rs @@ -184,15 +184,15 @@ pub mod path { use super::*; /// overwrites existing points - pub fn rounded_rectangle(path: &mut Vec, rect: Rect, corner_radius: Rounding) { + pub fn rounded_rectangle(path: &mut Vec, rect: Rect, rounding: Rounding) { path.clear(); let min = rect.min; let max = rect.max; - let cr = clamp_radius(corner_radius, rect); + let r = clamp_radius(rounding, rect); - if cr == Rounding::none() { + if r == Rounding::none() { let min = rect.min; let max = rect.max; path.reserve(4); @@ -201,10 +201,10 @@ pub mod path { path.push(pos2(max.x, max.y)); path.push(pos2(min.x, max.y)); } else { - add_circle_quadrant(path, pos2(max.x - cr.se, max.y - cr.se), cr.se, 0.0); - add_circle_quadrant(path, pos2(min.x + cr.sw, max.y - cr.sw), cr.sw, 1.0); - add_circle_quadrant(path, pos2(min.x + cr.nw, min.y + cr.nw), cr.nw, 2.0); - add_circle_quadrant(path, pos2(max.x - cr.ne, min.y + cr.ne), cr.ne, 3.0); + add_circle_quadrant(path, pos2(max.x - r.se, max.y - r.se), r.se, 0.0); + add_circle_quadrant(path, pos2(min.x + r.sw, max.y - r.sw), r.sw, 1.0); + add_circle_quadrant(path, pos2(min.x + r.nw, min.y + r.nw), r.nw, 2.0); + add_circle_quadrant(path, pos2(max.x - r.ne, min.y + r.ne), r.ne, 3.0); } } @@ -244,16 +244,16 @@ pub mod path { } // Ensures the radius of each corner is within a valid range - fn clamp_radius(corner_radius: Rounding, rect: Rect) -> Rounding { + fn clamp_radius(rounding: Rounding, rect: Rect) -> Rounding { let half_width = rect.width() * 0.5; let half_height = rect.height() * 0.5; let max_cr = half_width.min(half_height); Rounding { - nw: corner_radius.nw.at_most(max_cr), - ne: corner_radius.ne.at_most(max_cr), - sw: corner_radius.sw.at_most(max_cr), - se: corner_radius.se.at_most(max_cr), + nw: rounding.nw.at_most(max_cr).at_least(0.0), + ne: rounding.ne.at_most(max_cr).at_least(0.0), + sw: rounding.sw.at_most(max_cr).at_least(0.0), + se: rounding.se.at_most(max_cr).at_least(0.0), } } } @@ -862,7 +862,7 @@ impl Tessellator { pub(crate) fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) { let RectShape { mut rect, - corner_radius, + rounding, fill, stroke, } = *rect; @@ -883,7 +883,7 @@ impl Tessellator { let path = &mut self.scratchpad_path; path.clear(); - path::rounded_rectangle(&mut self.scratchpad_points, rect, corner_radius); + path::rounded_rectangle(&mut self.scratchpad_points, rect, rounding); path.add_line_loop(&self.scratchpad_points); path.fill(fill, &self.options, out); path.stroke_closed(stroke, &self.options, out);