Skip to content

Commit 5fd1a24

Browse files
timon-schelling0HyperCubeKeavon
authored
Desktop: Add Eyedropper tool support with native Vello (#3684)
* mostly done * fix * kinda works but tilt and flip broken * fix footprint Co-authored-by: James Lindsay <78500760+0HyperCube@users.noreply.github.com> * Code review * fix cursor hiding * Remove console.log --------- Co-authored-by: James Lindsay <78500760+0HyperCube@users.noreply.github.com> Co-authored-by: Keavon Chambers <keavon@keavon.com>
1 parent 19e9af3 commit 5fd1a24

13 files changed

Lines changed: 341 additions & 136 deletions

File tree

desktop/src/cef/internal/display_handler.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ impl<H: CefEventHandler> ImplDisplayHandler for DisplayHandlerImpl<H> {
7878
CT_PROGRESS => CursorIcon::Progress,
7979
CT_NODROP => CursorIcon::NoDrop,
8080
CT_COPY => CursorIcon::Copy,
81-
CT_NONE => CursorIcon::Default,
8281
CT_NOTALLOWED => CursorIcon::NotAllowed,
8382
CT_ZOOMIN => CursorIcon::ZoomIn,
8483
CT_ZOOMOUT => CursorIcon::ZoomOut,
@@ -91,6 +90,10 @@ impl<H: CefEventHandler> ImplDisplayHandler for DisplayHandlerImpl<H> {
9190
CT_DND_COPY => CursorIcon::Copy,
9291
CT_DND_LINK => CursorIcon::Alias,
9392
CT_NUM_VALUES => CursorIcon::Default,
93+
CT_NONE => {
94+
self.event_handler.cursor_change(crate::window::Cursor::None);
95+
return 1; // We handled the cursor change.
96+
}
9497
_ => CursorIcon::Default,
9598
};
9699

desktop/src/window.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,12 @@ impl Window {
169169
};
170170
custom_cursor.into()
171171
}
172+
Cursor::None => {
173+
self.winit_window.set_cursor_visible(false);
174+
return;
175+
}
172176
};
177+
self.winit_window.set_cursor_visible(true);
173178
self.winit_window.set_cursor(cursor);
174179
}
175180

