mirror of
https://github.com/penpot/penpot.git
synced 2025-05-13 09:46:37 +02:00
🎉 Implement rounded corners
This commit is contained in:
parent
576c912c81
commit
6e7a8c239c
6 changed files with 115 additions and 18 deletions
|
@ -376,6 +376,14 @@
|
||||||
value (:value blur)]
|
value (:value blur)]
|
||||||
(h/call internal-module "_set_shape_blur" type hidden value)))
|
(h/call internal-module "_set_shape_blur" type hidden value)))
|
||||||
|
|
||||||
|
(defn set-shape-corners
|
||||||
|
[corners]
|
||||||
|
(let [r1 (or (get corners 0) 0)
|
||||||
|
r2 (or (get corners 1) 0)
|
||||||
|
r3 (or (get corners 2) 0)
|
||||||
|
r4 (or (get corners 3) 0)]
|
||||||
|
(h/call internal-module "_set_shape_corners" r1 r2 r3 r4)))
|
||||||
|
|
||||||
(def debounce-render-without-cache (fns/debounce render-without-cache 100))
|
(def debounce-render-without-cache (fns/debounce render-without-cache 100))
|
||||||
|
|
||||||
(defn set-view
|
(defn set-view
|
||||||
|
@ -407,8 +415,13 @@
|
||||||
opacity (dm/get-prop shape :opacity)
|
opacity (dm/get-prop shape :opacity)
|
||||||
hidden (dm/get-prop shape :hidden)
|
hidden (dm/get-prop shape :hidden)
|
||||||
content (dm/get-prop shape :content)
|
content (dm/get-prop shape :content)
|
||||||
bool-content (dm/get-prop shape :bool-content)
|
blur (dm/get-prop shape :blur)
|
||||||
blur (dm/get-prop shape :blur)]
|
corners (when (some? (dm/get-prop shape :r1))
|
||||||
|
[(dm/get-prop shape :r1)
|
||||||
|
(dm/get-prop shape :r2)
|
||||||
|
(dm/get-prop shape :r3)
|
||||||
|
(dm/get-prop shape :r4)])
|
||||||
|
bool-content (dm/get-prop shape :bool-content)]
|
||||||
|
|
||||||
(use-shape id)
|
(use-shape id)
|
||||||
(set-shape-type type)
|
(set-shape-type type)
|
||||||
|
@ -424,6 +437,7 @@
|
||||||
(set-shape-blur blur))
|
(set-shape-blur blur))
|
||||||
(when (and (some? content) (= type :path)) (set-shape-path-content content))
|
(when (and (some? content) (= type :path)) (set-shape-path-content content))
|
||||||
(when (some? bool-content) (set-shape-bool-content bool-content))
|
(when (some? bool-content) (set-shape-bool-content bool-content))
|
||||||
|
(when (some? corners) (set-shape-corners corners))
|
||||||
(let [pending' (concat (set-shape-fills fills) (set-shape-strokes strokes))]
|
(let [pending' (concat (set-shape-fills fills) (set-shape-strokes strokes))]
|
||||||
(recur (inc index) (into pending pending'))))
|
(recur (inc index) (into pending pending'))))
|
||||||
pending))]
|
pending))]
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use skia_safe as skia;
|
||||||
|
|
||||||
mod debug;
|
mod debug;
|
||||||
mod math;
|
mod math;
|
||||||
mod mem;
|
mod mem;
|
||||||
|
@ -8,7 +10,6 @@ mod utils;
|
||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
use crate::shapes::{BoolType, Kind, Path};
|
use crate::shapes::{BoolType, Kind, Path};
|
||||||
use skia_safe as skia;
|
|
||||||
|
|
||||||
use crate::state::State;
|
use crate::state::State;
|
||||||
use crate::utils::uuid_from_u32_quartet;
|
use crate::utils::uuid_from_u32_quartet;
|
||||||
|
@ -125,7 +126,10 @@ pub unsafe extern "C" fn set_shape_kind_rect() {
|
||||||
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
|
|
||||||
if let Some(shape) = state.current_shape() {
|
if let Some(shape) = state.current_shape() {
|
||||||
shape.set_kind(Kind::Rect(math::Rect::new_empty()));
|
match shape.kind() {
|
||||||
|
Kind::Rect(_, _) => {}
|
||||||
|
_ => shape.set_kind(Kind::Rect(math::Rect::new_empty(), None)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -502,6 +506,14 @@ pub extern "C" fn clear_shape_strokes() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn set_shape_corners(r1: f32, r2: f32, r3: f32, r4: f32) {
|
||||||
|
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
|
||||||
|
if let Some(shape) = state.current_shape() {
|
||||||
|
shape.set_corners((r1, r2, r3, r4))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
init_gl();
|
init_gl();
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,9 +21,12 @@ use matrix::*;
|
||||||
pub use paths::*;
|
pub use paths::*;
|
||||||
pub use strokes::*;
|
pub use strokes::*;
|
||||||
|
|
||||||
|
pub type CornerRadius = skia::Point;
|
||||||
|
pub type Corners = [CornerRadius; 4];
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Kind {
|
pub enum Kind {
|
||||||
Rect(math::Rect),
|
Rect(math::Rect, Option<Corners>),
|
||||||
Circle(math::Rect),
|
Circle(math::Rect),
|
||||||
Path(Path),
|
Path(Path),
|
||||||
Bool(BoolType, Path),
|
Bool(BoolType, Path),
|
||||||
|
@ -54,7 +57,7 @@ impl Shape {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
children: Vec::<Uuid>::new(),
|
children: Vec::<Uuid>::new(),
|
||||||
kind: Kind::Rect(math::Rect::new_empty()),
|
kind: Kind::Rect(math::Rect::new_empty(), None),
|
||||||
selrect: math::Rect::new_empty(),
|
selrect: math::Rect::new_empty(),
|
||||||
transform: Matrix::identity(),
|
transform: Matrix::identity(),
|
||||||
rotation: 0.,
|
rotation: 0.,
|
||||||
|
@ -75,8 +78,8 @@ impl Shape {
|
||||||
pub fn set_selrect(&mut self, left: f32, top: f32, right: f32, bottom: f32) {
|
pub fn set_selrect(&mut self, left: f32, top: f32, right: f32, bottom: f32) {
|
||||||
self.selrect.set_ltrb(left, top, right, bottom);
|
self.selrect.set_ltrb(left, top, right, bottom);
|
||||||
match self.kind {
|
match self.kind {
|
||||||
Kind::Rect(_) => {
|
Kind::Rect(_, corners) => {
|
||||||
self.kind = Kind::Rect(self.selrect.to_owned());
|
self.kind = Kind::Rect(self.selrect.to_owned(), corners);
|
||||||
}
|
}
|
||||||
Kind::Circle(_) => {
|
Kind::Circle(_) => {
|
||||||
self.kind = Kind::Circle(self.selrect.to_owned());
|
self.kind = Kind::Circle(self.selrect.to_owned());
|
||||||
|
@ -206,6 +209,27 @@ impl Shape {
|
||||||
self.kind = kind;
|
self.kind = kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_corners(&mut self, raw_corners: (f32, f32, f32, f32)) {
|
||||||
|
let (r1, r2, r3, r4) = raw_corners;
|
||||||
|
let are_straight_corners = r1.abs() <= f32::EPSILON
|
||||||
|
&& r2.abs() <= f32::EPSILON
|
||||||
|
&& r3.abs() <= f32::EPSILON
|
||||||
|
&& r4.abs() <= f32::EPSILON;
|
||||||
|
|
||||||
|
let corners = if are_straight_corners {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some([
|
||||||
|
(r1, r1).into(),
|
||||||
|
(r2, r2).into(),
|
||||||
|
(r3, r3).into(),
|
||||||
|
(r4, r4).into(),
|
||||||
|
])
|
||||||
|
};
|
||||||
|
|
||||||
|
self.kind = Kind::Rect(self.selrect, corners);
|
||||||
|
}
|
||||||
|
|
||||||
fn to_path_transform(&self) -> Option<skia::Matrix> {
|
fn to_path_transform(&self) -> Option<skia::Matrix> {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
Kind::Path(_) | Kind::Bool(_, _) => {
|
Kind::Path(_) | Kind::Bool(_, _) => {
|
||||||
|
|
|
@ -171,6 +171,7 @@ impl Fill {
|
||||||
p.set_shader(gradient.to_linear_shader(&rect));
|
p.set_shader(gradient.to_linear_shader(&rect));
|
||||||
p.set_alpha((gradient.opacity * 255.) as u8);
|
p.set_alpha((gradient.opacity * 255.) as u8);
|
||||||
p.set_style(skia::PaintStyle::Fill);
|
p.set_style(skia::PaintStyle::Fill);
|
||||||
|
p.set_anti_alias(true);
|
||||||
p.set_blend_mode(skia::BlendMode::SrcOver);
|
p.set_blend_mode(skia::BlendMode::SrcOver);
|
||||||
p
|
p
|
||||||
}
|
}
|
||||||
|
@ -179,6 +180,7 @@ impl Fill {
|
||||||
p.set_shader(gradient.to_radial_shader(&rect));
|
p.set_shader(gradient.to_radial_shader(&rect));
|
||||||
p.set_alpha((gradient.opacity * 255.) as u8);
|
p.set_alpha((gradient.opacity * 255.) as u8);
|
||||||
p.set_style(skia::PaintStyle::Fill);
|
p.set_style(skia::PaintStyle::Fill);
|
||||||
|
p.set_anti_alias(true);
|
||||||
p.set_blend_mode(skia::BlendMode::SrcOver);
|
p.set_blend_mode(skia::BlendMode::SrcOver);
|
||||||
p
|
p
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use skia_safe as skia;
|
use skia_safe::{self as skia, RRect};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::{BlurType, Fill, Image, Kind, Path, Shape, Stroke, StrokeCap, StrokeKind};
|
use super::{BlurType, Corners, Fill, Image, Kind, Path, Shape, Stroke, StrokeCap, StrokeKind};
|
||||||
use crate::math::Rect;
|
use crate::math::Rect;
|
||||||
use crate::render::{ImageStore, Renderable};
|
use crate::render::{ImageStore, Renderable};
|
||||||
|
|
||||||
|
@ -111,9 +111,13 @@ fn render_fill(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(_, Kind::Rect(rect)) => {
|
(_, Kind::Rect(rect, None)) => {
|
||||||
surface.canvas().draw_rect(rect, &fill.to_paint(&selrect));
|
surface.canvas().draw_rect(rect, &fill.to_paint(&selrect));
|
||||||
}
|
}
|
||||||
|
(_, Kind::Rect(rect, Some(corners))) => {
|
||||||
|
let rrect = RRect::new_rect_radii(rect, corners);
|
||||||
|
surface.canvas().draw_rrect(rrect, &fill.to_paint(&selrect));
|
||||||
|
}
|
||||||
(_, Kind::Circle(rect)) => {
|
(_, Kind::Circle(rect)) => {
|
||||||
surface.canvas().draw_oval(rect, &fill.to_paint(&selrect));
|
surface.canvas().draw_oval(rect, &fill.to_paint(&selrect));
|
||||||
}
|
}
|
||||||
|
@ -148,7 +152,9 @@ fn render_stroke(
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match kind {
|
match kind {
|
||||||
Kind::Rect(rect) => draw_stroke_on_rect(surface.canvas(), stroke, rect, &selrect),
|
Kind::Rect(rect, corners) => {
|
||||||
|
draw_stroke_on_rect(surface.canvas(), stroke, rect, &selrect, corners)
|
||||||
|
}
|
||||||
Kind::Circle(rect) => draw_stroke_on_circle(surface.canvas(), stroke, rect, &selrect),
|
Kind::Circle(rect) => draw_stroke_on_circle(surface.canvas(), stroke, rect, &selrect),
|
||||||
Kind::Path(path) | Kind::Bool(_, path) => {
|
Kind::Path(path) | Kind::Bool(_, path) => {
|
||||||
draw_stroke_on_path(surface.canvas(), stroke, path, &selrect, path_transform);
|
draw_stroke_on_path(surface.canvas(), stroke, path, &selrect, path_transform);
|
||||||
|
@ -157,17 +163,34 @@ fn render_stroke(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_stroke_on_rect(canvas: &skia::Canvas, stroke: &Stroke, rect: &Rect, selrect: &Rect) {
|
fn draw_stroke_on_rect(
|
||||||
// Draw the different kind of strokes for a rect is perry straightforward, we just need apply a stroke to:
|
canvas: &skia::Canvas,
|
||||||
|
stroke: &Stroke,
|
||||||
|
rect: &Rect,
|
||||||
|
selrect: &Rect,
|
||||||
|
corners: &Option<Corners>,
|
||||||
|
) {
|
||||||
|
// Draw the different kind of strokes for a rect is straightforward, we just need apply a stroke to:
|
||||||
// - The same rect if it's a center stroke
|
// - The same rect if it's a center stroke
|
||||||
// - A bigger rect if it's an outer stroke
|
// - A bigger rect if it's an outer stroke
|
||||||
// - A smaller rect if it's an outer stroke
|
// - A smaller rect if it's an outer stroke
|
||||||
let stroke_rect = stroke.outer_rect(rect);
|
let stroke_rect = stroke.outer_rect(rect);
|
||||||
canvas.draw_rect(&stroke_rect, &stroke.to_paint(selrect));
|
let paint = stroke.to_paint(selrect);
|
||||||
|
|
||||||
|
match corners {
|
||||||
|
Some(radii) => {
|
||||||
|
let radii = stroke.outer_corners(radii);
|
||||||
|
let rrect = RRect::new_rect_radii(stroke_rect, &radii);
|
||||||
|
canvas.draw_rrect(rrect, &paint);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
canvas.draw_rect(&stroke_rect, &paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_stroke_on_circle(canvas: &skia::Canvas, stroke: &Stroke, rect: &Rect, selrect: &Rect) {
|
fn draw_stroke_on_circle(canvas: &skia::Canvas, stroke: &Stroke, rect: &Rect, selrect: &Rect) {
|
||||||
// Draw the different kind of strokes for an oval is perry straightforward, we just need apply a stroke to:
|
// Draw the different kind of strokes for an oval is straightforward, we just need apply a stroke to:
|
||||||
// - The same oval if it's a center stroke
|
// - The same oval if it's a center stroke
|
||||||
// - A bigger oval if it's an outer stroke
|
// - A bigger oval if it's an outer stroke
|
||||||
// - A smaller oval if it's an outer stroke
|
// - A smaller oval if it's an outer stroke
|
||||||
|
@ -451,9 +474,13 @@ pub fn draw_image_fill_in_container(
|
||||||
|
|
||||||
// Set the clipping rectangle to the container bounds
|
// Set the clipping rectangle to the container bounds
|
||||||
match kind {
|
match kind {
|
||||||
Kind::Rect(_) => {
|
Kind::Rect(_, None) => {
|
||||||
canvas.clip_rect(container, skia::ClipOp::Intersect, true);
|
canvas.clip_rect(container, skia::ClipOp::Intersect, true);
|
||||||
}
|
}
|
||||||
|
Kind::Rect(_, Some(corners)) => {
|
||||||
|
let rrect = RRect::new_rect_radii(container, corners);
|
||||||
|
canvas.clip_rrect(rrect, skia::ClipOp::Intersect, true);
|
||||||
|
}
|
||||||
Kind::Circle(_) => {
|
Kind::Circle(_) => {
|
||||||
let mut oval_path = skia::Path::new();
|
let mut oval_path = skia::Path::new();
|
||||||
oval_path.add_oval(container, None);
|
oval_path.add_oval(container, None);
|
||||||
|
@ -494,7 +521,9 @@ pub fn draw_image_stroke_in_container(
|
||||||
) {
|
) {
|
||||||
let outer_rect = stroke.outer_rect(container);
|
let outer_rect = stroke.outer_rect(container);
|
||||||
match kind {
|
match kind {
|
||||||
Kind::Rect(rect) => draw_stroke_on_rect(canvas, stroke, rect, &outer_rect),
|
Kind::Rect(rect, corners) => {
|
||||||
|
draw_stroke_on_rect(canvas, stroke, rect, &outer_rect, corners)
|
||||||
|
}
|
||||||
Kind::Circle(rect) => draw_stroke_on_circle(canvas, stroke, rect, &outer_rect),
|
Kind::Circle(rect) => draw_stroke_on_circle(canvas, stroke, rect, &outer_rect),
|
||||||
Kind::Path(p) | Kind::Bool(_, p) => {
|
Kind::Path(p) | Kind::Bool(_, p) => {
|
||||||
let mut path = p.to_skia_path();
|
let mut path = p.to_skia_path();
|
||||||
|
|
|
@ -2,6 +2,8 @@ use crate::math;
|
||||||
use crate::shapes::fills::Fill;
|
use crate::shapes::fills::Fill;
|
||||||
use skia_safe as skia;
|
use skia_safe as skia;
|
||||||
|
|
||||||
|
use super::Corners;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum StrokeStyle {
|
pub enum StrokeStyle {
|
||||||
Solid,
|
Solid,
|
||||||
|
@ -139,6 +141,20 @@ impl Stroke {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn outer_corners(&self, corners: &Corners) -> Corners {
|
||||||
|
let offset = match self.kind {
|
||||||
|
StrokeKind::CenterStroke => 0.0,
|
||||||
|
StrokeKind::InnerStroke => -self.width / 2.0,
|
||||||
|
StrokeKind::OuterStroke => self.width / 2.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut outer = corners.clone();
|
||||||
|
for corner in outer.iter_mut() {
|
||||||
|
corner.offset((offset, offset))
|
||||||
|
}
|
||||||
|
outer
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_paint(&self, rect: &math::Rect) -> skia::Paint {
|
pub fn to_paint(&self, rect: &math::Rect) -> skia::Paint {
|
||||||
let mut paint = self.fill.to_paint(rect);
|
let mut paint = self.fill.to_paint(rect);
|
||||||
paint.set_style(skia::PaintStyle::Stroke);
|
paint.set_style(skia::PaintStyle::Stroke);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue