Port the color picker popover to a Rust-defined layout (#4102)

* Break out VisualColorPickersInput.svelte

* Break out ColorComparisonInput.svelte and ColorPresetsInput.svelte

* Add backend definitions and plumbing for the 4 new widgets

* Port the ColorPicker.svelte layout and business logic to Rust

* Port more ColorComparisonInput.svelte logic to Rust

* Port more SpectrumInput.svelte logic to Rust

* Port more frontend logic to Rust

* Code review

* Code review

* Fix some CSS
This commit is contained in:
Keavon Chambers
2026-05-05 02:47:53 -07:00
committed by GitHub
parent 62203cb171
commit e59612c4ce
31 changed files with 2260 additions and 1333 deletions
+14 -24
View File
@@ -24,7 +24,6 @@ use editor::messages::tool::tool_messages::tool_prelude::WidgetId;
use graph_craft::document::NodeId;
use graphene_std::graphene_hash::CacheHashWrapper;
use graphene_std::raster::color::Color;
use graphene_std::vector::GradientStops;
use serde::Serialize;
use serde_wasm_bindgen::{self, from_value};
use std::cell::RefCell;
@@ -703,6 +702,20 @@ impl EditorWrapper {
Ok(())
}
/// Initialize the Rust color picker handler with a starting value (used when the frontend `<ColorPicker>` opens).
#[wasm_bindgen(js_name = openColorPicker)]
pub fn open_color_picker(&self, initial_value: JsValue, allow_none: bool, disabled: bool) -> Result<(), JsValue> {
let initial_value = serde_wasm_bindgen::from_value(initial_value).map_err(|e| Error::new(&format!("Invalid initial picker value: {e}")))?;
self.dispatch(ColorPickerMessage::Open { initial_value, allow_none, disabled });
Ok(())
}
/// Tell the Rust color picker handler that the popover is closing.
#[wasm_bindgen(js_name = closeColorPicker)]
pub fn close_color_picker(&self) {
self.dispatch(ColorPickerMessage::Close);
}
/// Update the color of the currently-edited gradient stop
#[wasm_bindgen(js_name = updateGradientStopColor)]
pub fn update_gradient_stop_color(&self, red: f32, green: f32, blue: f32, alpha: f32) -> Result<(), JsValue> {
@@ -999,26 +1012,3 @@ pub fn evaluate_math_expression(expression: &str) -> Option<f64> {
};
Some(real)
}
#[wasm_bindgen(js_name = sampleInterpolatedGradient)]
pub fn sample_interpolated_gradient(position: Vec<f64>, midpoint: Vec<f64>, color: Vec<JsValue>, omit_alpha: bool) -> String {
let color = color.into_iter().filter_map(|c| serde_wasm_bindgen::from_value(c).ok()).collect();
GradientStops { position, midpoint, color }
.interpolated_samples()
.into_iter()
.map(|(position, color, _)| {
let hex = if omit_alpha { color.to_rgb_hex_srgb_from_gamma() } else { color.to_rgba_hex_srgb_from_gamma() };
let percent = ((position * 100.) * 1e2).round() / 1e2;
format!("#{hex} {percent}%")
})
.collect::<Vec<_>>()
.join(", ")
}
#[wasm_bindgen(js_name = evaluateGradientAtPosition)]
pub fn evaluate_gradient_at_position(t: f64, position: Vec<f64>, midpoint: Vec<f64>, color: Vec<JsValue>) -> JsValue {
let color = color.into_iter().filter_map(|c| serde_wasm_bindgen::from_value(c).ok()).collect();
let color = GradientStops { position, midpoint, color }.evaluate(t);
serde_wasm_bindgen::to_value(&color).unwrap()
}