2022-02-19 19:28:25 +00:00
|
|
|
|
use egui::epaint::{CubicBezierShape, PathShape, QuadraticBezierShape};
|
2022-01-31 19:26:31 +00:00
|
|
|
|
use egui::*;
|
|
|
|
|
|
|
|
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
|
|
|
|
#[cfg_attr(feature = "serde", serde(default))]
|
|
|
|
|
pub struct PaintBezier {
|
2022-02-19 19:28:25 +00:00
|
|
|
|
/// Bézier curve degree, it can be 3, 4.
|
|
|
|
|
degree: usize,
|
|
|
|
|
/// The control points. The [`Self::degree`] first of them are used.
|
|
|
|
|
control_points: [Pos2; 4],
|
|
|
|
|
|
|
|
|
|
/// Stroke for Bézier curve.
|
2022-01-31 19:26:31 +00:00
|
|
|
|
stroke: Stroke,
|
2022-02-19 19:28:25 +00:00
|
|
|
|
|
|
|
|
|
/// Fill for Bézier curve.
|
2022-01-31 19:26:31 +00:00
|
|
|
|
fill: Color32,
|
2022-02-19 19:28:25 +00:00
|
|
|
|
|
|
|
|
|
/// Stroke for auxiliary lines.
|
|
|
|
|
aux_stroke: Stroke,
|
|
|
|
|
|
2022-01-31 19:26:31 +00:00
|
|
|
|
bounding_box_stroke: Stroke,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for PaintBezier {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
2022-02-19 19:28:25 +00:00
|
|
|
|
degree: 4,
|
|
|
|
|
control_points: [
|
|
|
|
|
pos2(50.0, 50.0),
|
2022-02-22 18:12:21 +00:00
|
|
|
|
pos2(60.0, 250.0),
|
|
|
|
|
pos2(200.0, 200.0),
|
|
|
|
|
pos2(250.0, 50.0),
|
2022-02-19 19:28:25 +00:00
|
|
|
|
],
|
2022-01-31 19:26:31 +00:00
|
|
|
|
stroke: Stroke::new(1.0, Color32::LIGHT_BLUE),
|
2022-02-19 19:28:25 +00:00
|
|
|
|
fill: Color32::from_rgb(50, 100, 150).linear_multiply(0.25),
|
|
|
|
|
aux_stroke: Stroke::new(1.0, Color32::RED.linear_multiply(0.25)),
|
|
|
|
|
bounding_box_stroke: Stroke::new(0.0, Color32::LIGHT_GREEN.linear_multiply(0.25)),
|
2022-01-31 19:26:31 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PaintBezier {
|
2022-02-22 18:12:21 +00:00
|
|
|
|
pub fn ui_control(&mut self, ui: &mut egui::Ui) {
|
|
|
|
|
ui.collapsing("Colors", |ui| {
|
|
|
|
|
ui.horizontal(|ui| {
|
|
|
|
|
ui.label("Fill color:");
|
|
|
|
|
ui.color_edit_button_srgba(&mut self.fill);
|
2022-01-31 19:26:31 +00:00
|
|
|
|
});
|
2022-02-22 18:12:21 +00:00
|
|
|
|
egui::stroke_ui(ui, &mut self.stroke, "Curve Stroke");
|
|
|
|
|
egui::stroke_ui(ui, &mut self.aux_stroke, "Auxiliary Stroke");
|
|
|
|
|
egui::stroke_ui(ui, &mut self.bounding_box_stroke, "Bounding Box Stroke");
|
|
|
|
|
});
|
2022-01-31 19:26:31 +00:00
|
|
|
|
|
2022-02-22 18:12:21 +00:00
|
|
|
|
ui.collapsing("Global tessellation options", |ui| {
|
|
|
|
|
let mut tessellation_options = *(ui.ctx().tessellation_options());
|
|
|
|
|
let tessellation_options = &mut tessellation_options;
|
|
|
|
|
tessellation_options.ui(ui);
|
|
|
|
|
let mut new_tessellation_options = ui.ctx().tessellation_options();
|
|
|
|
|
*new_tessellation_options = *tessellation_options;
|
|
|
|
|
});
|
2022-02-19 19:28:25 +00:00
|
|
|
|
|
2022-02-22 18:12:21 +00:00
|
|
|
|
ui.radio_value(&mut self.degree, 3, "Quadratic Bézier");
|
|
|
|
|
ui.radio_value(&mut self.degree, 4, "Cubic Bézier");
|
|
|
|
|
ui.label("Move the points by dragging them.");
|
|
|
|
|
ui.small("Only convex curves can be accurately filled.");
|
2022-01-31 19:26:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn ui_content(&mut self, ui: &mut Ui) -> egui::Response {
|
2022-02-19 19:28:25 +00:00
|
|
|
|
let (response, painter) =
|
|
|
|
|
ui.allocate_painter(Vec2::new(ui.available_width(), 300.0), Sense::hover());
|
2022-01-31 19:26:31 +00:00
|
|
|
|
|
|
|
|
|
let to_screen = emath::RectTransform::from_to(
|
2022-02-19 19:28:25 +00:00
|
|
|
|
Rect::from_min_size(Pos2::ZERO, response.rect.size()),
|
2022-01-31 19:26:31 +00:00
|
|
|
|
response.rect,
|
|
|
|
|
);
|
|
|
|
|
|
2022-02-19 19:28:25 +00:00
|
|
|
|
let control_point_radius = 8.0;
|
|
|
|
|
|
|
|
|
|
let mut control_point_shapes = vec![];
|
|
|
|
|
|
|
|
|
|
for (i, point) in self.control_points.iter_mut().enumerate().take(self.degree) {
|
|
|
|
|
let size = Vec2::splat(2.0 * control_point_radius);
|
|
|
|
|
|
|
|
|
|
let point_in_screen = to_screen.transform_pos(*point);
|
|
|
|
|
let point_rect = Rect::from_center_size(point_in_screen, size);
|
|
|
|
|
let point_id = response.id.with(i);
|
|
|
|
|
let point_response = ui.interact(point_rect, point_id, Sense::drag());
|
|
|
|
|
|
|
|
|
|
*point += point_response.drag_delta();
|
|
|
|
|
*point = to_screen.from().clamp(*point);
|
|
|
|
|
|
|
|
|
|
let point_in_screen = to_screen.transform_pos(*point);
|
|
|
|
|
let stroke = ui.style().interact(&point_response).fg_stroke;
|
|
|
|
|
|
|
|
|
|
control_point_shapes.push(Shape::circle_stroke(
|
|
|
|
|
point_in_screen,
|
|
|
|
|
control_point_radius,
|
|
|
|
|
stroke,
|
2022-01-31 19:26:31 +00:00
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-19 19:28:25 +00:00
|
|
|
|
let points_in_screen: Vec<Pos2> = self
|
|
|
|
|
.control_points
|
|
|
|
|
.iter()
|
|
|
|
|
.take(self.degree)
|
|
|
|
|
.map(|p| to_screen * *p)
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
match self.degree {
|
|
|
|
|
3 => {
|
|
|
|
|
let points = points_in_screen.clone().try_into().unwrap();
|
|
|
|
|
let shape =
|
|
|
|
|
QuadraticBezierShape::from_points_stroke(points, true, self.fill, self.stroke);
|
|
|
|
|
painter.add(epaint::RectShape::stroke(
|
2022-02-21 20:40:46 +00:00
|
|
|
|
shape.visual_bounding_rect(),
|
2022-02-19 19:28:25 +00:00
|
|
|
|
0.0,
|
|
|
|
|
self.bounding_box_stroke,
|
|
|
|
|
));
|
|
|
|
|
painter.add(shape);
|
|
|
|
|
}
|
|
|
|
|
4 => {
|
|
|
|
|
let points = points_in_screen.clone().try_into().unwrap();
|
|
|
|
|
let shape =
|
|
|
|
|
CubicBezierShape::from_points_stroke(points, true, self.fill, self.stroke);
|
|
|
|
|
painter.add(epaint::RectShape::stroke(
|
2022-02-21 20:40:46 +00:00
|
|
|
|
shape.visual_bounding_rect(),
|
2022-02-19 19:28:25 +00:00
|
|
|
|
0.0,
|
|
|
|
|
self.bounding_box_stroke,
|
|
|
|
|
));
|
|
|
|
|
painter.add(shape);
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
|
|
|
|
unreachable!();
|
|
|
|
|
}
|
2022-01-31 19:26:31 +00:00
|
|
|
|
};
|
|
|
|
|
|
2022-02-19 19:28:25 +00:00
|
|
|
|
painter.add(PathShape::line(points_in_screen, self.aux_stroke));
|
|
|
|
|
painter.extend(control_point_shapes);
|
2022-01-31 19:26:31 +00:00
|
|
|
|
|
2022-02-19 19:28:25 +00:00
|
|
|
|
response
|
2022-01-31 19:26:31 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl super::Demo for PaintBezier {
|
|
|
|
|
fn name(&self) -> &'static str {
|
2022-02-19 19:28:25 +00:00
|
|
|
|
") Bézier Curve"
|
2022-01-31 19:26:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn show(&mut self, ctx: &Context, open: &mut bool) {
|
|
|
|
|
use super::View as _;
|
|
|
|
|
Window::new(self.name())
|
|
|
|
|
.open(open)
|
|
|
|
|
.vscroll(false)
|
2022-02-19 19:28:25 +00:00
|
|
|
|
.resizable(false)
|
2022-02-22 18:12:21 +00:00
|
|
|
|
.default_size([300.0, 350.0])
|
2022-01-31 19:26:31 +00:00
|
|
|
|
.show(ctx, |ui| self.ui(ui));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl super::View for PaintBezier {
|
|
|
|
|
fn ui(&mut self, ui: &mut Ui) {
|
2022-02-17 16:08:13 +00:00
|
|
|
|
ui.vertical_centered(|ui| {
|
|
|
|
|
ui.add(crate::__egui_github_link_file!());
|
|
|
|
|
});
|
2022-01-31 19:26:31 +00:00
|
|
|
|
self.ui_control(ui);
|
|
|
|
|
|
|
|
|
|
Frame::dark_canvas(ui.style()).show(ui, |ui| {
|
|
|
|
|
self.ui_content(ui);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|