@@ -215,6 +220,7 @@ impl Window {
215220
pub(crate) enum Cursor {
216221
Icon(CursorIcon),
217222
Custom(CustomCursorSource),
223+
None,
218224
}
219225
impl From<CursorIcon> for Cursor {
220226
fn from(icon: CursorIcon) -> Self {

editor/src/consts.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ pub const LINE_ROTATE_SNAP_ANGLE: f64 = 15.;
124124
pub const BRUSH_SIZE_CHANGE_KEYBOARD: f64 = 5.;
125125
pub const DEFAULT_BRUSH_SIZE: f64 = 20.;
126126

127+
// EYEDROPPER TOOL
128+
pub const EYEDROPPER_PREVIEW_AREA_RESOLUTION: u32 = 11;
129+
127130
// GIZMOS
128131
pub const POINT_RADIUS_HANDLE_SNAP_THRESHOLD: f64 = 8.;
129132
pub const POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD: f64 = 7.9;

editor/src/messages/frontend/frontend_message.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::utility_types::{DocumentDetails, MouseCursorIcon, OpenDocument};
22
use crate::messages::app_window::app_window_message_handler::AppWindowPlatform;
3+
use crate::messages::frontend::utility_types::EyedropperPreviewImage;
34
use crate::messages::input_mapper::utility_types::misc::ActionShortcut;
45
use crate::messages::layout::utility_types::widget_prelude::*;
56
use crate::messages::portfolio::document::node_graph::utility_types::{
@@ -253,6 +254,7 @@ pub enum FrontendMessage {
253254
multiplier: (f64, f64),
254255
},
255256
UpdateEyedropperSamplingState {
257+
image: Option<EyedropperPreviewImage>,
256258
#[serde(rename = "mousePosition")]
257259
mouse_position: Option<(f64, f64)>,
258260
#[serde(rename = "primaryColor")]

editor/src/messages/frontend/utility_types.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,10 @@ pub enum ExportBounds {
6262
Selection,
6363
Artboard(LayerNodeIdentifier),
6464
}
65+
66+
#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
67+
pub struct EyedropperPreviewImage {
68+
pub data: Vec<u8>,
69+
pub width: u32,
70+
pub height: u32,
71+
}

editor/src/messages/portfolio/portfolio_message.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ pub enum PortfolioMessage {
148148
document_id: DocumentId,
149149
ignore_hash: bool,
150150
},
151+
SubmitEyedropperPreviewRender,
151152
ToggleResetNodesToDefinitionsOnOpen,
152153
ToggleFocusDocument,
153154
ToggleDataPanelOpen,

editor/src/messages/portfolio/portfolio_message_handler.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,6 +1178,41 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
11781178
Ok(message) => responses.add_front(message),
11791179
}
11801180
}
1181+
#[cfg(not(target_family = "wasm"))]
1182+
PortfolioMessage::SubmitEyedropperPreviewRender => {
1183+
use crate::consts::EYEDROPPER_PREVIEW_AREA_RESOLUTION;
1184+
1185+
let Some(document_id) = self.active_document_id else { return };
1186+
let Some(document) = self.documents.get_mut(&document_id) else { return };
1187+
1188+
let resolution = glam::UVec2::splat(EYEDROPPER_PREVIEW_AREA_RESOLUTION);
1189+
1190+
let preview_offset_in_viewport = ipp.mouse.position - (glam::DVec2::splat(EYEDROPPER_PREVIEW_AREA_RESOLUTION as f64 / 2.));
1191+
let preview_offset_in_viewport = DAffine2::from_translation(preview_offset_in_viewport);
1192+
1193+
let document_to_viewport = document.metadata().document_to_viewport;
1194+
1195+
let preview_transform = preview_offset_in_viewport.inverse() * document_to_viewport;
1196+
let pointer_position = document_to_viewport.inverse().transform_point2(ipp.mouse.position);
1197+
1198+
let result = self
1199+
.executor
1200+
.submit_eyedropper_preview(document_id, preview_transform, pointer_position, resolution, timing_information);
1201+
1202+
match result {
1203+
Err(description) => {
1204+
responses.add(DialogMessage::DisplayDialogError {
1205+
title: "Unable to update node graph".to_string(),
1206+
description,
1207+
});
1208+
}
1209+
Ok(message) => responses.add_front(message),
1210+
}
1211+
}
1212+
#[cfg(target_family = "wasm")]
1213+
PortfolioMessage::SubmitEyedropperPreviewRender => {
1214+
// TODO: Currently for Wasm, this is implemented through SVG rendering but the Eyedropper tool doesn't work at all when Vello is enabled as the renderer
1215+
}
11811216
PortfolioMessage::ToggleFocusDocument => {
11821217
self.focus_document = !self.focus_document;
11831218
responses.add(MenuBarMessage::SendLayout);

editor/src/messages/tool/tool_messages/eyedropper_tool.rs

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::tool_prelude::*;
2+
use crate::messages::frontend::utility_types::EyedropperPreviewImage;
23
use crate::messages::tool::utility_types::DocumentToolData;
34

45
#[derive(Default, ExtractField)]
@@ -19,6 +20,8 @@ pub enum EyedropperToolMessage {
1920
PointerMove,
2021
SampleSecondaryColorBegin,
2122
SampleSecondaryColorEnd,
23+
24+
PreviewImage { data: Vec<u8>, width: u32, height: u32 },
2225
}
2326

2427
impl ToolMetadata for EyedropperTool {
@@ -42,6 +45,17 @@ impl LayoutHolder for EyedropperTool {
4245
#[message_handler_data]
4346
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for EyedropperTool {
4447
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
48+
if let ToolMessage::Eyedropper(EyedropperToolMessage::PreviewImage { data, width, height }) = message {
49+
let image = EyedropperPreviewImage { data, width, height };
50+
51+
update_cursor_preview_common(responses, Some(image), context.input, context.global_tool_data, self.data.color_choice.clone());
52+
53+
if !self.data.preview {
54+
disable_cursor_preview(responses, &mut self.data);
55+
}
56+
return;
57+
}
58+
4559
self.fsm_state.process_event(message, &mut self.data, context, &(), responses, true);
4660
}
4761

@@ -74,13 +88,16 @@ enum EyedropperToolFsmState {
7488
}
7589

7690
#[derive(Clone, Debug, Default)]
77-
struct EyedropperToolData {}
91+
struct EyedropperToolData {
92+
preview: bool,
93+
color_choice: Option<String>,
94+
}
7895

7996
impl Fsm for EyedropperToolFsmState {
8097
type ToolData = EyedropperToolData;
8198
type ToolOptions = ();
8299

83-
fn transition(self, event: ToolMessage, _tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionMessageContext, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
100+
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionMessageContext, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
84101
let ToolActionMessageContext {
85102
global_tool_data, input, viewport, ..
86103
} = tool_action_data;
@@ -89,7 +106,7 @@ impl Fsm for EyedropperToolFsmState {
89106
match (self, event) {
90107
// Ready -> Sampling
91108
(EyedropperToolFsmState::Ready, mouse_down) if matches!(mouse_down, EyedropperToolMessage::SamplePrimaryColorBegin | EyedropperToolMessage::SampleSecondaryColorBegin) => {
92-
update_cursor_preview(responses, input, global_tool_data, None);
109+
update_cursor_preview(responses, tool_data, input, global_tool_data, None);
93110

94111
if mouse_down == EyedropperToolMessage::SamplePrimaryColorBegin {
95112
EyedropperToolFsmState::SamplingPrimary
@@ -101,24 +118,24 @@ impl Fsm for EyedropperToolFsmState {
101118
(EyedropperToolFsmState::SamplingPrimary | EyedropperToolFsmState::SamplingSecondary, EyedropperToolMessage::PointerMove) => {
102119
let mouse_position = viewport.logical(input.mouse.position);
103120
if viewport.is_in_bounds(mouse_position + viewport.offset()) {
104-
update_cursor_preview(responses, input, global_tool_data, None);
121+
update_cursor_preview(responses, tool_data, input, global_tool_data, None);
105122
} else {
106-
disable_cursor_preview(responses);
123+
disable_cursor_preview(responses, tool_data);
107124
}
108125

109126
self
110127
}
111128
// Sampling -> Ready
112129
(EyedropperToolFsmState::SamplingPrimary, EyedropperToolMessage::SamplePrimaryColorEnd) | (EyedropperToolFsmState::SamplingSecondary, EyedropperToolMessage::SampleSecondaryColorEnd) => {
113130
let set_color_choice = if self == EyedropperToolFsmState::SamplingPrimary { "Primary" } else { "Secondary" }.to_string();
114-
update_cursor_preview(responses, input, global_tool_data, Some(set_color_choice));
115-
disable_cursor_preview(responses);
131+
update_cursor_preview(responses, tool_data, input, global_tool_data, Some(set_color_choice));
132+
disable_cursor_preview(responses, tool_data);
116133

117134
EyedropperToolFsmState::Ready
118135
}
119136
// Any -> Ready
120137
(_, EyedropperToolMessage::Abort) => {
121-
disable_cursor_preview(responses);
138+
disable_cursor_preview(responses, tool_data);
122139

123140
EyedropperToolFsmState::Ready
124141
}
@@ -151,17 +168,53 @@ impl Fsm for EyedropperToolFsmState {
151168
}
152169
}
153170

154-
fn disable_cursor_preview(responses: &mut VecDeque<Message>) {
171+
fn disable_cursor_preview(responses: &mut VecDeque<Message>, tool_data: &mut EyedropperToolData) {
172+
tool_data.preview = false;
155173
responses.add(FrontendMessage::UpdateEyedropperSamplingState {
174+
image: None,
156175
mouse_position: None,
157176
primary_color: "".into(),
158177
secondary_color: "".into(),
159178
set_color_choice: None,
160179
});
161180
}
162181

163-
fn update_cursor_preview(responses: &mut VecDeque<Message>, input: &InputPreprocessorMessageHandler, global_tool_data: &DocumentToolData, set_color_choice: Option<String>) {
182+
#[cfg(not(target_family = "wasm"))]
183+
fn update_cursor_preview(
184+
responses: &mut VecDeque<Message>,
185+
tool_data: &mut EyedropperToolData,
186+
_input: &InputPreprocessorMessageHandler,
187+
_global_tool_data: &DocumentToolData,
188+
set_color_choice: Option<String>,
189+
) {
190+
tool_data.preview = true;
191+
tool_data.color_choice = set_color_choice;
192+
responses.add(PortfolioMessage::SubmitEyedropperPreviewRender);
193+
}
194+
195+
#[cfg(target_family = "wasm")]
196+
fn update_cursor_preview(
197+
responses: &mut VecDeque<Message>,
198+
tool_data: &mut EyedropperToolData,
199+
input: &InputPreprocessorMessageHandler,
200+
global_tool_data: &DocumentToolData,
201+
set_color_choice: Option<String>,
202+
) {
203+
tool_data.preview = true;
204+
tool_data.color_choice = set_color_choice.clone();
205+
206+
update_cursor_preview_common(responses, None, input, global_tool_data, set_color_choice);
207+
}
208+
209+
fn update_cursor_preview_common(
210+
responses: &mut VecDeque<Message>,
211+
image: Option<EyedropperPreviewImage>,
212+
input: &InputPreprocessorMessageHandler,
213+
global_tool_data: &DocumentToolData,
214+
set_color_choice: Option<String>,
215+
) {
164216
responses.add(FrontendMessage::UpdateEyedropperSamplingState {
217+
image,
165218
mouse_position: Some(input.mouse.position.into()),
166219
primary_color: "#".to_string() + global_tool_data.primary_color.to_rgb_hex_srgb().as_str(),
167220
secondary_color: "#".to_string() + global_tool_data.secondary_color.to_rgb_hex_srgb().as_str(),

0 commit comments

Comments
 (0)