T25c Generative: canvas
InpaintCanvas lays a mask over a picture: brush strokes in shares of the picture, rastered into a 320-column grid where a later stroke wins and an erasing one clears, painted as merged rectangles snapped to device pixels. A ring follows the pointer at the brush's size; [ and ] size the brush and X swaps it when focused. MaskBrush sets paint or erase and the size, with Undo and Clear waiting for a stroke. Pointer strokes move to forms::pen, which SignaturePad and the canvas share.
This commit is contained in:
1 parent
c349a025e5
commit
02f0a8deaf
11 files changed
+853
-57
No files matched your search
@@ -95,6 +95,7 @@ pub use rating::Rating;
|
||||
pub use search::SearchInput;
|
||||
pub use select::Select;
|
||||
pub(crate) use select::{Listing, field_button, field_text, listing};
|
||||
pub(crate) use signature::{Drawing, pen};
|
||||
pub use signature::{SignaturePad, Stroke};
|
||||
pub use slider::{RangeSlider, Slider};
|
||||
pub use stepper::Stepper;
|
||||
|
||||
+73
-55
@@ -1,9 +1,10 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
App, Bounds, Corners, DragMoveEvent, ElementId, EmptyView, EntityId, Hsla, InteractiveElement,
|
||||
IntoElement, MouseButton, ParentElement, Path, PathBuilder, Pixels, Point, RenderOnce,
|
||||
StatefulInteractiveElement, Styled, Window, canvas, div, fill, point, prelude::*, size,
|
||||
App, Bounds, Corners, Div, DragMoveEvent, ElementId, EmptyView, Entity, EntityId, Hsla,
|
||||
InteractiveElement, IntoElement, MouseButton, ParentElement, Path, PathBuilder, Pixels, Point,
|
||||
RenderOnce, Stateful, StatefulInteractiveElement, Styled, Window, canvas, div, fill, point,
|
||||
prelude::*, size,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -16,17 +17,17 @@ use crate::{
|
||||
pub type Stroke = Vec<Point<Pixels>>;
|
||||
|
||||
type OnStrokes = Rc<dyn Fn(&[Stroke], &mut Window, &mut App)>;
|
||||
type Lift = Rc<dyn Fn(Point<Pixels>, &mut Window, &mut App)>;
|
||||
type Lift = Rc<dyn Fn(Stroke, &mut Window, &mut App)>;
|
||||
|
||||
struct Pen {
|
||||
owner: EntityId,
|
||||
}
|
||||
|
||||
/// Where the pad sits, and the stroke under the pen.
|
||||
/// Where a pad sits, and the stroke under the pen.
|
||||
#[derive(Default)]
|
||||
struct Drawing {
|
||||
bounds: Bounds<Pixels>,
|
||||
stroke: Stroke,
|
||||
pub(crate) struct Drawing {
|
||||
pub bounds: Bounds<Pixels>,
|
||||
pub stroke: Stroke,
|
||||
}
|
||||
|
||||
impl Drawing {
|
||||
@@ -67,6 +68,63 @@ fn ink(points: &[Point<Pixels>], origin: Point<Pixels>, width: Pixels) -> Option
|
||||
Some(path.build().expect("a stroke is a simple open path"))
|
||||
}
|
||||
|
||||
/// Turns a press and drag on `pad` into a stroke of points from its corner, kept inside it; `lift` gets the stroke as the pen leaves. The pad's owner keeps `drawing`'s bounds.
|
||||
pub(crate) fn pen(
|
||||
pad: Stateful<Div>,
|
||||
drawing: &Entity<Drawing>,
|
||||
lift: impl Fn(Stroke, &mut Window, &mut App) + 'static,
|
||||
) -> Stateful<Div> {
|
||||
let owner = drawing.entity_id();
|
||||
let lift: Lift = Rc::new(lift);
|
||||
let finish = {
|
||||
let drawing = drawing.clone();
|
||||
move |at: Point<Pixels>, window: &mut Window, cx: &mut App| {
|
||||
let stroke = drawing.update(cx, |drawing, cx| {
|
||||
if !drawing.stroke.is_empty() {
|
||||
drawing.reach(at);
|
||||
}
|
||||
cx.notify();
|
||||
std::mem::take(&mut drawing.stroke)
|
||||
});
|
||||
if !stroke.is_empty() {
|
||||
lift(stroke, window, cx);
|
||||
}
|
||||
}
|
||||
};
|
||||
let (press, start, moving) = (drawing.clone(), drawing.clone(), drawing.clone());
|
||||
let up = Rc::new(finish);
|
||||
let leave = up.clone();
|
||||
pad.on_mouse_down(MouseButton::Left, move |event, _, cx| {
|
||||
press.update(cx, |drawing, cx| {
|
||||
drawing.stroke = vec![local(drawing.bounds, event.position)];
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.on_drag(Pen { owner }, move |_, _, window, cx| {
|
||||
let at = window.mouse_position();
|
||||
start.update(cx, |drawing, cx| {
|
||||
drawing.reach(at);
|
||||
cx.notify();
|
||||
});
|
||||
cx.new(|_| EmptyView)
|
||||
})
|
||||
.on_drag_move(move |event: &DragMoveEvent<Pen>, _, cx| {
|
||||
if event.drag(cx).owner != owner {
|
||||
return;
|
||||
}
|
||||
moving.update(cx, |drawing, cx| {
|
||||
drawing.reach(event.event.position);
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.on_mouse_up(MouseButton::Left, move |event, window, cx| {
|
||||
up(event.position, window, cx)
|
||||
})
|
||||
.on_mouse_up_out(MouseButton::Left, move |event, window, cx| {
|
||||
leave(event.position, window, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn paint(
|
||||
strokes: &[Stroke],
|
||||
bounds: Bounds<Pixels>,
|
||||
@@ -120,7 +178,6 @@ impl RenderOnce for SignaturePad {
|
||||
let strokes = use_seeded((self.id.clone(), "strokes"), self.strokes, window, cx);
|
||||
let drawing =
|
||||
window.use_keyed_state((self.id.clone(), "drawing"), cx, |_, _| Drawing::default());
|
||||
let owner = drawing.entity_id();
|
||||
let set: OnStrokes = {
|
||||
let (id, strokes, on_change) = (self.id.clone(), strokes.clone(), self.on_change);
|
||||
Rc::new(move |next, window, cx| {
|
||||
@@ -134,22 +191,13 @@ impl RenderOnce for SignaturePad {
|
||||
}
|
||||
})
|
||||
};
|
||||
let lift: Lift = {
|
||||
let (strokes, drawing, set) = (strokes.clone(), drawing.clone(), set.clone());
|
||||
Rc::new(move |at, window, cx| {
|
||||
let stroke = drawing.update(cx, |drawing, _| {
|
||||
if !drawing.stroke.is_empty() {
|
||||
drawing.reach(at);
|
||||
}
|
||||
std::mem::take(&mut drawing.stroke)
|
||||
});
|
||||
if stroke.is_empty() {
|
||||
return;
|
||||
}
|
||||
let lift = {
|
||||
let (strokes, set) = (strokes.clone(), set.clone());
|
||||
move |stroke: Stroke, window: &mut Window, cx: &mut App| {
|
||||
let mut next = strokes.read(cx).value.clone();
|
||||
next.push(stroke);
|
||||
set(&next, window, cx);
|
||||
})
|
||||
}
|
||||
};
|
||||
let theme = cx.theme();
|
||||
let colors = &theme.colors;
|
||||
@@ -158,9 +206,7 @@ impl RenderOnce for SignaturePad {
|
||||
let empty = strokes.read(cx).value.is_empty();
|
||||
let mut shown = strokes.read(cx).value.clone();
|
||||
shown.push(drawing.read(cx).stroke.clone());
|
||||
let (measure, press, start, pen) =
|
||||
(drawing.clone(), drawing.clone(), drawing.clone(), drawing);
|
||||
let (up, out) = (lift.clone(), lift);
|
||||
let measure = drawing.clone();
|
||||
let color = colors.fg;
|
||||
let pad = div()
|
||||
.id(self.id.clone())
|
||||
@@ -203,36 +249,8 @@ impl RenderOnce for SignaturePad {
|
||||
.top_0()
|
||||
.left_0()
|
||||
.size_full(),
|
||||
)
|
||||
.on_mouse_down(MouseButton::Left, move |event, _, cx| {
|
||||
press.update(cx, |drawing, cx| {
|
||||
drawing.stroke = vec![local(drawing.bounds, event.position)];
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.on_drag(Pen { owner }, move |_, _, window, cx| {
|
||||
let at = window.mouse_position();
|
||||
start.update(cx, |drawing, cx| {
|
||||
drawing.reach(at);
|
||||
cx.notify();
|
||||
});
|
||||
cx.new(|_| EmptyView)
|
||||
})
|
||||
.on_drag_move(move |event: &DragMoveEvent<Pen>, _, cx| {
|
||||
if event.drag(cx).owner != owner {
|
||||
return;
|
||||
}
|
||||
pen.update(cx, |drawing, cx| {
|
||||
drawing.reach(event.event.position);
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.on_mouse_up(MouseButton::Left, move |event, window, cx| {
|
||||
up(event.position, window, cx)
|
||||
})
|
||||
.on_mouse_up_out(MouseButton::Left, move |event, window, cx| {
|
||||
out(event.position, window, cx)
|
||||
});
|
||||
);
|
||||
let pad = pen(pad, &drawing, lift);
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
|
||||
Reference in new issue
Block a user