Deprecate all usages of the Color struct representing gamma space values, fixing round-trip precision bugs (#4149)

* Deprecate all usages of the Color struct representing gamma space values, fixing round-trip precision bugs

* Code review fixes
This commit is contained in:
Keavon Chambers
2026-05-14 22:48:33 -07:00
committed by GitHub
parent 456a7c868d
commit a56746c6bf
67 changed files with 1210 additions and 941 deletions
@@ -6,7 +6,7 @@ use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::utility_types::DocumentToolData;
use graphene_std::Color;
use graphene_std::vector::style::{FillChoice, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
use graphene_std::vector::style::{FillChoice, FillChoiceUI, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
/// Color selector widgets seen in [`LayoutTarget::ToolOptions`] bar.
pub struct ToolColorOptions {
@@ -47,13 +47,12 @@ impl ToolColorOptions {
self.enabled == Some(true)
}
/// The active solid color in linear sRGB, suitable for storing in a working color or downstream rendering input.
/// `fill_choice` is stored in gamma space (per [`FillChoice`]'s contract), so this method converts to linear before returning.
/// The active solid color, suitable for storing in a working color or downstream rendering input.
pub fn active_color(&self) -> Option<Color> {
if !self.is_active() {
return None;
}
Some(self.fill_choice.as_ref()?.as_solid()?.to_linear_srgb())
self.fill_choice.as_ref()?.as_solid()
}
pub fn apply_fill(&self, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
@@ -86,7 +85,8 @@ impl ToolColorOptions {
// In the mixed state (`fill_choice` is `None`) the dash overlay covers the swatch, so the underlying widget value just drives the picker's initial position.
// `FillChoice::None` gives it a neutral starting point.
let mixed_color = self.fill_choice.is_none();
let widget_value = self.fill_choice.clone().unwrap_or(FillChoice::None);
// Convert the internal linear-light `FillChoice` to the JS-boundary `FillChoiceUI` (with `SRGBA8` colors) for the widget value.
let widget_value = FillChoiceUI::from(self.fill_choice.as_ref().unwrap_or(&FillChoice::None));
let mixed_enabled = self.enabled.is_none();
// In the mixed-enabled state the underlying `checked` value is hidden behind the indeterminate dash.
// The frontend's click handler sends `true` when the user resolves the mixed state by clicking.
@@ -191,10 +191,9 @@ impl DrawingToolState {
}
}
/// Builds a `FillChoice::Solid` from a linear-space color, applying gamma conversion to display sRGB.
/// Common helper used throughout the color-syncing code where working colors (linear) flow into swatches that store gamma-encoded colors.
pub fn solid_gamma(color: Color) -> FillChoice {
FillChoice::Solid(color.to_gamma_srgb())
/// Builds a `FillChoice::Solid` from a color.
pub fn solid(color: Color) -> FillChoice {
FillChoice::Solid(color)
}
/// The fill working color (the source for the fill swatch when nothing is selected).
@@ -219,8 +218,8 @@ pub fn sync_color_options(
document: &DocumentMessageHandler,
selection_changed: bool,
) -> bool {
let fill_fallback = solid_gamma(fill_working_color(global, drawing.colors_swapped));
let stroke_fallback = solid_gamma(stroke_working_color(global, drawing.colors_swapped));
let fill_fallback = solid(fill_working_color(global, drawing.colors_swapped));
let stroke_fallback = solid(stroke_working_color(global, drawing.colors_swapped));
let mut changed = false;
@@ -364,7 +363,7 @@ fn sync_stroke_options(drawing: &mut DrawingToolState, document: &DocumentMessag
/// Same as [`sync_color_options`] but for tools that only have a fill option (e.g., text). The fill follows the given working color when nothing is selected.
pub fn sync_fill_only(fill: &mut ToolColorOptions, natural_fill_enabled: bool, fill_color: Color, document: &DocumentMessageHandler, selection_changed: bool) -> bool {
let fill_fallback = solid_gamma(fill_color);
let fill_fallback = solid(fill_color);
let new_fill = if let Some(state) = graph_modification_utils::selected_fill_state(document) {
let active = state.enabled == Some(true);
@@ -417,11 +416,7 @@ pub fn apply_fill_only_color_pick(fill: &mut ToolColorOptions, fill_choice: Fill
}
graph_modification_utils::set_fill_for_selected_layers(fill_choice, document, responses);
} else if let FillChoice::Solid(color) = fill_choice {
// Swatch is gamma; working colors are linear.
responses.add(ToolMessage::SelectWorkingColor {
color: color.to_linear_srgb(),
primary: slot_is_primary,
});
responses.add(ToolMessage::SelectWorkingColor { color, primary: slot_is_primary });
}
}
@@ -436,9 +431,8 @@ pub fn apply_stroke_color_pick(drawing: &mut DrawingToolState, color: Option<Col
}
graph_modification_utils::set_stroke_color_for_selected_layers(color, drawing.effective_line_weight(), document, responses);
} else if let Some(color) = color {
// Swatch is gamma; working colors are linear.
responses.add(ToolMessage::SelectWorkingColor {
color: color.to_linear_srgb(),
color,
primary: !drawing.colors_swapped,
});
}
@@ -459,14 +453,14 @@ pub fn apply_fill_only_enabled(fill: &mut ToolColorOptions, enabled: bool, worki
// Mixed re-tick has no per-layer color to restore; fall back to the working color and keep tracking it.
let fill_choice = fill.fill_choice.clone().unwrap_or_else(|| {
fill.tracks_working_color = true;
solid_gamma(working_color)
solid(working_color)
});
fill.fill_choice = Some(fill_choice.clone());
graph_modification_utils::set_fill_for_selected_layers(fill_choice, document, responses);
} else {
// Unticking from mixed: capture the working color as the saved value so the swatch keeps following the link.
if fill.fill_choice.is_none() {
fill.fill_choice = Some(solid_gamma(working_color));
fill.fill_choice = Some(solid(working_color));
fill.tracks_working_color = true;
}
graph_modification_utils::remove_fill_for_selected_layers(document, responses);
@@ -482,13 +476,13 @@ pub fn apply_stroke_enabled(drawing: &mut DrawingToolState, enabled: bool, globa
if enabled {
let stroke_choice = drawing.stroke.fill_choice.clone().unwrap_or_else(|| {
drawing.stroke.tracks_working_color = true;
solid_gamma(stroke_working_color(global, drawing.colors_swapped))
solid(stroke_working_color(global, drawing.colors_swapped))
});
drawing.stroke.fill_choice = Some(stroke_choice.clone());
graph_modification_utils::set_stroke_color_for_selected_layers(stroke_choice.as_solid(), drawing.effective_line_weight(), document, responses);
} else {
if drawing.stroke.fill_choice.is_none() {
drawing.stroke.fill_choice = Some(solid_gamma(stroke_working_color(global, drawing.colors_swapped)));
drawing.stroke.fill_choice = Some(solid(stroke_working_color(global, drawing.colors_swapped)));
drawing.stroke.tracks_working_color = true;
}
graph_modification_utils::remove_stroke_for_selected_layers(document, responses);
@@ -513,14 +507,14 @@ pub fn apply_working_colors(drawing: &mut DrawingToolState, global: &DocumentToo
/// Refreshes a single swatch from the given working color, subject to the rules in [`apply_working_colors`].
pub fn refresh_slot_working_color(slot: &mut ToolColorOptions, working_color: Color, document: &DocumentMessageHandler) {
if slot.fill_choice.is_some() && (!has_selection(document) || slot.tracks_working_color) {
slot.fill_choice = Some(solid_gamma(working_color));
slot.fill_choice = Some(solid(working_color));
}
}
/// Resets the tool's swatches to the working colors. Called on tool deactivation and shape-mode changes.
pub fn reset_colors_on_deactivation(drawing: &mut DrawingToolState, global: &DocumentToolData) {
drawing.fill.fill_choice = Some(solid_gamma(fill_working_color(global, drawing.colors_swapped)));
drawing.stroke.fill_choice = Some(solid_gamma(stroke_working_color(global, drawing.colors_swapped)));
drawing.fill.fill_choice = Some(solid(fill_working_color(global, drawing.colors_swapped)));
drawing.stroke.fill_choice = Some(solid(stroke_working_color(global, drawing.colors_swapped)));
drawing.fill.tracks_working_color = true;
drawing.stroke.tracks_working_color = true;
}
@@ -329,10 +329,10 @@ pub fn get_fill_color(layer: LayerNodeIdentifier, network_interface: &NodeNetwor
let fill_index = 1;
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::fill::IDENTIFIER))?;
let TaggedValue::Fill(Fill::Solid(color)) = inputs.get(fill_index)?.as_value()? else {
let &TaggedValue::Fill(Fill::Solid(color)) = inputs.get(fill_index)?.as_value()? else {
return None;
};
Some(color.to_linear_srgb())
Some(color)
}
/// Get the current blend mode of a layer from the closest upstream "Blend Mode" node.
@@ -9,6 +9,7 @@ use crate::messages::prelude::*;
use crate::messages::tool::transform_layer::transform_layer_message_handler::TransformLayerMessageContext;
use crate::messages::tool::utility_types::{HintData, ToolType};
use crate::node_graph_executor::NodeGraphExecutor;
use graphene_std::color::SRGBA8;
use graphene_std::raster::color::Color;
const ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |context| DocumentMessage::DrawArtboardOverlays { context }.into();
@@ -280,7 +281,7 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
let r = (random_number >> 16) as u8;
let g = (random_number >> 8) as u8;
let b = random_number as u8;
let random_color = Color::from_rgba8_srgb(r, g, b, 255);
let random_color = Color::from(SRGBA8::new(r, g, b, 255));
if primary {
document_data.primary_color = random_color;
@@ -4,13 +4,13 @@ use crate::messages::portfolio::document::graph_operation::transform_utils::get_
use crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_proto_node_type};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::FlowType;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, solid_gamma};
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, solid};
use graph_craft::document::NodeId;
use graph_craft::document::value::TaggedValue;
use graphene_std::Color;
use graphene_std::brush::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
use graphene_std::raster::BlendMode;
use graphene_std::vector::style::FillChoice;
use graphene_std::vector::style::{FillChoice, FillChoiceUI};
const BRUSH_MAX_SIZE: f64 = 5000.;
@@ -104,13 +104,12 @@ impl ToolMetadata for BrushTool {
impl LayoutHolder for BrushTool {
fn layout(&self) -> Layout {
let mut widgets = vec![
ColorInput::new(self.options.color.fill_choice.clone().unwrap_or(FillChoice::None))
ColorInput::new(FillChoiceUI::from(self.options.color.fill_choice.as_ref().unwrap_or(&FillChoice::None)))
.mixed(self.options.color.fill_choice.is_none())
.narrow(true)
.on_update(|color: &ColorInput| {
BrushToolMessage::UpdateOptions {
// The picker emits gamma-space colors; working colors are stored in linear sRGB.
options: BrushToolMessageOptionsUpdate::Color(color.value.as_solid().map(|c| c.to_linear_srgb())),
options: BrushToolMessageOptionsUpdate::Color(color.value.as_solid().map(Color::from)),
}
.into()
})
@@ -245,7 +244,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Brus
}
}
BrushToolMessageOptionsUpdate::WorkingColorsChanged => {
self.options.color.fill_choice = Some(solid_gamma(context.global_tool_data.primary_color));
self.options.color.fill_choice = Some(solid(context.global_tool_data.primary_color));
}
}
@@ -1,6 +1,7 @@
use super::tool_prelude::*;
use crate::messages::frontend::utility_types::EyedropperPreviewImage;
use crate::messages::tool::utility_types::DocumentToolData;
use graphene_std::color::SRGBA8;
use graphene_std::vector::style::RenderMode;
#[derive(Default, ExtractField)]
@@ -233,8 +234,8 @@ fn update_cursor_preview_common(
responses.add(FrontendMessage::UpdateEyedropperSamplingState {
image,
mouse_position: Some(input.mouse.position.into()),
primary_color: "#".to_string() + global_tool_data.primary_color.to_rgb_hex_srgb().as_str(),
secondary_color: "#".to_string() + global_tool_data.secondary_color.to_rgb_hex_srgb().as_str(),
primary_color: SRGBA8::from(global_tool_data.primary_color).to_css_hex(),
secondary_color: SRGBA8::from(global_tool_data.secondary_color).to_css_hex(),
set_color_choice,
});
}
@@ -1,9 +1,10 @@
use super::tool_prelude::*;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::tool::common_functionality::color_selector::solid_gamma;
use crate::messages::tool::common_functionality::color_selector::solid;
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
use graphene_std::color::SRGBA8;
use graphene_std::raster::color::Color;
use graphene_std::vector::style::Fill;
use graphene_std::vector::style::{Fill, FillChoiceUI};
#[derive(Default, ExtractField)]
pub struct FillTool {
@@ -43,11 +44,11 @@ impl ToolMetadata for FillTool {
impl LayoutHolder for FillTool {
fn layout(&self) -> Layout {
let widgets = vec![
ColorInput::new(solid_gamma(self.primary_color))
ColorInput::new(FillChoiceUI::from(&solid(self.primary_color)))
.narrow(true)
.on_update(|color: &ColorInput| {
FillToolMessage::SetColor {
color: color.value.as_solid().map(|c| c.to_linear_srgb()),
color: color.value.as_solid().map(Color::from),
}
.into()
})
@@ -141,7 +142,7 @@ impl Fsm for FillToolFsmState {
// Get the layer the user is hovering over
if let Some(layer) = document.click(input, viewport) {
let color_hex = format!("#{}", preview_color.to_rgba_hex_srgb());
let color_hex = SRGBA8::from(preview_color).to_css_hex();
overlay_context.fill_path_pattern(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer), &color_hex);
}
@@ -161,8 +162,8 @@ impl Fsm for FillToolFsmState {
return self;
}
let fill = match color_event {
FillToolMessage::FillPrimaryColor => Fill::Solid(global_tool_data.primary_color.to_gamma_srgb()),
FillToolMessage::FillSecondaryColor => Fill::Solid(global_tool_data.secondary_color.to_gamma_srgb()),
FillToolMessage::FillPrimaryColor => Fill::Solid(global_tool_data.primary_color),
FillToolMessage::FillSecondaryColor => Fill::Solid(global_tool_data.secondary_color),
_ => return self,
};
@@ -201,6 +202,7 @@ impl Fsm for FillToolFsmState {
#[cfg(test)]
mod test_fill {
pub use crate::test_utils::test_prelude::*;
use graphene_std::color::SRGBA8;
use graphene_std::vector::fill;
use graphene_std::vector::style::Fill;
@@ -240,7 +242,7 @@ mod test_fill {
editor.click_tool(ToolType::Fill, MouseKeys::LEFT, DVec2::new(2., 2.), ModifierKeys::empty()).await;
let fills = get_fills(&mut editor).await;
assert_eq!(fills.len(), 1);
assert_eq!(fills[0].as_solid().unwrap().to_rgba8_srgb(), Color::GREEN.to_rgba8_srgb());
assert_eq!(SRGBA8::from(fills[0].as_solid().unwrap()), SRGBA8::from(Color::GREEN));
}
#[tokio::test]
@@ -252,6 +254,6 @@ mod test_fill {
editor.click_tool(ToolType::Fill, MouseKeys::LEFT, DVec2::new(2., 2.), ModifierKeys::SHIFT).await;
let fills = get_fills(&mut editor).await;
assert_eq!(fills.len(), 1);
assert_eq!(fills[0].as_solid().unwrap().to_rgba8_srgb(), Color::YELLOW.to_rgba8_srgb());
assert_eq!(SRGBA8::from(fills[0].as_solid().unwrap()), SRGBA8::from(Color::YELLOW));
}
}
@@ -97,7 +97,7 @@ impl LayoutHolder for FreehandTool {
},
|color: &ColorInput| {
FreehandToolMessage::UpdateOptions {
options: FreehandOptionsUpdate::FillColor(color.value.clone()),
options: FreehandOptionsUpdate::FillColor(FillChoice::from(&color.value)),
}
.into()
},
@@ -127,7 +127,7 @@ impl LayoutHolder for FreehandTool {
},
|color: &ColorInput| {
FreehandToolMessage::UpdateOptions {
options: FreehandOptionsUpdate::StrokeColor(color.value.as_solid()),
options: FreehandOptionsUpdate::StrokeColor(color.value.as_solid().map(Color::from)),
}
.into()
},
@@ -11,8 +11,9 @@ use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer, get_gradient_stops};
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
use graph_craft::document::value::TaggedValue;
use graphene_std::color::SRGBA8;
use graphene_std::raster::color::Color;
use graphene_std::vector::style::{Fill, FillChoice, Gradient, GradientSpreadMethod, GradientStop, GradientStops, GradientType};
use graphene_std::vector::style::{Fill, FillChoice, FillChoiceUI, Gradient, GradientSpreadMethod, GradientStop, GradientStops, GradientStopsUI, GradientType};
#[derive(Default, ExtractField)]
pub struct GradientTool {
@@ -49,7 +50,7 @@ pub enum GradientToolMessage {
CommitTransactionForColorStop,
CloseStopColorPicker,
UpdateStopColor { color: Color },
UpdateStops { stops: GradientStops },
UpdateStops { stops: GradientStopsUI },
UpdateOptions { options: GradientOptionsUpdate },
}
@@ -120,7 +121,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
}
}
ToolMessage::Gradient(GradientToolMessage::UpdateStops { stops }) => {
apply_stops_update(&mut self.data, context, responses, stops);
apply_stops_update(&mut self.data, context, responses, GradientStops::from(&stops));
}
ToolMessage::Gradient(GradientToolMessage::CloseStopColorPicker) => {
if self.data.color_picker_transaction_open {
@@ -243,7 +244,7 @@ impl LayoutHolder for GradientTool {
},
]))
});
let stops_widget = ColorInput::new(stops_value)
let stops_widget = ColorInput::new(FillChoiceUI::from(&stops_value))
.allow_none(false)
.narrow(true)
.tooltip_label("Gradient Stops")
@@ -856,7 +857,7 @@ impl Fsm for GradientToolFsmState {
let (start, end) = (transform.transform_point2(*start), transform.transform_point2(*end));
fn color_to_hex(color: graphene_std::Color) -> String {
format!("#{}", color.to_rgb_hex_srgb_from_gamma())
SRGBA8::from(color).to_css_hex()
}
let start_hex = stops.color.first().map(|&c| color_to_hex(c)).unwrap_or(String::from(COLOR_OVERLAY_BLUE));
@@ -1024,10 +1025,10 @@ impl Fsm for GradientToolFsmState {
let transform = gradient_space_transform(layer, document);
let gradient = &selected_gradient.gradient;
if stop_index < gradient.stops.position.len() {
let color = gradient.stops.color[stop_index].to_gamma_srgb();
let color = gradient.stops.color[stop_index];
let position = gradient.stops.position[stop_index];
let position = transform.transform_point2(gradient.start.lerp(gradient.end, position)).into();
responses.add(FrontendMessage::UpdateGradientStopColorPickerPosition { color, position });
responses.add(FrontendMessage::UpdateGradientStopColorPickerPosition { color: color.into(), position });
}
}
@@ -1081,9 +1082,9 @@ impl Fsm for GradientToolFsmState {
.transform
.transform_point2(selected_gradient.gradient.start.lerp(selected_gradient.gradient.end, stop_pos));
let position = viewport_pos.into();
let color = selected_gradient.gradient.stops.color[stop_index].to_gamma_srgb();
let color = selected_gradient.gradient.stops.color[stop_index];
tool_data.color_picker_editing_color_stop = Some(stop_index);
responses.add(FrontendMessage::UpdateGradientStopColorPickerPosition { color, position });
responses.add(FrontendMessage::UpdateGradientStopColorPickerPosition { color: color.into(), position });
}
}
_ => {}
@@ -1910,6 +1911,7 @@ mod test_gradient {
pub use crate::test_utils::test_prelude::*;
use glam::DAffine2;
use graph_craft::document::value::TaggedValue;
use graphene_std::color::SRGBA8;
use graphene_std::vector::style::{Fill, Gradient};
use graphene_std::vector::{GradientStop, GradientStops, fill};
@@ -2022,8 +2024,8 @@ mod test_gradient {
let (gradient, transform) = get_gradient(&mut editor).await;
// Gradient goes from primary color to secondary color
let stops = gradient.stops.iter().map(|stop| (stop.position, stop.color.to_rgba8_srgb())).collect::<Vec<_>>();
assert_eq!(stops, vec![(0., Color::GREEN.to_rgba8_srgb()), (1., Color::BLUE.to_rgba8_srgb())]);
let stops = gradient.stops.iter().map(|stop| (stop.position, SRGBA8::from(stop.color))).collect::<Vec<_>>();
assert_eq!(stops, vec![(0., SRGBA8::from(Color::GREEN)), (1., SRGBA8::from(Color::BLUE))]);
assert!(transform.transform_point2(gradient.start).abs_diff_eq(DVec2::new(2., 3.), 1e-10));
assert!(transform.transform_point2(gradient.end).abs_diff_eq(DVec2::new(24., 4.), 1e-10));
}
@@ -2215,7 +2217,7 @@ mod test_gradient {
let positions: Vec<f64> = stops.iter().map(|stop| stop.position).collect();
assert_stops_at_positions(&positions, &[0., 0.25, 1.], 0.1);
let middle_color = stops.color[1].to_rgba8_srgb();
let middle_color = SRGBA8::from(stops.color[1]);
// Simulate dragging the middle stop to position 0.8
let click_position = DVec2::new(25., 0.);
@@ -2256,9 +2258,9 @@ mod test_gradient {
assert_stops_at_positions(&updated_positions, &[0., 0.8, 1.], 0.1);
// Colors should maintain their associations with the stop points
assert_eq!(updated_stops.color[0].to_rgba8_srgb(), Color::GREEN.to_rgba8_srgb());
assert_eq!(updated_stops.color[1].to_rgba8_srgb(), middle_color);
assert_eq!(updated_stops.color[2].to_rgba8_srgb(), Color::BLUE.to_rgba8_srgb());
assert_eq!(SRGBA8::from(updated_stops.color[0]), SRGBA8::from(Color::GREEN));
assert_eq!(SRGBA8::from(updated_stops.color[1]), middle_color);
assert_eq!(SRGBA8::from(updated_stops.color[2]), SRGBA8::from(Color::BLUE));
}
#[tokio::test]
@@ -2495,8 +2497,8 @@ mod test_gradient {
assert_eq!(updated.stops.len(), 3, "Stop count should be preserved");
assert_stops_at_positions(&updated.stops.position, &[0., 0.5, 1.], 1e-10);
assert_eq!(updated.stops.color[0].to_rgba8_srgb(), Color::RED.to_rgba8_srgb(), "First stop color should be preserved");
assert_eq!(updated.stops.color[1].to_rgba8_srgb(), Color::GREEN.to_rgba8_srgb(), "Middle stop color should be preserved");
assert_eq!(updated.stops.color[2].to_rgba8_srgb(), Color::BLUE.to_rgba8_srgb(), "Last stop color should be preserved");
assert_eq!(SRGBA8::from(updated.stops.color[0]), SRGBA8::from(Color::RED), "First stop color should be preserved");
assert_eq!(SRGBA8::from(updated.stops.color[1]), SRGBA8::from(Color::GREEN), "Middle stop color should be preserved");
assert_eq!(SRGBA8::from(updated.stops.color[2]), SRGBA8::from(Color::BLUE), "Last stop color should be preserved");
}
}
@@ -2850,13 +2850,10 @@ impl Fsm for PathToolFsmState {
let layer = graph_modification_utils::new_custom(NodeId::new(), nodes, parent, responses);
// Defaults chosen because the pasted geometry has no inherent associated style
let stroke_color = Color::BLACK;
let fill_color = Color::WHITE;
let stroke = graphene_std::vector::style::Stroke::new(Some(stroke_color.to_gamma_srgb()), DEFAULT_STROKE_WIDTH);
let stroke = graphene_std::vector::style::Stroke::new(Some(Color::BLACK), DEFAULT_STROKE_WIDTH);
responses.add(GraphOperationMessage::StrokeSet { layer, stroke });
let fill = graphene_std::vector::style::Fill::solid(fill_color.to_gamma_srgb());
let fill = graphene_std::vector::style::Fill::solid(Color::WHITE);
responses.add(GraphOperationMessage::FillSet { layer, fill });
new_layers.push(layer);
@@ -154,7 +154,7 @@ impl LayoutHolder for PenTool {
},
|color: &ColorInput| {
PenToolMessage::UpdateOptions {
options: PenOptionsUpdate::FillColor(color.value.clone()),
options: PenOptionsUpdate::FillColor(FillChoice::from(&color.value)),
}
.into()
},
@@ -184,7 +184,7 @@ impl LayoutHolder for PenTool {
},
|color: &ColorInput| {
PenToolMessage::UpdateOptions {
options: PenOptionsUpdate::StrokeColor(color.value.as_solid()),
options: PenOptionsUpdate::StrokeColor(color.value.as_solid().map(Color::from)),
}
.into()
},
@@ -248,7 +248,7 @@ impl LayoutHolder for SelectTool {
},
|color: &ColorInput| {
SelectToolMessage::SelectOptions {
options: SelectOptionsUpdate::FillColor(color.value.clone()),
options: SelectOptionsUpdate::FillColor(FillChoice::from(&color.value)),
}
.into()
},
@@ -278,7 +278,7 @@ impl LayoutHolder for SelectTool {
},
|color: &ColorInput| {
SelectToolMessage::SelectOptions {
options: SelectOptionsUpdate::StrokeColor(color.value.as_solid()),
options: SelectOptionsUpdate::StrokeColor(color.value.as_solid().map(Color::from)),
}
.into()
},
@@ -467,7 +467,7 @@ impl LayoutHolder for ShapeTool {
},
|color: &ColorInput| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::FillColor(color.value.clone()),
options: ShapeOptionsUpdate::FillColor(FillChoice::from(&color.value)),
}
.into()
},
@@ -498,7 +498,7 @@ impl LayoutHolder for ShapeTool {
},
|color: &ColorInput| {
ShapeToolMessage::UpdateOptions {
options: ShapeOptionsUpdate::StrokeColor(color.value.as_solid()),
options: ShapeOptionsUpdate::StrokeColor(color.value.as_solid().map(Color::from)),
}
.into()
},
@@ -105,7 +105,7 @@ impl LayoutHolder for SplineTool {
},
|color: &ColorInput| {
SplineToolMessage::UpdateOptions {
options: SplineOptionsUpdate::FillColor(color.value.clone()),
options: SplineOptionsUpdate::FillColor(FillChoice::from(&color.value)),
}
.into()
},
@@ -135,7 +135,7 @@ impl LayoutHolder for SplineTool {
},
|color: &ColorInput| {
SplineToolMessage::UpdateOptions {
options: SplineOptionsUpdate::StrokeColor(color.value.as_solid()),
options: SplineOptionsUpdate::StrokeColor(color.value.as_solid().map(Color::from)),
}
.into()
},
@@ -9,7 +9,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::Inpu
use crate::messages::portfolio::utility_types::{CachedData, FontCatalog, FontCatalogStyle};
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::color_selector::{
ToolColorOptions, apply_fill_only_color_pick, apply_fill_only_enabled, refresh_slot_working_color, selection_changed_since_last_sync, solid_gamma, sync_fill_only,
ToolColorOptions, apply_fill_only_color_pick, apply_fill_only_enabled, refresh_slot_working_color, selection_changed_since_last_sync, solid, sync_fill_only,
};
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::resize::Resize;
@@ -20,9 +20,10 @@ use crate::messages::tool::utility_types::ToolRefreshOptions;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput};
use graphene_std::choice_type::ChoiceTypeStatic;
use graphene_std::color::SRGBA8;
use graphene_std::renderer::Quad;
use graphene_std::text::{Font, FontCache, TextAlign, TypesettingConfig, lines_clipping};
use graphene_std::vector::style::{Fill, FillChoice};
use graphene_std::vector::style::{Fill, FillChoice, FillChoiceUI};
use graphene_std::{Color, NodeInputDecleration};
#[derive(Default, ExtractField)]
@@ -269,12 +270,12 @@ impl TextTool {
fn layout(&self, font_catalog: &FontCatalog, document: &DocumentMessageHandler) -> Layout {
let mut widgets = vec![
ColorInput::new(self.options.fill.fill_choice.clone().unwrap_or(graphene_std::vector::style::FillChoice::None))
ColorInput::new(FillChoiceUI::from(self.options.fill.fill_choice.as_ref().unwrap_or(&FillChoice::None)))
.mixed(self.options.fill.fill_choice.is_none())
.narrow(true)
.on_update(|color: &ColorInput| {
TextToolMessage::UpdateOptions {
options: TextOptionsUpdate::FillColor(color.value.clone()),
options: TextOptionsUpdate::FillColor(FillChoice::from(&color.value)),
}
.into()
})
@@ -295,7 +296,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Text
// reset the displayed fill color so the next activation starts fresh from the current working color.
// Guarded on `Ready` so Esc-mid-editing (which also fires Abort) doesn't wipe the user's customized fill option.
if matches!(&message, ToolMessage::Text(TextToolMessage::Abort)) && self.fsm_state == TextToolFsmState::Ready {
self.options.fill.fill_choice = Some(solid_gamma(context.global_tool_data.primary_color));
self.options.fill.fill_choice = Some(solid(context.global_tool_data.primary_color));
}
let options = match message {
@@ -319,7 +320,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Text
if can_edit_selected(context.document).is_some() {
sync_fill_only(&mut self.options.fill, true, context.global_tool_data.primary_color, context.document, selection_changed);
} else if selection_changed {
self.options.fill.fill_choice = Some(solid_gamma(context.global_tool_data.primary_color));
self.options.fill.fill_choice = Some(solid(context.global_tool_data.primary_color));
self.options.fill.tracks_working_color = true;
}
// Text tool has no fill checkbox; keep enabled so new text never starts with `None`
@@ -497,7 +498,7 @@ impl TextToolData {
text: editing_text.text.clone(),
line_height_ratio: editing_text.typesetting.line_height_ratio,
font_size: editing_text.typesetting.font_size,
color: editing_text.color.map_or("#000000".to_string(), |color| format!("#{}", color.to_rgba_hex_srgb())),
color: editing_text.color.map_or("#000000".to_string(), |color| SRGBA8::from(color).to_css_hex()),
font_data: font_cache.get(&editing_text.font).map(|(data, _)| data.clone()).unwrap_or_default().into(),
transform: editing_text.transform.to_cols_array(),
max_width: editing_text.typesetting.max_width,
@@ -574,7 +575,7 @@ impl TextToolData {
});
responses.add(GraphOperationMessage::FillSet {
layer: self.layer,
fill: if let Some(color) = editing_text.color { Fill::Solid(color.to_gamma_srgb()) } else { Fill::None },
fill: if let Some(color) = editing_text.color { Fill::Solid(color) } else { Fill::None },
});
let transform = editing_text.transform;
self.editing_text = Some(editing_text);
+2 -3
View File
@@ -14,6 +14,7 @@ use crate::messages::preferences::PreferencesMessageHandler;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeType;
use crate::node_graph_executor::NodeGraphExecutor;
use graphene_std::color::SRGBA8;
use graphene_std::raster::color::Color;
use std::borrow::Cow;
use std::fmt::{self, Debug};
@@ -128,9 +129,7 @@ pub struct DocumentToolData {
impl DocumentToolData {
pub fn update_working_colors(&self, responses: &mut VecDeque<Message>) {
let layout = Layout(vec![
LayoutGroup::row(vec![
WorkingColorsInput::new(self.primary_color.to_gamma_srgb(), self.secondary_color.to_gamma_srgb()).widget_instance(),
]),
LayoutGroup::row(vec![WorkingColorsInput::new(SRGBA8::from(self.primary_color), SRGBA8::from(self.secondary_color)).widget_instance()]),
LayoutGroup::row(vec![
IconButton::new("SwapVertical", 16)
.tooltip_label("Swap Working Colors")