Skip to content

Commit 58a53a9

Browse files
committed
Remake Eyedropper tool to sample pixel colors from viewport canvas (#801)
* Remake Eyedropper tool to sample pixel colors from viewport canvas * Bug fixes * Reorder export buttons * Remove the larger primary/secondary ring * Add aborting with Escape
1 parent fe1a03f commit 58a53a9

File tree

17 files changed

+451
-128
lines changed

17 files changed

+451
-128
lines changed

editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ impl PropertyHolder for ExportDialogMessageHandler {
6363
})),
6464
];
6565

66-
let entries = [(FileType::Svg, "SVG"), (FileType::Png, "PNG"), (FileType::Jpg, "JPG")]
66+
let entries = [(FileType::Png, "PNG"), (FileType::Jpg, "JPG"), (FileType::Svg, "SVG")]
6767
.into_iter()
6868
.map(|(val, name)| RadioEntryData {
6969
label: name.into(),

editor/src/messages/frontend/frontend_message.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,16 @@ pub enum FrontendMessage {
157157
size: (f64, f64),
158158
multiplier: (f64, f64),
159159
},
160+
UpdateEyedropperSamplingState {
161+
#[serde(rename = "mousePosition")]
162+
mouse_position: Option<(f64, f64)>,
163+
#[serde(rename = "primaryColor")]
164+
primary_color: String,
165+
#[serde(rename = "secondaryColor")]
166+
secondary_color: String,
167+
#[serde(rename = "setColorChoice")]
168+
set_color_choice: Option<String>,
169+
},
160170
UpdateImageData {
161171
#[serde(rename = "documentId")]
162172
document_id: u64,

editor/src/messages/frontend/utility_types.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub struct FrontendImageData {
2121
pub enum MouseCursorIcon {
2222
#[default]
2323
Default,
24+
None,
2425
ZoomIn,
2526
ZoomOut,
2627
Grabbing,
@@ -36,17 +37,17 @@ pub enum MouseCursorIcon {
3637
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
3738
pub enum FileType {
3839
#[default]
39-
Svg,
4040
Png,
4141
Jpg,
42+
Svg,
4243
}
4344

4445
impl FileType {
4546
pub fn to_mime(self) -> &'static str {
4647
match self {
47-
FileType::Svg => "image/svg+xml",
4848
FileType::Png => "image/png",
4949
FileType::Jpg => "image/jpeg",
50+
FileType::Svg => "image/svg+xml",
5051
}
5152
}
5253
}

editor/src/messages/input_mapper/default_mapping.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,12 @@ pub fn default_mapping() -> Mapping {
9292
entry!(KeyUp(Mmb); action_dispatch=NavigateToolMessage::TransformCanvasEnd),
9393
//
9494
// EyedropperToolMessage
95-
entry!(KeyDown(Lmb); action_dispatch=EyedropperToolMessage::LeftMouseDown),
96-
entry!(KeyDown(Rmb); action_dispatch=EyedropperToolMessage::RightMouseDown),
95+
entry!(PointerMove; action_dispatch=EyedropperToolMessage::PointerMove),
96+
entry!(KeyDown(Lmb); action_dispatch=EyedropperToolMessage::LeftPointerDown),
97+
entry!(KeyDown(Rmb); action_dispatch=EyedropperToolMessage::RightPointerDown),
98+
entry!(KeyUp(Lmb); action_dispatch=EyedropperToolMessage::LeftPointerUp),
99+
entry!(KeyUp(Rmb); action_dispatch=EyedropperToolMessage::RightPointerUp),
100+
entry!(KeyDown(Escape); action_dispatch=EyedropperToolMessage::Abort),
97101
//
98102
// TextToolMessage
99103
entry!(KeyUp(Lmb); action_dispatch=TextToolMessage::Interact),
@@ -170,8 +174,8 @@ pub fn default_mapping() -> Mapping {
170174
entry!(KeyDown(Enter); action_dispatch=SplineToolMessage::Confirm),
171175
//
172176
// FillToolMessage
173-
entry!(KeyDown(Lmb); action_dispatch=FillToolMessage::LeftMouseDown),
174-
entry!(KeyDown(Rmb); action_dispatch=FillToolMessage::RightMouseDown),
177+
entry!(KeyDown(Lmb); action_dispatch=FillToolMessage::LeftPointerDown),
178+
entry!(KeyDown(Rmb); action_dispatch=FillToolMessage::RightPointerDown),
175179
//
176180
// ToolMessage
177181
entry!(KeyDown(KeyV); action_dispatch=ToolMessage::ActivateToolSelect),

editor/src/messages/input_mapper/utility_types/input_mouse.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ impl ViewportBounds {
2727
pub fn center(&self) -> DVec2 {
2828
self.bottom_right.lerp(self.top_left, 0.5)
2929
}
30+
31+
pub fn in_bounds(&self, position: ViewportPosition) -> bool {
32+
position.x >= 0. && position.y >= 0. && position.x <= self.bottom_right.x && position.y <= self.bottom_right.y
33+
}
3034
}
3135

3236
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]

editor/src/messages/portfolio/document/properties_panel/utility_functions.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -910,8 +910,8 @@ fn node_section_imaginate(imaginate_layer: &ImaginateLayer, layer: &Layer, persi
910910
\n\
911911
Include an artist name like \"Rembrandt\" or art medium like \"watercolor\" or \"photography\" to influence the look. List multiple to meld styles.\n\
912912
\n\
913-
To boost the importance of a word or phrase, wrap it in quotes ending with a colon and a multiplier, for example:\n\
914-
\"(colorless:0.7) green (ideas sleep:1.3) furiously\"
913+
To boost (or lessen) the importance of a word or phrase, wrap it in quotes ending with a colon and a multiplier, for example:\n\
914+
\"Colorless green ideas (sleep:1.3) furiously\"
915915
"
916916
.trim()
917917
.into(),

editor/src/messages/tool/tool_message_handler.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
6969
return;
7070
}
7171

72-
// Send the Abort state transition to the tool
72+
// Send the old and new tools a transition to their FSM Abort states
7373
let mut send_abort_to_tool = |tool_type, update_hints_and_cursor: bool| {
7474
if let Some(tool) = tool_data.tools.get_mut(&tool_type) {
7575
if let Some(tool_abort_message) = tool.event_to_message_map().tool_abort {
@@ -82,8 +82,6 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
8282
}
8383
}
8484
};
85-
86-
// Send the old and new tools a transition to their FSM Abort states
8785
send_abort_to_tool(tool_type, true);
8886
send_abort_to_tool(old_tool, false);
8987

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

Lines changed: 78 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
1-
use crate::consts::SELECTION_TOLERANCE;
21
use crate::messages::frontend::utility_types::MouseCursorIcon;
32
use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion;
43
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
54
use crate::messages::prelude::*;
6-
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
5+
use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
76
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
87

9-
use graphene::intersection::Quad;
10-
use graphene::layers::layer_info::LayerDataType;
11-
12-
use glam::DVec2;
138
use serde::{Deserialize, Serialize};
149

1510
#[derive(Default)]
@@ -27,8 +22,11 @@ pub enum EyedropperToolMessage {
2722
Abort,
2823

2924
// Tool-specific messages
30-
LeftMouseDown,
31-
RightMouseDown,
25+
LeftPointerDown,
26+
LeftPointerUp,
27+
PointerMove,
28+
RightPointerDown,
29+
RightPointerUp,
3230
}
3331

3432
impl ToolMetadata for EyedropperTool {
@@ -67,8 +65,12 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for EyedropperTo
6765
}
6866

6967
advertise_actions!(EyedropperToolMessageDiscriminant;
70-
LeftMouseDown,
71-
RightMouseDown,
68+
LeftPointerDown,
69+
LeftPointerUp,
70+
PointerMove,
71+
RightPointerDown,
72+
RightPointerUp,
73+
Abort,
7274
);
7375
}
7476

@@ -85,6 +87,8 @@ impl ToolTransition for EyedropperTool {
8587
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8688
enum EyedropperToolFsmState {
8789
Ready,
90+
SamplingPrimary,
91+
SamplingSecondary,
8892
}
8993

9094
impl Default for EyedropperToolFsmState {
@@ -104,7 +108,7 @@ impl Fsm for EyedropperToolFsmState {
104108
self,
105109
event: ToolMessage,
106110
_tool_data: &mut Self::ToolData,
107-
(document, _document_id, _global_tool_data, input, font_cache): ToolActionHandlerData,
111+
(_document, _document_id, global_tool_data, input, _font_cache): ToolActionHandlerData,
108112
_tool_options: &Self::ToolOptions,
109113
responses: &mut VecDeque<Message>,
110114
) -> Self {
@@ -113,28 +117,41 @@ impl Fsm for EyedropperToolFsmState {
113117

114118
if let ToolMessage::Eyedropper(event) = event {
115119
match (self, event) {
116-
(Ready, lmb_or_rmb) if lmb_or_rmb == LeftMouseDown || lmb_or_rmb == RightMouseDown => {
117-
let mouse_pos = input.mouse.position;
118-
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
119-
let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]);
120-
121-
// TODO: Destroy this pyramid
122-
if let Some(path) = document.graphene_document.intersects_quad_root(quad, font_cache).last() {
123-
if let Ok(layer) = document.graphene_document.layer(path) {
124-
if let LayerDataType::Shape(shape) = &layer.data {
125-
if shape.style.fill().is_some() {
126-
match lmb_or_rmb {
127-
EyedropperToolMessage::LeftMouseDown => responses.push_back(ToolMessage::SelectPrimaryColor { color: shape.style.fill().color() }.into()),
128-
EyedropperToolMessage::RightMouseDown => responses.push_back(ToolMessage::SelectSecondaryColor { color: shape.style.fill().color() }.into()),
129-
_ => {}
130-
}
131-
}
132-
}
133-
}
120+
// Ready -> Sampling
121+
(Ready, mouse_down) | (Ready, mouse_down) if mouse_down == LeftPointerDown || mouse_down == RightPointerDown => {
122+
update_cursor_preview(responses, input, global_tool_data, None);
123+
124+
if mouse_down == LeftPointerDown {
125+
SamplingPrimary
126+
} else {
127+
SamplingSecondary
128+
}
129+
}
130+
// Sampling -> Sampling
131+
(SamplingPrimary, PointerMove) | (SamplingSecondary, PointerMove) => {
132+
if input.viewport_bounds.in_bounds(input.mouse.position) {
133+
update_cursor_preview(responses, input, global_tool_data, None);
134+
} else {
135+
disable_cursor_preview(responses);
134136
}
135137

138+
self
139+
}
140+
// Sampling -> Ready
141+
(SamplingPrimary, mouse_up) | (SamplingSecondary, mouse_up) if mouse_up == LeftPointerUp || mouse_up == RightPointerUp => {
142+
let set_color_choice = if self == SamplingPrimary { "Primary".to_string() } else { "Secondary".to_string() };
143+
update_cursor_preview(responses, input, global_tool_data, Some(set_color_choice));
144+
disable_cursor_preview(responses);
145+
136146
Ready
137147
}
148+
// Any -> Ready
149+
(_, Abort) => {
150+
disable_cursor_preview(responses);
151+
152+
Ready
153+
}
154+
// Ready -> Ready
138155
_ => self,
139156
}
140157
} else {
@@ -160,12 +177,43 @@ impl Fsm for EyedropperToolFsmState {
160177
plus: false,
161178
},
162179
])]),
180+
EyedropperToolFsmState::SamplingPrimary => HintData(vec![]),
181+
EyedropperToolFsmState::SamplingSecondary => HintData(vec![]),
163182
};
164183

165184
responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into());
166185
}
167186

168187
fn update_cursor(&self, responses: &mut VecDeque<Message>) {
169-
responses.push_back(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }.into());
188+
let cursor = match *self {
189+
EyedropperToolFsmState::Ready => MouseCursorIcon::Default,
190+
EyedropperToolFsmState::SamplingPrimary | EyedropperToolFsmState::SamplingSecondary => MouseCursorIcon::None,
191+
};
192+
193+
responses.push_back(FrontendMessage::UpdateMouseCursor { cursor }.into());
170194
}
171195
}
196+
197+
fn disable_cursor_preview(responses: &mut VecDeque<Message>) {
198+
responses.push_back(
199+
FrontendMessage::UpdateEyedropperSamplingState {
200+
mouse_position: None,
201+
primary_color: "".into(),
202+
secondary_color: "".into(),
203+
set_color_choice: None,
204+
}
205+
.into(),
206+
);
207+
}
208+
209+
fn update_cursor_preview(responses: &mut VecDeque<Message>, input: &InputPreprocessorMessageHandler, global_tool_data: &DocumentToolData, set_color_choice: Option<String>) {
210+
responses.push_back(
211+
FrontendMessage::UpdateEyedropperSamplingState {
212+
mouse_position: Some(input.mouse.position.into()),
213+
primary_color: "#".to_string() + global_tool_data.primary_color.rgb_hex().as_str(),
214+
secondary_color: "#".to_string() + global_tool_data.secondary_color.rgb_hex().as_str(),
215+
set_color_choice,
216+
}
217+
.into(),
218+
);
219+
}

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ pub enum FillToolMessage {
2828
Abort,
2929

3030
// Tool-specific messages
31-
LeftMouseDown,
32-
RightMouseDown,
31+
LeftPointerDown,
32+
RightPointerDown,
3333
}
3434

3535
impl ToolMetadata for FillTool {
@@ -68,8 +68,8 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for FillTool {
6868
}
6969

7070
advertise_actions!(FillToolMessageDiscriminant;
71-
LeftMouseDown,
72-
RightMouseDown,
71+
LeftPointerDown,
72+
RightPointerDown,
7373
);
7474
}
7575

@@ -114,15 +114,15 @@ impl Fsm for FillToolFsmState {
114114

115115
if let ToolMessage::Fill(event) = event {
116116
match (self, event) {
117-
(Ready, lmb_or_rmb) if lmb_or_rmb == LeftMouseDown || lmb_or_rmb == RightMouseDown => {
117+
(Ready, lmb_or_rmb) if lmb_or_rmb == LeftPointerDown || lmb_or_rmb == RightPointerDown => {
118118
let mouse_pos = input.mouse.position;
119119
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
120120
let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]);
121121

122122
if let Some(path) = document.graphene_document.intersects_quad_root(quad, font_cache).last() {
123123
let color = match lmb_or_rmb {
124-
LeftMouseDown => global_tool_data.primary_color,
125-
RightMouseDown => global_tool_data.secondary_color,
124+
LeftPointerDown => global_tool_data.primary_color,
125+
RightPointerDown => global_tool_data.secondary_color,
126126
Abort => unreachable!(),
127127
};
128128
let fill = Fill::Solid(color);

editor/src/messages/tool/utility_types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ impl DocumentToolData {
7878
}
7979
.into(),
8080
);
81+
82+
responses.push_back(EyedropperToolMessage::PointerMove.into());
8183
}
8284
}
8385

0 commit comments

Comments
 (0)