mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-20 21:46:08 +08:00
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:
@@ -21,7 +21,7 @@ pub enum HsvChannel {
|
||||
#[impl_message(Message, ColorPicker)]
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum ColorPickerMessage {
|
||||
/// Initialize the picker state from an external color/gradient and announce its options. Called by the frontend when a `<ColorPicker>` opens.
|
||||
/// Initialize the picker state from an external color/gradient and announce its options. Called by the frontend when a `<ColorPicker />` opens.
|
||||
Open { initial_value: FillChoice, allow_none: bool, disabled: bool },
|
||||
/// Clear the picker state. Called by the frontend when the popover closes.
|
||||
Close,
|
||||
|
||||
@@ -2,9 +2,10 @@ use crate::messages::color_picker::color_picker_message::{HsvChannel, RgbChannel
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::{ColorPresetsInputUpdate, SpectrumInputUpdate, SpectrumMarker, VisualColorPickersInputUpdate};
|
||||
use crate::messages::prelude::*;
|
||||
use color::{AlphaColor, Srgb};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::vector::style::{FillChoice, GradientStops};
|
||||
use graphene_std::color::SRGBA8;
|
||||
use graphene_std::core_types::misc::parse_css_color;
|
||||
use graphene_std::vector::style::{FillChoice, FillChoiceUI, GradientStops, GradientStopsUI};
|
||||
|
||||
/// Bounds for a midpoint position (relative to the interval between two adjacent gradient stops).
|
||||
const MIN_MIDPOINT: f64 = 0.01;
|
||||
@@ -105,10 +106,13 @@ impl MessageHandler<ColorPickerMessage, ()> for ColorPickerMessageHandler {
|
||||
ColorPickerMessage::SetChannelRgb { channel, value } => {
|
||||
let Some(strength) = value else { return };
|
||||
let Some(current) = self.current_color() else { return };
|
||||
// The RGB inputs are 0..255 sRGB display values; substitute the new channel into the gamma triple and lift back to linear for storage.
|
||||
let new_gamma_channel = (strength / 255.) as f32;
|
||||
let [cur_r, cur_g, cur_b, cur_a] = current.to_gamma_srgb_channels();
|
||||
let updated = match channel {
|
||||
RgbChannel::Red => Color::from_rgbaf32_unchecked((strength / 255.) as f32, current.g(), current.b(), current.a()),
|
||||
RgbChannel::Green => Color::from_rgbaf32_unchecked(current.r(), (strength / 255.) as f32, current.b(), current.a()),
|
||||
RgbChannel::Blue => Color::from_rgbaf32_unchecked(current.r(), current.g(), (strength / 255.) as f32, current.a()),
|
||||
RgbChannel::Red => Color::from_gamma_srgb_channels(new_gamma_channel, cur_g, cur_b, cur_a),
|
||||
RgbChannel::Green => Color::from_gamma_srgb_channels(cur_r, new_gamma_channel, cur_b, cur_a),
|
||||
RgbChannel::Blue => Color::from_gamma_srgb_channels(cur_r, cur_g, new_gamma_channel, cur_a),
|
||||
};
|
||||
self.adopt_color(updated);
|
||||
self.emit_color(responses);
|
||||
@@ -150,7 +154,7 @@ impl MessageHandler<ColorPickerMessage, ()> for ColorPickerMessageHandler {
|
||||
match preset {
|
||||
FillChoice::None => {
|
||||
self.set_new_hsva(0., 0., 0., 1., true);
|
||||
responses.add(FrontendMessage::ColorPickerColorChanged { value: FillChoice::None });
|
||||
responses.add(FrontendMessage::ColorPickerColorChanged { value: FillChoiceUI::None });
|
||||
}
|
||||
FillChoice::Solid(color) => {
|
||||
self.adopt_color(color);
|
||||
@@ -175,7 +179,7 @@ impl MessageHandler<ColorPickerMessage, ()> for ColorPickerMessageHandler {
|
||||
self.set_old_hsva(temp.0, temp.1, temp.2, temp.3, temp.4);
|
||||
|
||||
if self.is_none {
|
||||
responses.add(FrontendMessage::ColorPickerColorChanged { value: FillChoice::None });
|
||||
responses.add(FrontendMessage::ColorPickerColorChanged { value: FillChoiceUI::None });
|
||||
} else {
|
||||
self.emit_color(responses);
|
||||
}
|
||||
@@ -197,6 +201,7 @@ impl MessageHandler<ColorPickerMessage, ()> for ColorPickerMessageHandler {
|
||||
}
|
||||
|
||||
impl ColorPickerMessageHandler {
|
||||
// The picker's internal HSV state is HSV of sRGB display values
|
||||
fn current_color(&self) -> Option<Color> {
|
||||
if self.is_none {
|
||||
None
|
||||
@@ -239,7 +244,8 @@ impl ColorPickerMessageHandler {
|
||||
|
||||
/// Set HSV state from a Color, preserving hue and saturation in degenerate cases.
|
||||
fn adopt_color(&mut self, color: Color) {
|
||||
let [target_h, target_s, target_v] = rgb_to_hsv(color.r() as f64, color.g() as f64, color.b() as f64);
|
||||
let [target_h, target_s, target_v, target_a] = color.to_hsva();
|
||||
let (target_h, target_s, target_v, target_a) = (target_h as f64, target_s as f64, target_v as f64, target_a as f64);
|
||||
|
||||
// Preserve hue: avoid jumping from 360° (top) to 0° (bottom) and don't reset hue when the color is desaturated or fully dark.
|
||||
if !(target_h == 0. && self.hue == 1.) && target_s > 0. && target_v > 0. {
|
||||
@@ -250,7 +256,7 @@ impl ColorPickerMessageHandler {
|
||||
self.saturation = target_s;
|
||||
}
|
||||
self.value = target_v;
|
||||
self.alpha = color.a() as f64;
|
||||
self.alpha = target_a;
|
||||
self.is_none = false;
|
||||
}
|
||||
|
||||
@@ -264,9 +270,15 @@ impl ColorPickerMessageHandler {
|
||||
{
|
||||
*stop_color = color;
|
||||
let stops = gradient.clone();
|
||||
responses.add(FrontendMessage::ColorPickerColorChanged { value: FillChoice::Gradient(stops) });
|
||||
let fill_choice = FillChoice::Gradient(stops);
|
||||
responses.add(FrontendMessage::ColorPickerColorChanged {
|
||||
value: FillChoiceUI::from(&fill_choice),
|
||||
});
|
||||
} else {
|
||||
responses.add(FrontendMessage::ColorPickerColorChanged { value: FillChoice::Solid(color) });
|
||||
let fill_choice = FillChoice::Solid(color);
|
||||
responses.add(FrontendMessage::ColorPickerColorChanged {
|
||||
value: FillChoiceUI::from(&fill_choice),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,8 +376,9 @@ impl ColorPickerMessageHandler {
|
||||
}
|
||||
|
||||
self.gradient = Some(gradient.clone());
|
||||
let fill_choice = FillChoice::Gradient(gradient);
|
||||
responses.add(FrontendMessage::ColorPickerColorChanged {
|
||||
value: FillChoice::Gradient(gradient),
|
||||
value: FillChoiceUI::from(&fill_choice),
|
||||
});
|
||||
self.send_layouts(responses);
|
||||
}
|
||||
@@ -392,7 +405,7 @@ impl ColorPickerMessageHandler {
|
||||
// For gradient editing, the markers' handle colors mirror their gradient stop colors
|
||||
let markers = gradient.iter().map(|stop| SpectrumMarker::new(stop.position, stop.midpoint, stop.color)).collect();
|
||||
let mut row_widgets = vec![
|
||||
SpectrumInput::new(gradient.clone())
|
||||
SpectrumInput::new(GradientStopsUI::from(gradient))
|
||||
.markers(markers)
|
||||
.active_marker_index(self.active_marker_index)
|
||||
.active_marker_is_midpoint(self.active_marker_is_midpoint)
|
||||
@@ -453,7 +466,11 @@ impl ColorPickerMessageHandler {
|
||||
let old_color = self.old_color();
|
||||
|
||||
let hex_value = new_color.map(|c| color_to_hex_optional_alpha(&c)).unwrap_or_else(|| "-".to_string());
|
||||
let rgb_255 = new_color.map(|c| (c.r() as f64 * 255., c.g() as f64 * 255., c.b() as f64 * 255.));
|
||||
// RGB readouts display sRGB byte values to the user, so we convert from linear-light to gamma here before quantizing.
|
||||
let rgb_255 = new_color.map(|c| {
|
||||
let [r, g, b, _] = c.to_gamma_srgb_channels();
|
||||
(r as f64 * 255., g as f64 * 255., b as f64 * 255.)
|
||||
});
|
||||
|
||||
// Epsilon comparison since the picker round-trips through HSV
|
||||
let differs = match (new_color, old_color) {
|
||||
@@ -470,7 +487,7 @@ impl ColorPickerMessageHandler {
|
||||
|
||||
// New/old comparison swatch with swap button
|
||||
groups.push(LayoutGroup::row(vec![
|
||||
ColorComparisonInput::new(new_color, old_color)
|
||||
ColorComparisonInput::new(new_color.map(SRGBA8::from), old_color.map(SRGBA8::from))
|
||||
.is_none(self.is_none)
|
||||
.old_is_none(self.old_is_none)
|
||||
.disabled(self.disabled)
|
||||
@@ -566,7 +583,10 @@ impl ColorPickerMessageHandler {
|
||||
.disabled(self.disabled)
|
||||
.show_none_option(self.allow_none && self.gradient.is_none())
|
||||
.on_update(|update: &ColorPresetsInputUpdate| match update {
|
||||
ColorPresetsInputUpdate::Preset(fill_choice) => ColorPickerMessage::PickPreset { preset: fill_choice.clone() }.into(),
|
||||
ColorPresetsInputUpdate::Preset(fill_choice) => ColorPickerMessage::PickPreset {
|
||||
preset: FillChoice::from(fill_choice),
|
||||
}
|
||||
.into(),
|
||||
ColorPresetsInputUpdate::EyedropperColorCode(code) => ColorPickerMessage::EyedropperColorCode { code: code.clone() }.into(),
|
||||
})
|
||||
.widget_instance(),
|
||||
@@ -611,33 +631,10 @@ const SATURATION_DESCRIPTION: &str = "The vividness from grayscale to full color
|
||||
const VALUE_DESCRIPTION: &str = "The brightness from black to full color.";
|
||||
const ALPHA_DESCRIPTION: &str = "The level of translucency, from transparent (0%) to opaque (100%).";
|
||||
|
||||
/// Convert an `rgb(0..1)` triple to `hsv(0..1)`. Mirrors the legacy frontend `colorToHSV`.
|
||||
fn rgb_to_hsv(red: f64, green: f64, blue: f64) -> [f64; 3] {
|
||||
let max = red.max(green).max(blue);
|
||||
let min = red.min(green).min(blue);
|
||||
let delta = max - min;
|
||||
|
||||
let mut hue = if delta == 0. {
|
||||
0.
|
||||
} else if max == red {
|
||||
((green - blue) / delta).rem_euclid(6.)
|
||||
} else if max == green {
|
||||
(blue - red) / delta + 2.
|
||||
} else {
|
||||
(red - green) / delta + 4.
|
||||
};
|
||||
hue = (hue * 60. + 360.).rem_euclid(360.) / 360.;
|
||||
|
||||
let saturation = if max == 0. { 0. } else { delta / max };
|
||||
let value = max;
|
||||
|
||||
[hue, saturation, value]
|
||||
}
|
||||
|
||||
/// The popover's background color (the `--color-2-mildblack` design token, `#222`). Used by the comparison swatch's
|
||||
/// outline computation to brighten the inset border for colors close to this background.
|
||||
const POPOVER_BACKGROUND: Color = Color::from_rgbaf32_unchecked(0x22 as f32 / 255., 0x22 as f32 / 255., 0x22 as f32 / 255., 1.);
|
||||
/// The luminance window (in linear-light) within which a color is considered close enough to the popover background
|
||||
/// The popover's background color as sRGB gamma-encoded channels (the `--color-2-mildblack` design token, `#222`).
|
||||
/// Used by the comparison swatch's outline computation to brighten the inset border for colors close to this background.
|
||||
const POPOVER_BACKGROUND_GAMMA_CHANNELS: [f32; 4] = [0x22 as f32 / 255., 0x22 as f32 / 255., 0x22 as f32 / 255., 1.];
|
||||
/// The luminance window within which a color is considered close enough to the popover background
|
||||
/// to warrant an outline. Mirrors the `proximityRange` argument the legacy frontend passed to `contrastingOutlineFactor`.
|
||||
const OUTLINE_PROXIMITY_RANGE: f64 = 0.01;
|
||||
|
||||
@@ -646,61 +643,22 @@ const OUTLINE_PROXIMITY_RANGE: f64 = 0.01;
|
||||
fn contrasting_outline_factor(color: Option<Color>) -> f64 {
|
||||
let Some(color) = color else { return 0. };
|
||||
|
||||
// WCAG-style relative luminance, with alpha composited over white in gamma space
|
||||
let luminance = |color: Color| {
|
||||
// TODO: Remove the `.to_linear_srgb()` once we move to correctly treating `Color` as linear.
|
||||
Color::WHITE
|
||||
.alpha_blend(Color::from_unassociated_alpha(color.r(), color.g(), color.b(), color.a()))
|
||||
.to_linear_srgb()
|
||||
.luminance_srgb() as f64
|
||||
// WCAG-style relative luminance, with alpha composited over white in sRGB gamma space (matching the perceptual intent of `SRGBA8::contrasting_text_color`).
|
||||
let luminance_from_gamma_channels = |[r, g, b, a]: [f32; 4]| -> f64 {
|
||||
let inv_a = 1. - a;
|
||||
Color::from_gamma_srgb_channels(inv_a + r * a, inv_a + g * a, inv_a + b * a, 1.).luminance_rec_709() as f64
|
||||
};
|
||||
|
||||
let distance = (luminance(POPOVER_BACKGROUND) - luminance(color)).abs().max(0.);
|
||||
let color_gamma_channels = color.to_gamma_srgb_channels();
|
||||
let distance = (luminance_from_gamma_channels(POPOVER_BACKGROUND_GAMMA_CHANNELS) - luminance_from_gamma_channels(color_gamma_channels))
|
||||
.abs()
|
||||
.max(0.);
|
||||
let proximity = 1. - (distance / OUTLINE_PROXIMITY_RANGE).min(1.);
|
||||
let [_, saturation, _] = rgb_to_hsv(color.r() as f64, color.g() as f64, color.b() as f64);
|
||||
proximity * (1. - saturation)
|
||||
let [_, saturation, _, _] = color.to_hsva();
|
||||
proximity * (1. - saturation as f64)
|
||||
}
|
||||
|
||||
/// Format a Color as a `#`-prefixed hex string, including the alpha component only if it's not fully opaque.
|
||||
/// Format a linear `Color` as a `#`-prefixed hex string, including the alpha component only if it's not fully opaque.
|
||||
fn color_to_hex_optional_alpha(color: &Color) -> String {
|
||||
format!(
|
||||
"#{}",
|
||||
if color.a() >= 1. {
|
||||
color.to_rgb_hex_srgb_from_gamma()
|
||||
} else {
|
||||
color.to_rgba_hex_srgb_from_gamma()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Parse a CSS color string (named color, hex, `rgb(...)`, etc.) into a `Color` using the `color` crate's CSS Color 4 parser.
|
||||
/// Tries the input as-is first (catches CSS named colors like `red`, `rgb(...)`, and well-formed hex like `#abcdef`), then falls back to treating the input as bare hex with length-based expansion to a CSS-parseable form:
|
||||
/// - 1 char `f` → `#fff` (CSS 3-char shorthand)
|
||||
/// - 2 char `ab` → `#ababab` (repeated to 6 chars)
|
||||
/// - 4 char `abcd` → `#00abcd` (left-padded with `00`)
|
||||
/// - 5 char `abcde` → `#0abcde` (left-padded with `0`)
|
||||
/// - 3, 6, 8 char inputs are passed through with a `#` prefix.
|
||||
fn parse_css_color(input: &str) -> Option<Color> {
|
||||
let trimmed = input.trim();
|
||||
|
||||
let parsed = color::parse_color(trimmed).ok().or_else(|| {
|
||||
let bare = trimmed.strip_prefix('#').unwrap_or(trimmed);
|
||||
if bare.is_empty() || !bare.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
return None;
|
||||
}
|
||||
let expanded = match bare.len() {
|
||||
1 => bare.repeat(3),
|
||||
2 => bare.repeat(3),
|
||||
4 => format!("00{bare}"),
|
||||
5 => format!("0{bare}"),
|
||||
_ => bare.to_string(),
|
||||
};
|
||||
let candidate = format!("#{expanded}");
|
||||
// Avoid retrying the exact same string we just failed to parse.
|
||||
(candidate != trimmed).then(|| color::parse_color(&candidate).ok()).flatten()
|
||||
})?;
|
||||
|
||||
let srgb: AlphaColor<Srgb> = parsed.to_alpha_color();
|
||||
let [red, green, blue, alpha] = srgb.components;
|
||||
Color::from_rgbaf32(red, green, blue, alpha)
|
||||
SRGBA8::from(*color).to_css_hex()
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@ use crate::messages::portfolio::utility_types::WorkspacePanelLayout;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::tool_messages::eyedropper_tool::PrimarySecondary;
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::color::SRGBA8;
|
||||
use graphene_std::raster::Image;
|
||||
use graphene_std::raster::color::Color;
|
||||
use graphene_std::text::Font;
|
||||
use graphene_std::vector::style::FillChoice;
|
||||
use graphene_std::vector::style::FillChoiceUI;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
@@ -167,16 +167,16 @@ pub enum FrontendMessage {
|
||||
document_id: DocumentId,
|
||||
},
|
||||
UpdateGradientStopColorPickerPosition {
|
||||
color: Color, // TODO: Color (without `none`) -> Color (with `none`)
|
||||
color: SRGBA8, // TODO: Color (without `none`) -> Color (with `none`)
|
||||
position: (f64, f64),
|
||||
},
|
||||
/// The Rust color picker handler picked a new color/gradient. The frontend `<ColorPicker>` forwards this as its `colorOrGradient` event.
|
||||
/// The Rust color picker handler picked a new color/gradient. The frontend `<ColorPicker />` forwards this as its `colorOrGradient` event.
|
||||
ColorPickerColorChanged {
|
||||
value: FillChoice,
|
||||
value: FillChoiceUI,
|
||||
},
|
||||
/// The Rust color picker handler is starting an undo transaction. The frontend `<ColorPicker>` forwards this as its `startHistoryTransaction` event.
|
||||
/// The Rust color picker handler is starting an undo transaction. The frontend `<ColorPicker />` forwards this as its `startHistoryTransaction` event.
|
||||
ColorPickerStartHistoryTransaction,
|
||||
/// The Rust color picker handler is committing the in-flight undo transaction. The frontend `<ColorPicker>` forwards this as its `commitHistoryTransaction` event.
|
||||
/// The Rust color picker handler is committing the in-flight undo transaction. The frontend `<ColorPicker />` forwards this as its `commitHistoryTransaction` event.
|
||||
ColorPickerCommitHistoryTransaction,
|
||||
UpdateImportsExports {
|
||||
/// If the primary import is not visible, then it is None.
|
||||
@@ -241,7 +241,7 @@ pub enum FrontendMessage {
|
||||
svg: String,
|
||||
},
|
||||
UpdateImageData {
|
||||
image_data: Vec<(u64, Image<Color>)>,
|
||||
image_data: Vec<(u64, Image<SRGBA8>)>,
|
||||
},
|
||||
UpdateDocumentLayerDetails {
|
||||
data: LayerPanelEntry,
|
||||
|
||||
@@ -14,7 +14,7 @@ pub struct DocumentInfo {
|
||||
pub is_saved: bool,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(large_number_types_as_bigints, from_wasm_abi))]
|
||||
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct PersistedState {
|
||||
pub documents: Vec<DocumentInfo>,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
use graphene_std::vector::style::FillChoice;
|
||||
use graphene_std::color::SRGBA8;
|
||||
use graphene_std::vector::style::FillChoiceUI;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -179,11 +180,11 @@ impl LayoutMessageHandler {
|
||||
let callback_message = match action {
|
||||
WidgetValueAction::Commit => (color_button.on_commit.callback)(&()),
|
||||
WidgetValueAction::Update => {
|
||||
let Ok(fill_choice) = serde_json::from_value::<FillChoice>(value) else {
|
||||
warn!("ColorInput update was not able to be parsed as FillChoice: {color_button:?}");
|
||||
let Ok(fill_choice_ui) = serde_json::from_value::<FillChoiceUI>(value) else {
|
||||
warn!("ColorInput update was not able to be parsed as FillChoiceUI: {color_button:?}");
|
||||
return;
|
||||
};
|
||||
color_button.value = fill_choice;
|
||||
color_button.value = fill_choice_ui;
|
||||
(color_button.on_update.callback)(color_button)
|
||||
}
|
||||
};
|
||||
@@ -496,25 +497,14 @@ fn populate_computed_display_fields(layout: &mut Layout) {
|
||||
}
|
||||
Widget::SpectrumInput(spectrum_input) => {
|
||||
spectrum_input.track_css = spectrum_input.track.to_css_linear_gradient();
|
||||
spectrum_input.track_start_css = spectrum_input
|
||||
.track
|
||||
.color
|
||||
.first()
|
||||
.map(|color| format!("#{}", color.to_rgba_hex_srgb_from_gamma()))
|
||||
.unwrap_or_else(|| "black".to_string());
|
||||
spectrum_input.track_end_css = spectrum_input
|
||||
.track
|
||||
.color
|
||||
.last()
|
||||
.map(|color| format!("#{}", color.to_rgba_hex_srgb_from_gamma()))
|
||||
.unwrap_or_else(|| "black".to_string());
|
||||
spectrum_input.track_start_css = spectrum_input.track.color.first().map(|color| color.to_css_hex()).unwrap_or_else(|| "black".to_string());
|
||||
spectrum_input.track_end_css = spectrum_input.track.color.last().map(|color| color.to_css_hex()).unwrap_or_else(|| "black".to_string());
|
||||
}
|
||||
Widget::ColorComparisonInput(comparison) => {
|
||||
use graphene_std::Color;
|
||||
let contrasting = |color: Option<Color>| format!("#{}", color.map_or(Color::BLACK, |color| color.contrasting_text_color_from_gamma()).to_rgba_hex_srgb_from_gamma());
|
||||
comparison.new_color_css = comparison.new_color.map(|color| format!("#{}", color.to_rgba_hex_srgb_from_gamma())).unwrap_or_default();
|
||||
let contrasting = |color: Option<SRGBA8>| color.map_or(SRGBA8::BLACK, |color| color.contrasting_text_color()).to_css_hex();
|
||||
comparison.new_color_css = comparison.new_color.map(|color| color.to_css_hex()).unwrap_or_default();
|
||||
comparison.new_color_contrasting = contrasting(comparison.new_color);
|
||||
comparison.old_color_css = comparison.old_color.map(|color| format!("#{}", color.to_rgba_hex_srgb_from_gamma())).unwrap_or_default();
|
||||
comparison.old_color_css = comparison.old_color.map(|color| color.to_css_hex()).unwrap_or_default();
|
||||
comparison.old_color_contrasting = contrasting(comparison.old_color);
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@@ -22,7 +22,7 @@ impl core::fmt::Display for WidgetId {
|
||||
|
||||
macro_rules! define_layout_target {
|
||||
($($(#[$attr:meta])* $variant:ident),* $(,)?) => {
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(from_wasm_abi))]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Eq, Copy, serde::Serialize, serde::Deserialize)]
|
||||
#[repr(u8)]
|
||||
pub enum LayoutTarget {
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::WidgetCallback;
|
||||
use derivative::*;
|
||||
use graphene_std::vector::style::FillChoice;
|
||||
use graphene_std::vector::style::FillChoiceUI;
|
||||
use graphite_proc_macros::WidgetBuilder;
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
@@ -187,11 +187,10 @@ pub struct ImageButton {
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
pub struct ColorInput {
|
||||
// Content
|
||||
/// WARNING: The colors are gamma, not linear!
|
||||
#[widget_builder(constructor)]
|
||||
pub value: FillChoice,
|
||||
pub value: FillChoiceUI,
|
||||
/// CSS `linear-gradient(...)` (or solid-color stand-in) for the swatch's `background-image`. Auto-populated from `value` at layout-send time.
|
||||
/// `None` when `value` is `FillChoice::None`, in which case the frontend uses its "none" fallback styling.
|
||||
/// `None` when `value` is `FillChoiceUI::None`, in which case the frontend uses its "none" fallback styling.
|
||||
#[serde(rename = "chosenGradient")]
|
||||
#[widget_builder(skip)]
|
||||
pub chosen_gradient: Option<String>,
|
||||
|
||||
@@ -4,8 +4,9 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
|
||||
use derivative::*;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::color::SRGBA8;
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use graphene_std::vector::style::{FillChoice, GradientStops};
|
||||
use graphene_std::vector::style::{FillChoiceUI, GradientStopsUI};
|
||||
use graphite_proc_macros::WidgetBuilder;
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
@@ -349,9 +350,9 @@ pub struct RadioEntryData {
|
||||
pub struct WorkingColorsInput {
|
||||
// Content
|
||||
#[widget_builder(constructor)]
|
||||
pub primary: Color,
|
||||
pub primary: SRGBA8,
|
||||
#[widget_builder(constructor)]
|
||||
pub secondary: Color,
|
||||
pub secondary: SRGBA8,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
@@ -484,10 +485,10 @@ pub struct ColorComparisonInput {
|
||||
// Content
|
||||
#[widget_builder(constructor)]
|
||||
#[serde(rename = "newColor")]
|
||||
pub new_color: Option<Color>,
|
||||
pub new_color: Option<SRGBA8>,
|
||||
#[widget_builder(constructor)]
|
||||
#[serde(rename = "oldColor")]
|
||||
pub old_color: Option<Color>,
|
||||
pub old_color: Option<SRGBA8>,
|
||||
#[serde(rename = "isNone")]
|
||||
pub is_none: bool,
|
||||
#[serde(rename = "oldIsNone")]
|
||||
@@ -544,7 +545,7 @@ pub struct ColorPresetsInput {
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum ColorPresetsInputUpdate {
|
||||
Preset(FillChoice),
|
||||
Preset(FillChoiceUI),
|
||||
EyedropperColorCode(String),
|
||||
}
|
||||
|
||||
@@ -555,7 +556,7 @@ pub struct SpectrumInput {
|
||||
// Content
|
||||
/// The colored gradient drawn behind the markers (display-only, caller-owned).
|
||||
#[widget_builder(constructor)]
|
||||
pub track: GradientStops,
|
||||
pub track: GradientStopsUI,
|
||||
/// CSS `linear-gradient(...)` string for the track strip's `background-image`. Auto-populated from `track` at layout-send time.
|
||||
#[serde(rename = "trackCSS")]
|
||||
#[widget_builder(skip)]
|
||||
@@ -607,14 +608,14 @@ pub struct SpectrumMarker {
|
||||
position: f64,
|
||||
/// Position (0..1) of the midpoint between this marker and the next, used only if `show_midpoints` is true. The last marker's value is ignored.
|
||||
midpoint: f64,
|
||||
/// CSS color string for the marker handle's fill. Set via `SpectrumMarker::new` from a [`Color`] (gamma space).
|
||||
/// CSS color string for the marker handle's fill. Set via `SpectrumMarker::new` from a linear [`Color`].
|
||||
#[serde(rename = "handleColorCSS")]
|
||||
handle_color_css: String,
|
||||
}
|
||||
|
||||
impl SpectrumMarker {
|
||||
pub fn new(position: f64, midpoint: f64, handle_color: Color) -> Self {
|
||||
let handle_color_css = format!("#{}", handle_color.to_rgba_hex_srgb_from_gamma());
|
||||
let handle_color_css = SRGBA8::from(handle_color).to_css_hex();
|
||||
Self { position, midpoint, handle_color_css }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,13 @@ use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use glam::{Affine2, DAffine2, Vec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::blending::BlendMode;
|
||||
use graphene_std::color::SRGBA8;
|
||||
use graphene_std::gradient::GradientStops;
|
||||
use graphene_std::list::List;
|
||||
use graphene_std::memo::IORecord;
|
||||
use graphene_std::raster_types::{CPU, GPU, Raster};
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::vector::style::{Fill, FillChoice, GradientSpreadMethod, GradientType};
|
||||
use graphene_std::vector::style::{Fill, FillChoice, FillChoiceUI, GradientSpreadMethod, GradientType};
|
||||
use graphene_std::{Artboard, Color, Context, Graphic};
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
@@ -385,11 +386,15 @@ impl TableItemLayout for Vector {
|
||||
match self.style.fill.clone() {
|
||||
Fill::None => table_rows.push(vec![
|
||||
TextLabel::new("Fill").narrow(true).widget_instance(),
|
||||
ColorInput::new(FillChoice::None).disabled(true).menu_direction(Some(MenuDirection::Top)).narrow(true).widget_instance(),
|
||||
ColorInput::new(FillChoiceUI::None)
|
||||
.disabled(true)
|
||||
.menu_direction(Some(MenuDirection::Top))
|
||||
.narrow(true)
|
||||
.widget_instance(),
|
||||
]),
|
||||
Fill::Solid(color) => table_rows.push(vec![
|
||||
TextLabel::new("Fill").narrow(true).widget_instance(),
|
||||
ColorInput::new(FillChoice::Solid(color))
|
||||
ColorInput::new(FillChoiceUI::from(&FillChoice::Solid(color)))
|
||||
.disabled(true)
|
||||
.menu_direction(Some(MenuDirection::Top))
|
||||
.narrow(true)
|
||||
@@ -398,7 +403,7 @@ impl TableItemLayout for Vector {
|
||||
Fill::Gradient(gradient) => {
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Fill").narrow(true).widget_instance(),
|
||||
ColorInput::new(FillChoice::Gradient(gradient.stops))
|
||||
ColorInput::new(FillChoiceUI::from(&FillChoice::Gradient(gradient.stops)))
|
||||
.disabled(true)
|
||||
.menu_direction(Some(MenuDirection::Top))
|
||||
.narrow(true)
|
||||
@@ -423,7 +428,11 @@ impl TableItemLayout for Vector {
|
||||
let color = if let Some(color) = stroke.color { FillChoice::Solid(color) } else { FillChoice::None };
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke").narrow(true).widget_instance(),
|
||||
ColorInput::new(color).disabled(true).menu_direction(Some(MenuDirection::Top)).narrow(true).widget_instance(),
|
||||
ColorInput::new(FillChoiceUI::from(&color))
|
||||
.disabled(true)
|
||||
.menu_direction(Some(MenuDirection::Top))
|
||||
.narrow(true)
|
||||
.widget_instance(),
|
||||
]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke Weight").narrow(true).widget_instance(),
|
||||
@@ -561,10 +570,10 @@ impl TableItemLayout for Color {
|
||||
"Color"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format!("Color (#{})", self.to_gamma_srgb().to_rgba_hex_srgb())
|
||||
format!("Color (#{})", SRGBA8::from(*self).to_rgba_hex())
|
||||
}
|
||||
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||
ColorInput::new(FillChoice::Solid(*self))
|
||||
ColorInput::new(FillChoiceUI::from(&FillChoice::Solid(*self)))
|
||||
.disabled(true)
|
||||
.menu_direction(Some(MenuDirection::Top))
|
||||
.narrow(true)
|
||||
@@ -584,7 +593,7 @@ impl TableItemLayout for GradientStops {
|
||||
format!("Gradient ({} stops)", self.len())
|
||||
}
|
||||
fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance {
|
||||
ColorInput::new(FillChoice::Gradient(self.clone()))
|
||||
ColorInput::new(FillChoiceUI::from(&FillChoice::Gradient(self.clone())))
|
||||
.menu_direction(Some(MenuDirection::Top))
|
||||
.disabled(true)
|
||||
.narrow(true)
|
||||
|
||||
@@ -30,7 +30,7 @@ use graphene_std::text_nodes::StringCapitalization;
|
||||
use graphene_std::transform::{Footprint, ReferencePoint, ScaleType, Transform};
|
||||
use graphene_std::vector::misc::BooleanOperation;
|
||||
use graphene_std::vector::misc::{ArcType, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType};
|
||||
use graphene_std::vector::style::{Fill, FillChoice, GradientSpreadMethod, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
use graphene_std::vector::style::{Fill, FillChoice, FillChoiceUI, GradientSpreadMethod, GradientStops, GradientStopsUI, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
use graphene_std::vector::{QRCodeErrorCorrectionLevel, VectorModification};
|
||||
|
||||
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
|
||||
@@ -1164,19 +1164,19 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
|
||||
match &**tagged_value {
|
||||
TaggedValue::Color(color) => widgets.push(
|
||||
color_button
|
||||
.value(match color {
|
||||
.value(FillChoiceUI::from(&match color {
|
||||
Some(color) => FillChoice::Solid(*color),
|
||||
None => FillChoice::None,
|
||||
})
|
||||
.on_update(update_value(|input: &ColorInput| TaggedValue::Color(input.value.as_solid()), node_id, index))
|
||||
}))
|
||||
.on_update(update_value(|input: &ColorInput| TaggedValue::Color(input.value.as_solid().map(Color::from)), node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_instance(),
|
||||
),
|
||||
TaggedValue::Gradient(stops) => widgets.push(
|
||||
color_button
|
||||
.value(FillChoice::Gradient(stops.clone()))
|
||||
.value(FillChoiceUI::from(&FillChoice::Gradient(stops.clone())))
|
||||
.on_update(update_value(
|
||||
|input: &ColorInput| TaggedValue::Gradient(input.value.as_gradient().cloned().unwrap_or_default()),
|
||||
|input: &ColorInput| TaggedValue::Gradient(input.value.as_gradient().map(GradientStops::from).unwrap_or_default()),
|
||||
node_id,
|
||||
index,
|
||||
))
|
||||
@@ -1417,7 +1417,7 @@ fn build_shared_spectrum_section(node_id: NodeId, context: &mut NodePropertiesCo
|
||||
|
||||
// Build the shared spectrum widget (placed on the first non-exposed row)
|
||||
let spectrum_widget = (!spectrum_markers.is_empty()).then(|| {
|
||||
SpectrumInput::new(bw_track())
|
||||
SpectrumInput::new(GradientStopsUI::from(&bw_track()))
|
||||
.markers(spectrum_markers)
|
||||
.show_midpoints(false)
|
||||
.allow_insert(false)
|
||||
@@ -1591,7 +1591,7 @@ fn spectrum_slider_row(
|
||||
|
||||
let position_to_value = move |position: f64| value_min + position * value_range;
|
||||
row.push(
|
||||
SpectrumInput::new(track)
|
||||
SpectrumInput::new(GradientStopsUI::from(&track))
|
||||
.markers(vec![SpectrumMarker::new(position, 0.5, handle_color)])
|
||||
.show_midpoints(false)
|
||||
.allow_insert(false)
|
||||
@@ -2482,7 +2482,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
widgets_first_row.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
|
||||
widgets_first_row.push(
|
||||
ColorInput::default()
|
||||
.value(fill.clone().into())
|
||||
.value(FillChoiceUI::from(&FillChoice::from(fill.clone())))
|
||||
.on_update(move |x: &ColorInput| Message::Batched {
|
||||
messages: Box::new([
|
||||
match &fill2 {
|
||||
@@ -2508,7 +2508,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
NodeGraphMessage::SetInputValue {
|
||||
node_id,
|
||||
input_index: FillInput::<Color>::INDEX,
|
||||
value: TaggedValue::Fill(x.value.to_fill(fill2.as_gradient())),
|
||||
value: TaggedValue::Fill(FillChoice::from(&x.value).to_fill(fill2.as_gradient())),
|
||||
}
|
||||
.into(),
|
||||
]),
|
||||
|
||||
@@ -3,9 +3,9 @@ use crate::messages::portfolio::document::overlays::utility_types::OverlayContex
|
||||
use crate::messages::portfolio::document::utility_types::misc::{GridSnapping, GridType};
|
||||
use crate::messages::prelude::*;
|
||||
use glam::DVec2;
|
||||
use graphene_std::raster::color::Color;
|
||||
use graphene_std::color::SRGBA8;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::vector::style::FillChoice;
|
||||
use graphene_std::vector::style::FillChoiceUI;
|
||||
|
||||
fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, spacing: DVec2) {
|
||||
let origin = document.snapping_state.grid.origin;
|
||||
@@ -274,12 +274,12 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
|
||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||
]);
|
||||
color_widgets.push(
|
||||
ColorInput::new(FillChoice::Solid(Color::from_hex_str(&grid.color).unwrap_or(Color::BLACK)))
|
||||
ColorInput::new(FillChoiceUI::Solid(SRGBA8::from_hex_str(&grid.color).unwrap_or(SRGBA8::BLACK)))
|
||||
.tooltip_label("Grid Display Color")
|
||||
.allow_none(false)
|
||||
.on_update(update_val::<ColorInput, _>(grid, |grid, color| {
|
||||
if let Some(color) = color.value.as_solid() {
|
||||
grid.color = format!("#{}", color.to_rgba_hex_srgb_from_gamma());
|
||||
if let Some(srgba) = color.value.as_solid() {
|
||||
grid.color = srgba.to_css_hex();
|
||||
}
|
||||
}))
|
||||
.widget_instance(),
|
||||
|
||||
@@ -999,13 +999,10 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
});
|
||||
|
||||
// Add default fill and stroke to the layer
|
||||
let fill_color = Color::WHITE;
|
||||
let stroke_color = Color::BLACK;
|
||||
|
||||
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 });
|
||||
|
||||
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 });
|
||||
|
||||
// Create new point ids and add those into the existing Vector path
|
||||
|
||||
@@ -34,6 +34,7 @@ impl FontCatalog {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(from_wasm_abi))]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FontCatalogFamily {
|
||||
/// The font family name.
|
||||
@@ -55,6 +56,7 @@ impl FontCatalogFamily {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FontCatalogStyle {
|
||||
pub weight: u32,
|
||||
@@ -88,7 +90,7 @@ impl FontCatalogStyle {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(from_wasm_abi))]
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum PanelType {
|
||||
Welcome,
|
||||
@@ -124,7 +126,7 @@ impl PanelType {
|
||||
pub struct PanelGroupId(pub u64);
|
||||
|
||||
/// Which edge of a panel group to split on when docking a dragged panel.
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(from_wasm_abi))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum DockingSplitDirection {
|
||||
Left,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -6,6 +6,7 @@ use graph_craft::document::value::{RenderOutput, RenderOutputType, TaggedValue};
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
|
||||
use graph_craft::proto::GraphErrors;
|
||||
use graphene_std::application_io::{NodeGraphUpdateMessage, RenderConfig, TimingInformation};
|
||||
use graphene_std::color::SRGBA8;
|
||||
use graphene_std::raster::{CPU, Raster};
|
||||
use graphene_std::renderer::RenderMetadata;
|
||||
use graphene_std::text::FontCache;
|
||||
@@ -398,7 +399,21 @@ impl NodeGraphExecutor {
|
||||
|
||||
match render_output.data {
|
||||
RenderOutputType::Svg { svg, image_data } => {
|
||||
// Send to frontend
|
||||
// Convert each linear-light `Image<Color>` into the JS-boundary `Image<SRGBA8>` form (gamma byte channels) before dispatching.
|
||||
let image_data = image_data
|
||||
.into_iter()
|
||||
.map(|(id, image)| {
|
||||
(
|
||||
id,
|
||||
graphene_std::raster::Image {
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
data: image.data.iter().map(|&c| SRGBA8::from(c)).collect(),
|
||||
base64_string: image.base64_string,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
responses.add(FrontendMessage::UpdateImageData { image_data });
|
||||
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user