Revamp the ColorPicker popover and ColorInput widget (#830)

* Add cancel hint to Eyedropper tool

* Improve eyedropper overlay CSS

* Make CSS for transparent checkered background reusable

* Add color choice preview to color picker

* Draw text and markers as contrasting white or black

* Add reactive color updating and new/initial swapping

* Add Hex, RGB, HSV, and Opacity inputs

* Add none color and preset buttons

* Add eyedropper button and fix alignment (now visually done)

* Wire up none colors through the backend and style the ColorInput widget

* Add color info chip to ColorInput widget

* Fix all UX bugs

* Add more tooltips

* Fix FloatingMenu recursive loop

* Prevent mouse stray from closing color picker while dragging pickers

Closes #703

* Fix deselect all layers shortcut

* Add temporary eyedropper for Chromium browsers and a coming soon fallback
This commit is contained in:
Keavon Chambers
2022-10-28 18:36:04 -07:00
committed by GitHub
parent 803fa4a045
commit 7d2bdf11e1
23 changed files with 830 additions and 279 deletions

View File

@@ -201,7 +201,7 @@ pub fn default_mapping() -> Mapping {
entry!(KeyDown(KeyP); modifiers=[Alt], action_dispatch=DocumentMessage::DebugPrintDocument),
entry!(KeyDown(KeyZ); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::Redo),
entry!(KeyDown(KeyZ); modifiers=[Accel], action_dispatch=DocumentMessage::Undo),
entry!(KeyDown(KeyA); modifiers=[Accel, Alt], action_dispatch=DocumentMessage::DeselectAllLayers),
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::DeselectAllLayers),
entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=DocumentMessage::SelectAllLayers),
entry!(KeyDown(KeyS); modifiers=[Accel], action_dispatch=DocumentMessage::SaveDocument),
entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=DocumentMessage::DuplicateSelectedLayers),

View File

@@ -4,6 +4,7 @@ use crate::messages::layout::utility_types::layout_widget::Layout;
use crate::messages::layout::utility_types::layout_widget::Widget;
use crate::messages::prelude::*;
use graphene::color::Color;
use graphene::layers::text_layer::Font;
use serde_json::Value;
@@ -60,8 +61,23 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
responses.push_back(callback_message);
}
Widget::ColorInput(color_input) => {
let update_value = value.as_str().map(String::from);
color_input.value = update_value;
let update_value = value.as_object().expect("ColorInput update was not of type: object");
let parsed_color = (|| {
let is_none = update_value.get("none")?.as_bool()?;
if !is_none {
Some(Some(Color::from_rgbaf32(
update_value.get("red")?.as_f64()? as f32,
update_value.get("green")?.as_f64()? as f32,
update_value.get("blue")?.as_f64()? as f32,
update_value.get("alpha")?.as_f64()? as f32,
)?))
} else {
Some(None)
}
})()
.unwrap_or_else(|| panic!("ColorInput update was not able to be parsed with color data: {:?}", color_input));
color_input.value = parsed_color;
let callback_message = (color_input.on_update.callback)(color_input);
responses.push_back(callback_message);
}

View File

