`RectShape`: add control over where the stoke goes (#5647)
Adds `RectShape::stroke_kind` so you can select if the stroke goes inside, outside, or is centered on the rectangle. Also adds `RectShape::round_to_pixels` so you can override `TessellationOptions::round_rects_to_pixels`.
This commit is contained in:
parent
83649f2e29
commit
6be17136f2
|
|
@ -423,7 +423,10 @@ impl Frame {
|
||||||
let fill_rect = self.fill_rect(content_rect);
|
let fill_rect = self.fill_rect(content_rect);
|
||||||
let widget_rect = self.widget_rect(content_rect);
|
let widget_rect = self.widget_rect(content_rect);
|
||||||
|
|
||||||
let frame_shape = Shape::Rect(epaint::RectShape::new(fill_rect, rounding, fill, stroke));
|
let frame_shape = Shape::Rect(
|
||||||
|
epaint::RectShape::new(fill_rect, rounding, fill, stroke)
|
||||||
|
.with_stroke_kind(epaint::StrokeKind::Outside),
|
||||||
|
);
|
||||||
|
|
||||||
if shadow == Default::default() {
|
if shadow == Default::default() {
|
||||||
frame_shape
|
frame_shape
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,8 @@ pub fn adjust_colors(
|
||||||
rounding: _,
|
rounding: _,
|
||||||
fill,
|
fill,
|
||||||
stroke,
|
stroke,
|
||||||
|
stroke_kind: _,
|
||||||
|
round_to_pixels: _,
|
||||||
blur_width: _,
|
blur_width: _,
|
||||||
brush: _,
|
brush: _,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,18 @@ pub struct RectShape {
|
||||||
/// This means the [`Self::visual_bounding_rect`] is `rect.size() + 2.0 * stroke.width`.
|
/// This means the [`Self::visual_bounding_rect`] is `rect.size() + 2.0 * stroke.width`.
|
||||||
pub stroke: Stroke,
|
pub stroke: Stroke,
|
||||||
|
|
||||||
|
/// Is the stroke on the inside, outside, or centered on the rectangle?
|
||||||
|
pub stroke_kind: StrokeKind,
|
||||||
|
|
||||||
|
/// Snap the rectangle to pixels?
|
||||||
|
///
|
||||||
|
/// Rounding produces sharper rectangles.
|
||||||
|
/// It is the outside of the fill (=inside of the stroke)
|
||||||
|
/// that will be rounded to the physical pixel grid.
|
||||||
|
///
|
||||||
|
/// If `None`, [`crate::TessellationOptions::round_rects_to_pixels`] will be used.
|
||||||
|
pub round_to_pixels: Option<bool>,
|
||||||
|
|
||||||
/// If larger than zero, the edges of the rectangle
|
/// If larger than zero, the edges of the rectangle
|
||||||
/// (for both fill and stroke) will be blurred.
|
/// (for both fill and stroke) will be blurred.
|
||||||
///
|
///
|
||||||
|
|
@ -63,6 +75,8 @@ impl RectShape {
|
||||||
rounding: rounding.into(),
|
rounding: rounding.into(),
|
||||||
fill: fill_color.into(),
|
fill: fill_color.into(),
|
||||||
stroke: stroke.into(),
|
stroke: stroke.into(),
|
||||||
|
stroke_kind: StrokeKind::Outside,
|
||||||
|
round_to_pixels: None,
|
||||||
blur_width: 0.0,
|
blur_width: 0.0,
|
||||||
brush: Default::default(),
|
brush: Default::default(),
|
||||||
}
|
}
|
||||||
|
|
@ -84,6 +98,26 @@ impl RectShape {
|
||||||
Self::new(rect, rounding, fill, stroke)
|
Self::new(rect, rounding, fill, stroke)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set if the stroke is on the inside, outside, or centered on the rectangle.
|
||||||
|
#[inline]
|
||||||
|
pub fn with_stroke_kind(mut self, stroke_kind: StrokeKind) -> Self {
|
||||||
|
self.stroke_kind = stroke_kind;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Snap the rectangle to pixels?
|
||||||
|
///
|
||||||
|
/// Rounding produces sharper rectangles.
|
||||||
|
/// It is the outside of the fill (=inside of the stroke)
|
||||||
|
/// that will be rounded to the physical pixel grid.
|
||||||
|
///
|
||||||
|
/// If `None`, [`crate::TessellationOptions::round_rects_to_pixels`] will be used.
|
||||||
|
#[inline]
|
||||||
|
pub fn with_round_to_pixels(mut self, round_to_pixels: bool) -> Self {
|
||||||
|
self.round_to_pixels = Some(round_to_pixels);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// If larger than zero, the edges of the rectangle
|
/// If larger than zero, the edges of the rectangle
|
||||||
/// (for both fill and stroke) will be blurred.
|
/// (for both fill and stroke) will be blurred.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -56,17 +56,17 @@ impl std::hash::Hash for Stroke {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describes how the stroke of a shape should be painted.
|
/// Describes how the stroke of a shape should be painted.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub enum StrokeKind {
|
pub enum StrokeKind {
|
||||||
/// The stroke should be painted entirely outside of the shape
|
|
||||||
Outside,
|
|
||||||
|
|
||||||
/// The stroke should be painted entirely inside of the shape
|
/// The stroke should be painted entirely inside of the shape
|
||||||
Inside,
|
Inside,
|
||||||
|
|
||||||
/// The stroke should be painted right on the edge of the shape, half inside and half outside.
|
/// The stroke should be painted right on the edge of the shape, half inside and half outside.
|
||||||
Middle,
|
Middle,
|
||||||
|
|
||||||
|
/// The stroke should be painted entirely outside of the shape
|
||||||
|
Outside,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for StrokeKind {
|
impl Default for StrokeKind {
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@
|
||||||
|
|
||||||
#![allow(clippy::identity_op)]
|
#![allow(clippy::identity_op)]
|
||||||
|
|
||||||
use crate::texture_atlas::PreparedDisc;
|
|
||||||
use crate::{
|
|
||||||
color, emath, stroke, CircleShape, ClippedPrimitive, ClippedShape, Color32, CubicBezierShape,
|
|
||||||
EllipseShape, Mesh, PathShape, Primitive, QuadraticBezierShape, RectShape, Rounding, Shape,
|
|
||||||
Stroke, TextShape, TextureId, Vertex, WHITE_UV,
|
|
||||||
};
|
|
||||||
use emath::{pos2, remap, vec2, GuiRounding as _, NumExt, Pos2, Rect, Rot2, Vec2};
|
use emath::{pos2, remap, vec2, GuiRounding as _, NumExt, Pos2, Rect, Rot2, Vec2};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
color, emath, stroke, texture_atlas::PreparedDisc, CircleShape, ClippedPrimitive, ClippedShape,
|
||||||
|
Color32, CubicBezierShape, EllipseShape, Mesh, PathShape, Primitive, QuadraticBezierShape,
|
||||||
|
RectShape, Rounding, Shape, Stroke, StrokeKind, TextShape, TextureId, Vertex, WHITE_UV,
|
||||||
|
};
|
||||||
|
|
||||||
use self::color::ColorMode;
|
use self::color::ColorMode;
|
||||||
use self::stroke::PathStroke;
|
use self::stroke::PathStroke;
|
||||||
|
|
||||||
|
|
@ -686,6 +686,8 @@ pub struct TessellationOptions {
|
||||||
///
|
///
|
||||||
/// This makes the rectangle strokes more crisp,
|
/// This makes the rectangle strokes more crisp,
|
||||||
/// and makes filled rectangles tile perfectly (without feathering).
|
/// and makes filled rectangles tile perfectly (without feathering).
|
||||||
|
///
|
||||||
|
/// You can override this with [`crate::RectShape::round_to_pixels`].
|
||||||
pub round_rects_to_pixels: bool,
|
pub round_rects_to_pixels: bool,
|
||||||
|
|
||||||
/// Output the clip rectangles to be painted.
|
/// Output the clip rectangles to be painted.
|
||||||
|
|
@ -1669,27 +1671,41 @@ impl Tessellator {
|
||||||
///
|
///
|
||||||
/// * `rect`: the rectangle to tessellate.
|
/// * `rect`: the rectangle to tessellate.
|
||||||
/// * `out`: triangles are appended to this.
|
/// * `out`: triangles are appended to this.
|
||||||
pub fn tessellate_rect(&mut self, rect: &RectShape, out: &mut Mesh) {
|
pub fn tessellate_rect(&mut self, rect_shape: &RectShape, out: &mut Mesh) {
|
||||||
let brush = rect.brush.as_ref();
|
let brush = rect_shape.brush.as_ref();
|
||||||
let RectShape {
|
let RectShape {
|
||||||
mut rect,
|
mut rect,
|
||||||
mut rounding,
|
mut rounding,
|
||||||
fill,
|
fill,
|
||||||
stroke,
|
stroke,
|
||||||
|
stroke_kind,
|
||||||
|
round_to_pixels,
|
||||||
mut blur_width,
|
mut blur_width,
|
||||||
..
|
brush: _, // brush is extracted on its own, because it is not Copy
|
||||||
} = *rect;
|
} = *rect_shape;
|
||||||
|
|
||||||
|
let round_to_pixels = round_to_pixels.unwrap_or(self.options.round_rects_to_pixels);
|
||||||
|
|
||||||
|
// Modify `rect` so that it represents the filled region, with the stroke on the outside:
|
||||||
|
match stroke_kind {
|
||||||
|
StrokeKind::Inside => {
|
||||||
|
rect = rect.shrink(stroke.width);
|
||||||
|
}
|
||||||
|
StrokeKind::Middle => {
|
||||||
|
rect = rect.shrink(stroke.width / 2.0);
|
||||||
|
}
|
||||||
|
StrokeKind::Outside => {
|
||||||
|
// Already good
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.options.coarse_tessellation_culling
|
if self.options.coarse_tessellation_culling
|
||||||
&& !rect.expand(stroke.width).intersects(self.clip_rect)
|
&& !rect.expand(stroke.width).intersects(self.clip_rect)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if rect.is_negative() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.options.round_rects_to_pixels {
|
if round_to_pixels {
|
||||||
// Since the stroke extends outside of the rectangle,
|
// Since the stroke extends outside of the rectangle,
|
||||||
// we can round the rectangle sides to the physical pixel edges,
|
// we can round the rectangle sides to the physical pixel edges,
|
||||||
// and the filled rect will appear crisp, as will the inside of the stroke.
|
// and the filled rect will appear crisp, as will the inside of the stroke.
|
||||||
|
|
@ -1736,7 +1752,7 @@ impl Tessellator {
|
||||||
if rect.width() < 0.5 * self.feathering {
|
if rect.width() < 0.5 * self.feathering {
|
||||||
// Very thin - approximate by a vertical line-segment:
|
// Very thin - approximate by a vertical line-segment:
|
||||||
let line = [rect.center_top(), rect.center_bottom()];
|
let line = [rect.center_top(), rect.center_bottom()];
|
||||||
if fill != Color32::TRANSPARENT {
|
if 0.0 < rect.width() && fill != Color32::TRANSPARENT {
|
||||||
self.tessellate_line_segment(line, Stroke::new(rect.width(), fill), out);
|
self.tessellate_line_segment(line, Stroke::new(rect.width(), fill), out);
|
||||||
}
|
}
|
||||||
if !stroke.is_empty() {
|
if !stroke.is_empty() {
|
||||||
|
|
@ -1746,7 +1762,7 @@ impl Tessellator {
|
||||||
} else if rect.height() < 0.5 * self.feathering {
|
} else if rect.height() < 0.5 * self.feathering {
|
||||||
// Very thin - approximate by a horizontal line-segment:
|
// Very thin - approximate by a horizontal line-segment:
|
||||||
let line = [rect.left_center(), rect.right_center()];
|
let line = [rect.left_center(), rect.right_center()];
|
||||||
if fill != Color32::TRANSPARENT {
|
if 0.0 < rect.height() && fill != Color32::TRANSPARENT {
|
||||||
self.tessellate_line_segment(line, Stroke::new(rect.height(), fill), out);
|
self.tessellate_line_segment(line, Stroke::new(rect.height(), fill), out);
|
||||||
}
|
}
|
||||||
if !stroke.is_empty() {
|
if !stroke.is_empty() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue