mirror of
https://github.com/penpot/penpot.git
synced 2026-07-31 18:36:18 +00:00
🐛 Fix gradients and text selection (#10941)
* 🐛 Add missing text fills * ✨ Keep text leaf selected while editing
This commit is contained in:
parent
03f9393200
commit
a722a33503
@ -12,7 +12,9 @@ pub fn render_overlay(
|
||||
editor_state: &TextEditorState,
|
||||
shape: &Shape,
|
||||
) {
|
||||
if !editor_state.has_focus {
|
||||
let has_selection = editor_state.selection.is_selection();
|
||||
|
||||
if !editor_state.has_focus && !has_selection {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -25,12 +27,12 @@ pub fn render_overlay(
|
||||
canvas.scale((zoom, zoom));
|
||||
canvas.translate((-viewbox.area.left, -viewbox.area.top));
|
||||
|
||||
if editor_state.selection.is_selection() {
|
||||
if has_selection {
|
||||
// With an active selection there is no blinking caret (the caret is one
|
||||
// end of the selection); drawing it would make it toggle on top of the
|
||||
// highlight while the selection is held.
|
||||
render_selection(canvas, editor_state, text_content, shape);
|
||||
} else if editor_state.cursor_visible {
|
||||
} else if editor_state.has_focus && editor_state.cursor_visible {
|
||||
render_cursor(canvas, zoom, options.dpr, editor_state, text_content, shape);
|
||||
}
|
||||
|
||||
|
||||
@ -42,6 +42,29 @@ impl Gradient {
|
||||
self.offsets.extend(offsets);
|
||||
}
|
||||
|
||||
pub fn start(&self) -> (f32, f32) {
|
||||
self.start
|
||||
}
|
||||
|
||||
pub fn end(&self) -> (f32, f32) {
|
||||
self.end
|
||||
}
|
||||
|
||||
pub fn opacity(&self) -> u8 {
|
||||
self.opacity
|
||||
}
|
||||
|
||||
pub fn width(&self) -> f32 {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn stops(&self) -> impl Iterator<Item = (Color, f32)> + '_ {
|
||||
self.colors
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(self.offsets.iter().copied())
|
||||
}
|
||||
|
||||
pub fn to_linear_shader(&self, rect: &Rect) -> Option<skia::Shader> {
|
||||
let start = (
|
||||
rect.left + self.start.0 * rect.width(),
|
||||
@ -126,6 +149,14 @@ impl ImageFill {
|
||||
pub fn keep_aspect_ratio(&self) -> bool {
|
||||
self.keep_aspect_ratio
|
||||
}
|
||||
|
||||
pub fn width(&self) -> i32 {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn height(&self) -> i32 {
|
||||
self.height
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
|
||||
@ -385,11 +385,15 @@ impl TextEditorState {
|
||||
}
|
||||
|
||||
pub fn focus(&mut self, shape_id: Uuid) {
|
||||
let same_shape = self.active_shape_id == Some(shape_id);
|
||||
|
||||
self.has_focus = true;
|
||||
self.active_shape_id = Some(shape_id);
|
||||
self.cursor_visible = true;
|
||||
self.last_blink_time_ms = 0.0;
|
||||
self.selection.reset();
|
||||
if !same_shape {
|
||||
self.selection.reset();
|
||||
}
|
||||
self.is_pointer_selection_active = false;
|
||||
self.is_overtype_mode = false;
|
||||
self.pending_events.clear();
|
||||
|
||||
@ -48,15 +48,15 @@ impl TryFrom<&shapes::Fill> for RawFillData {
|
||||
| (color.b() as u32),
|
||||
}))
|
||||
}
|
||||
shapes::Fill::LinearGradient(_) => {
|
||||
Err("LinearGradient serialization is not implemented".to_string())
|
||||
}
|
||||
shapes::Fill::RadialGradient(_) => {
|
||||
Err("RadialGradient serialization is not implemented".to_string())
|
||||
}
|
||||
shapes::Fill::Image(_) => {
|
||||
Err("Image fill serialization is not implemented".to_string())
|
||||
}
|
||||
shapes::Fill::LinearGradient(linear_gradient) => Ok(RawFillData::Linear(
|
||||
gradient::RawGradientData::from(linear_gradient),
|
||||
)),
|
||||
shapes::Fill::RadialGradient(radial_gradient) => Ok(RawFillData::Radial(
|
||||
gradient::RawGradientData::from(radial_gradient),
|
||||
)),
|
||||
shapes::Fill::Image(image_fill) => Ok(RawFillData::Image(
|
||||
image::RawImageFillData::from(image_fill),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -155,4 +155,36 @@ mod tests {
|
||||
RawFillData::Solid(solid::RawSolidData { color: 0xfffabada })
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gradient_fill_round_trip() {
|
||||
let gradient = shapes::Gradient::new(
|
||||
(0.0, 0.5),
|
||||
(1.0, 0.5),
|
||||
0x80,
|
||||
0.25,
|
||||
&[
|
||||
(shapes::Color::from(0xfffabada), 0.0),
|
||||
(shapes::Color::from(0xff00ff00), 1.0),
|
||||
],
|
||||
);
|
||||
|
||||
let fill = shapes::Fill::LinearGradient(gradient.clone());
|
||||
let raw_fill = RawFillData::try_from(&fill).expect("gradient must be serializable");
|
||||
let bytes = <[u8; RAW_FILL_DATA_SIZE]>::from(raw_fill);
|
||||
|
||||
assert_eq!(bytes[0], 0x01);
|
||||
assert_eq!(shapes::Fill::from(RawFillData::from(bytes)), fill);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_image_fill_round_trip() {
|
||||
let image_fill = shapes::ImageFill::new(crate::uuid::Uuid::nil(), 0x80, 300, 200, true);
|
||||
let fill = shapes::Fill::Image(image_fill);
|
||||
let raw_fill = RawFillData::try_from(&fill).expect("image fill must be serializable");
|
||||
let bytes = <[u8; RAW_FILL_DATA_SIZE]>::from(raw_fill);
|
||||
|
||||
assert_eq!(bytes[0], 0x03);
|
||||
assert_eq!(shapes::Fill::from(RawFillData::from(bytes)), fill);
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,6 +44,41 @@ impl RawStopData {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Gradient> for RawGradientData {
|
||||
fn from(gradient: &Gradient) -> Self {
|
||||
let mut stops = [RawStopData {
|
||||
color: 0,
|
||||
offset: 0.0,
|
||||
}; MAX_GRADIENT_STOPS];
|
||||
|
||||
let mut stop_count: u8 = 0;
|
||||
for (index, (color, offset)) in gradient.stops().take(MAX_GRADIENT_STOPS).enumerate() {
|
||||
stops[index] = RawStopData {
|
||||
color: ((color.a() as u32) << 24)
|
||||
| ((color.r() as u32) << 16)
|
||||
| ((color.g() as u32) << 8)
|
||||
| (color.b() as u32),
|
||||
offset,
|
||||
};
|
||||
stop_count += 1;
|
||||
}
|
||||
|
||||
let (start_x, start_y) = gradient.start();
|
||||
let (end_x, end_y) = gradient.end();
|
||||
|
||||
RawGradientData {
|
||||
start_x,
|
||||
start_y,
|
||||
end_x,
|
||||
end_y,
|
||||
opacity: gradient.opacity(),
|
||||
width: gradient.width(),
|
||||
stop_count,
|
||||
stops,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RawGradientData> for Gradient {
|
||||
fn from(raw_gradient: RawGradientData) -> Self {
|
||||
let stops = raw_gradient
|
||||
|
||||
@ -48,6 +48,29 @@ pub struct RawImageFillData {
|
||||
height: i32,
|
||||
}
|
||||
|
||||
impl From<&ImageFill> for RawImageFillData {
|
||||
fn from(image_fill: &ImageFill) -> Self {
|
||||
let id = image_fill.id();
|
||||
let (a, b, c, d) = crate::utils::uuid_to_u32_quartet(&id);
|
||||
let flags = if image_fill.keep_aspect_ratio() {
|
||||
FLAG_KEEP_ASPECT_RATIO
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
Self {
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d,
|
||||
opacity: image_fill.opacity(),
|
||||
flags,
|
||||
width: image_fill.width(),
|
||||
height: image_fill.height(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RawImageFillData> for ImageFill {
|
||||
fn from(value: RawImageFillData) -> Self {
|
||||
let id = uuid_from_u32_quartet(value.a, value.b, value.c, value.d);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user