@@ -39,13 +39,12 @@ impl Default for CheckboxInput {
#[derive(Clone, Derivative, Serialize, Deserialize)]
#[derivative(Debug, PartialEq, Default)]
pub struct ColorInput {
pub value: Option<String>,
pub label: Option<String>,
pub value: Option<Color>,
// TODO: Add allow_none
#[serde(rename = "noTransparency")]
#[derivative(Default(value = "true"))]
pub no_transparency: bool,
pub no_transparency: bool, // TODO: Rename allow_transparency (and invert usages)
pub disabled: bool,
@@ -295,6 +294,8 @@ pub struct TextInput {
pub tooltip: String,
pub centered: bool,
#[serde(rename = "minWidth")]
pub min_width: u32,

View File

@@ -10,7 +10,6 @@ use crate::messages::layout::utility_types::widgets::label_widgets::{IconLabel,
use crate::messages::portfolio::utility_types::{ImaginateServerStatus, PersistentData};
use crate::messages::prelude::*;
use graphene::color::Color;
use graphene::document::pick_layer_safe_imaginate_resolution;
use graphene::layers::imaginate_layer::{ImaginateLayer, ImaginateSamplingMethod, ImaginateStatus};
use graphene::layers::layer_info::{Layer, LayerDataType, LayerDataTypeDiscriminant};
@@ -190,18 +189,10 @@ pub fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDequ
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::ColorInput(ColorInput {
value: Some(color.rgba_hex()),
value: Some(*color),
on_update: WidgetCallback::new(|text_input: &ColorInput| {
if let Some(value) = &text_input.value {
if let Some(color) = Color::from_rgba_str(value).or_else(|| Color::from_rgb_str(value)) {
let new_fill = Fill::Solid(color);
PropertiesPanelMessage::ModifyFill { fill: new_fill }.into()
} else {
PropertiesPanelMessage::ResendActiveProperties.into()
}
} else {
PropertiesPanelMessage::ModifyFill { fill: Fill::None }.into()
}
let fill = if let Some(value) = text_input.value { Fill::Solid(value) } else { Fill::None };
PropertiesPanelMessage::ModifyFill { fill }.into()
}),
no_transparency: true,
..Default::default()
@@ -1186,21 +1177,11 @@ fn node_gradient_color(gradient: &Gradient, percent_label: &'static str, positio
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::ColorInput(ColorInput {
value: gradient_clone.positions[position].1.map(|color| color.rgba_hex()),
value: gradient_clone.positions[position].1,
on_update: WidgetCallback::new(move |text_input: &ColorInput| {
if let Some(value) = &text_input.value {
if let Some(color) = Color::from_rgba_str(value).or_else(|| Color::from_rgb_str(value)) {
let mut new_gradient = (*gradient_clone).clone();
new_gradient.positions[position].1 = Some(color);
send_fill_message(new_gradient)
} else {
PropertiesPanelMessage::ResendActiveProperties.into()
}
} else {
let mut new_gradient = (*gradient_clone).clone();
new_gradient.positions[position].1 = None;
send_fill_message(new_gradient)
}
let mut new_gradient = (*gradient_clone).clone();
new_gradient.positions[position].1 = text_input.value;
send_fill_message(new_gradient)
}),
..ColorInput::default()
})),
@@ -1223,18 +1204,10 @@ fn node_section_fill(fill: &Fill) -> Option<LayoutGroup> {
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::ColorInput(ColorInput {
value: if let Fill::Solid(color) = fill { Some(color.rgba_hex()) } else { None },
value: if let Fill::Solid(color) = fill { Some(*color) } else { None },
on_update: WidgetCallback::new(|text_input: &ColorInput| {
if let Some(value) = &text_input.value {
if let Some(color) = Color::from_rgba_str(value).or_else(|| Color::from_rgb_str(value)) {
let new_fill = Fill::Solid(color);
PropertiesPanelMessage::ModifyFill { fill: new_fill }.into()
} else {
PropertiesPanelMessage::ResendActiveProperties.into()
}
} else {
PropertiesPanelMessage::ModifyFill { fill: Fill::None }.into()
}
let fill = if let Some(value) = text_input.value { Fill::Solid(value) } else { Fill::None };
PropertiesPanelMessage::ModifyFill { fill }.into()
}),
..ColorInput::default()
})),
@@ -1276,7 +1249,7 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutGroup {
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::ColorInput(ColorInput {
value: stroke.color().map(|color| color.rgba_hex()),
value: stroke.color(),
on_update: WidgetCallback::new(move |text_input: &ColorInput| {
internal_stroke1
.clone()
@@ -1324,6 +1297,7 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutGroup {
})),
WidgetHolder::new(Widget::TextInput(TextInput {
value: stroke.dash_lengths(),
centered: true,
on_update: WidgetCallback::new(move |text_input: &TextInput| {
internal_stroke3
.clone()

View File

@@ -1,5 +1,5 @@
use crate::messages::frontend::utility_types::MouseCursorIcon;
use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion;
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, MouseMotion};
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
@@ -177,8 +177,13 @@ impl Fsm for EyedropperToolFsmState {
plus: false,
},
])]),
EyedropperToolFsmState::SamplingPrimary => HintData(vec![]),
EyedropperToolFsmState::SamplingSecondary => HintData(vec![]),
EyedropperToolFsmState::SamplingPrimary | EyedropperToolFsmState::SamplingSecondary => HintData(vec![HintGroup(vec![HintInfo {
key_groups: vec![KeysGroup(vec![Key::Escape])],
key_groups_mac: None,
mouse: None,
label: String::from("Cancel"),
plus: false,
}])]),
};
responses.push_back(FrontendMessage::UpdateInputHints { hint_data }.into());