mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 11:18:11 +08:00
Clean up code for drawing overlays to accept sRGB hex codes instead of Color structs (#3839)
* Clean up code for drawing overlays to accept sRGB hex codes instead of Color structs * Consolidate hex code parsing functions
This commit is contained in:
@@ -9,7 +9,7 @@ use graphene_std::vector::style::FillChoice;
|
||||
|
||||
fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, spacing: DVec2) {
|
||||
let origin = document.snapping_state.grid.origin;
|
||||
let grid_color = "#".to_string() + &document.snapping_state.grid.grid_color.to_rgba_hex_srgb();
|
||||
let grid_color = document.snapping_state.grid.color.as_str();
|
||||
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {
|
||||
return;
|
||||
};
|
||||
@@ -38,7 +38,7 @@ fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context:
|
||||
} else {
|
||||
DVec2::new(secondary_pos, primary_end)
|
||||
};
|
||||
overlay_context.line(document_to_viewport.transform_point2(start), document_to_viewport.transform_point2(end), Some(&grid_color), None);
|
||||
overlay_context.line(document_to_viewport.transform_point2(start), document_to_viewport.transform_point2(end), Some(grid_color), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context:
|
||||
// TODO: Implement this with a dashed line (`set_line_dash`), with integer spacing which is continuously adjusted to correct the accumulated error.
|
||||
fn grid_overlay_rectangular_dot(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, spacing: DVec2) {
|
||||
let origin = document.snapping_state.grid.origin;
|
||||
let grid_color = "#".to_string() + &document.snapping_state.grid.grid_color.to_rgba_hex_srgb();
|
||||
let grid_color = document.snapping_state.grid.color.as_str();
|
||||
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {
|
||||
return;
|
||||
};
|
||||
@@ -80,13 +80,13 @@ fn grid_overlay_rectangular_dot(document: &DocumentMessageHandler, overlay_conte
|
||||
let x_per_dot = (end.x - start.x) / total_dots;
|
||||
for dot_index in 0..=total_dots as usize {
|
||||
let exact_x = x_per_dot * dot_index as f64;
|
||||
overlay_context.pixel(document_to_viewport.transform_point2(DVec2::new(start.x + exact_x, start.y)).round(), Some(&grid_color))
|
||||
overlay_context.pixel(document_to_viewport.transform_point2(DVec2::new(start.x + exact_x, start.y)).round(), Some(grid_color))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, y_axis_spacing: f64, angle_a: f64, angle_b: f64) {
|
||||
let grid_color = "#".to_string() + &document.snapping_state.grid.grid_color.to_rgba_hex_srgb();
|
||||
let grid_color = document.snapping_state.grid.color.as_str();
|
||||
let cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap();
|
||||
let origin = document.snapping_state.grid.origin;
|
||||
let document_to_viewport = document
|
||||
@@ -111,7 +111,7 @@ fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &m
|
||||
let x_pos = (((min_x - origin.x) / spacing).ceil() + line_index as f64) * spacing + origin.x;
|
||||
let start = DVec2::new(x_pos, min_y);
|
||||
let end = DVec2::new(x_pos, max_y);
|
||||
overlay_context.line(document_to_viewport.transform_point2(start), document_to_viewport.transform_point2(end), Some(&grid_color), None);
|
||||
overlay_context.line(document_to_viewport.transform_point2(start), document_to_viewport.transform_point2(end), Some(grid_color), None);
|
||||
}
|
||||
|
||||
for (tan, multiply) in [(tan_a, -1.), (tan_b, 1.)] {
|
||||
@@ -125,13 +125,13 @@ fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &m
|
||||
let y_pos = (((inverse_project(&min_y) - origin.y) / spacing).ceil() + line_index as f64) * spacing + origin.y;
|
||||
let start = DVec2::new(min_x, project(&DVec2::new(min_x, y_pos)));
|
||||
let end = DVec2::new(max_x, project(&DVec2::new(max_x, y_pos)));
|
||||
overlay_context.line(document_to_viewport.transform_point2(start), document_to_viewport.transform_point2(end), Some(&grid_color), None);
|
||||
overlay_context.line(document_to_viewport.transform_point2(start), document_to_viewport.transform_point2(end), Some(grid_color), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn grid_overlay_isometric_dot(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, y_axis_spacing: f64, angle_a: f64, angle_b: f64) {
|
||||
let grid_color = "#".to_string() + &document.snapping_state.grid.grid_color.to_rgba_hex_srgb();
|
||||
let grid_color = document.snapping_state.grid.color.as_str();
|
||||
let cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap();
|
||||
let origin = document.snapping_state.grid.origin;
|
||||
let document_to_viewport = document
|
||||
@@ -173,7 +173,7 @@ fn grid_overlay_isometric_dot(document: &DocumentMessageHandler, overlay_context
|
||||
overlay_context.dashed_line(
|
||||
document_to_viewport.transform_point2(start),
|
||||
document_to_viewport.transform_point2(end),
|
||||
Some(&grid_color),
|
||||
Some(grid_color),
|
||||
None,
|
||||
Some(1.),
|
||||
Some((spacing_x / cos_a) * document_to_viewport.matrix2.x_axis.length() - 1.),
|
||||
@@ -220,13 +220,6 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
|
||||
}
|
||||
})
|
||||
};
|
||||
let update_color = |grid, update: fn(&mut GridSnapping) -> Option<&mut Color>| {
|
||||
update_val::<ColorInput, _>(grid, move |grid, color| {
|
||||
if let (Some(color), Some(update_color)) = (color.value.as_solid(), update(grid)) {
|
||||
*update_color = color.to_linear_srgb();
|
||||
}
|
||||
})
|
||||
};
|
||||
let update_display = |grid, update: fn(&mut GridSnapping) -> Option<&mut bool>| {
|
||||
update_val::<CheckboxInput, _>(grid, move |grid, checkbox| {
|
||||
if let Some(update) = update(grid) {
|
||||
@@ -285,10 +278,14 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
|
||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||
]);
|
||||
color_widgets.push(
|
||||
ColorInput::new(FillChoice::Solid(grid.grid_color.to_gamma_srgb()))
|
||||
ColorInput::new(FillChoice::Solid(Color::from_hex_str(&grid.color).unwrap_or(Color::BLACK)))
|
||||
.tooltip_label("Grid Display Color")
|
||||
.allow_none(false)
|
||||
.on_update(update_color(grid, |grid| Some(&mut grid.grid_color)))
|
||||
.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());
|
||||
}
|
||||
}))
|
||||
.widget_instance(),
|
||||
);
|
||||
widgets.push(LayoutGroup::Row { widgets: color_widgets });
|
||||
|
||||
@@ -249,3 +249,15 @@ pub fn text_width(text: &str, font_size: f64) -> f64 {
|
||||
let bounds = text_context.bounding_box(text, &font, &GLOBAL_FONT_CACHE, typesetting, false);
|
||||
bounds.x
|
||||
}
|
||||
|
||||
pub fn hex_to_rgba_u8(hex: &str) -> [u8; 4] {
|
||||
let hex = hex.trim().trim_start_matches('#');
|
||||
if hex.len() != 6 && hex.len() != 8 {
|
||||
return [0, 0, 0, 255];
|
||||
}
|
||||
let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);
|
||||
let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0);
|
||||
let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0);
|
||||
let a = if hex.len() >= 8 { u8::from_str_radix(&hex[6..8], 16).unwrap_or(255) } else { 255 };
|
||||
[r, g, b, a]
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
use crate::consts::{
|
||||
ARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLACK, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW,
|
||||
ARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLACK, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_WHITE_05, COLOR_OVERLAY_YELLOW,
|
||||
COLOR_OVERLAY_YELLOW_DULL, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS,
|
||||
GRADIENT_MIDPOINT_DIAMOND_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,
|
||||
};
|
||||
use crate::messages::portfolio::document::overlays::utility_functions::{GLOBAL_FONT_CACHE, GLOBAL_TEXT_CONTEXT};
|
||||
use crate::messages::portfolio::document::overlays::utility_functions::{GLOBAL_FONT_CACHE, GLOBAL_TEXT_CONTEXT, hex_to_rgba_u8};
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::prelude::Message;
|
||||
use crate::messages::prelude::ViewportMessageHandler;
|
||||
use core::borrow::Borrow;
|
||||
use core::f64::consts::{FRAC_PI_2, PI, TAU};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::subpath::{self, Subpath};
|
||||
use graphene_std::table::Table;
|
||||
@@ -404,9 +403,9 @@ impl OverlayContext {
|
||||
self.internal().fill_path(subpaths, transform, color);
|
||||
}
|
||||
|
||||
/// Fills the area inside the path with a pattern. Assumes `color` is in gamma space.
|
||||
/// Fills the area inside the path with a pattern. Assumes `color` is an sRGB hex string.
|
||||
/// Used by the fill tool to show the area to be filled.
|
||||
pub fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &Color) {
|
||||
pub fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {
|
||||
self.internal().fill_path_pattern(subpaths, transform, color);
|
||||
}
|
||||
|
||||
@@ -457,11 +456,7 @@ impl OverlayContextInternal {
|
||||
}
|
||||
|
||||
fn parse_color(color: &str) -> peniko::Color {
|
||||
let hex = color.trim_start_matches('#');
|
||||
let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);
|
||||
let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0);
|
||||
let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0);
|
||||
let a = if hex.len() >= 8 { u8::from_str_radix(&hex[6..8], 16).unwrap_or(255) } else { 255 };
|
||||
let [r, g, b, a] = hex_to_rgba_u8(color);
|
||||
peniko::Color::from_rgba8(r, g, b, a)
|
||||
}
|
||||
|
||||
@@ -784,12 +779,7 @@ impl OverlayContextInternal {
|
||||
|
||||
pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {
|
||||
let sign = scale.signum();
|
||||
let mut fill_color = Color::from_rgb_hex_for_overlays(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap())
|
||||
.unwrap()
|
||||
.with_alpha(0.05)
|
||||
.to_rgba_hex_srgb();
|
||||
fill_color.insert(0, '#');
|
||||
let fill_color = Some(fill_color.as_str());
|
||||
let fill_color = Some(COLOR_OVERLAY_WHITE_05);
|
||||
self.line(start + DVec2::X * radius * sign, start + DVec2::X * radius * scale.abs(), None, None);
|
||||
self.circle(start, radius, fill_color, None);
|
||||
self.circle(start, radius * scale.abs(), fill_color, None);
|
||||
@@ -820,15 +810,9 @@ impl OverlayContextInternal {
|
||||
|
||||
// Hover ring
|
||||
if show_hover_ring {
|
||||
let mut fill_color = Color::from_rgb_hex_for_overlays(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
|
||||
.unwrap()
|
||||
.with_alpha(0.5)
|
||||
.to_rgba_hex_srgb();
|
||||
fill_color.insert(0, '#');
|
||||
|
||||
let circle = kurbo::Circle::new((center.x, center.y), hover_ring_centerline_radius);
|
||||
self.scene
|
||||
.stroke(&kurbo::Stroke::new(hover_ring_stroke_width), transform, Self::parse_color(&fill_color), None, &circle);
|
||||
.stroke(&kurbo::Stroke::new(hover_ring_stroke_width), transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &circle);
|
||||
}
|
||||
|
||||
// Arrows
|
||||
@@ -1067,16 +1051,16 @@ impl OverlayContextInternal {
|
||||
self.scene.fill(peniko::Fill::NonZero, self.get_transform(), Self::parse_color(color), None, &path);
|
||||
}
|
||||
|
||||
/// Fills the area inside the path with a pattern. Assumes `color` is in gamma space.
|
||||
/// Fills the area inside the path with a pattern. Assumes `color` is an sRGB hex string.
|
||||
/// Used by the fill tool to show the area to be filled.
|
||||
fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &Color) {
|
||||
fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {
|
||||
const PATTERN_WIDTH: u32 = 4;
|
||||
const PATTERN_HEIGHT: u32 = 4;
|
||||
|
||||
// Create a 4x4 pixel pattern with colored pixels at (0,0) and (2,2)
|
||||
// This matches the Canvas2D checkerboard pattern
|
||||
let mut data = vec![0u8; (PATTERN_WIDTH * PATTERN_HEIGHT * 4) as usize];
|
||||
let rgba = color.to_rgba8_srgb();
|
||||
let rgba = hex_to_rgba_u8(color);
|
||||
|
||||
// ┌▄▄┬──┬──┬──┐
|
||||
// ├▀▀┼──┼──┼──┤
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::utility_functions::overlay_canvas_context;
|
||||
use super::utility_functions::{hex_to_rgba_u8, overlay_canvas_context};
|
||||
use crate::consts::{
|
||||
ARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLACK, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW,
|
||||
ARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLACK, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_WHITE_05, COLOR_OVERLAY_YELLOW,
|
||||
COLOR_OVERLAY_YELLOW_DULL, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS,
|
||||
GRADIENT_MIDPOINT_DIAMOND_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SEGMENT_SELECTED_THICKNESS,
|
||||
SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,
|
||||
@@ -11,7 +11,6 @@ use crate::messages::viewport::ViewportMessageHandler;
|
||||
use core::borrow::Borrow;
|
||||
use core::f64::consts::{FRAC_PI_2, PI, TAU};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
@@ -698,12 +697,7 @@ impl OverlayContext {
|
||||
|
||||
pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {
|
||||
let sign = scale.signum();
|
||||
let mut fill_color = Color::from_rgb_hex_for_overlays(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap())
|
||||
.unwrap()
|
||||
.with_alpha(0.05)
|
||||
.to_rgba_hex_srgb();
|
||||
fill_color.insert(0, '#');
|
||||
let fill_color = Some(fill_color.as_str());
|
||||
let fill_color = Some(COLOR_OVERLAY_WHITE_05);
|
||||
self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None, None);
|
||||
self.circle(start, radius, fill_color, None);
|
||||
self.circle(start, radius * scale.abs(), fill_color, None);
|
||||
@@ -738,16 +732,10 @@ impl OverlayContext {
|
||||
|
||||
// Hover ring
|
||||
if show_hover_ring {
|
||||
let mut fill_color = Color::from_rgb_hex_for_overlays(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
|
||||
.unwrap()
|
||||
.with_alpha(0.5)
|
||||
.to_rgba_hex_srgb();
|
||||
fill_color.insert(0, '#');
|
||||
|
||||
self.render_context.set_line_width(HOVER_RING_STROKE_WIDTH);
|
||||
self.render_context.begin_path();
|
||||
self.render_context.arc(center.x, center.y, HOVER_RING_CENTERLINE_RADIUS, 0., TAU).expect("Failed to draw hover ring");
|
||||
self.render_context.set_stroke_style_str(&fill_color);
|
||||
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50);
|
||||
self.render_context.stroke();
|
||||
}
|
||||
|
||||
@@ -1017,9 +1005,9 @@ impl OverlayContext {
|
||||
self.render_context.fill();
|
||||
}
|
||||
|
||||
/// Fills the area inside the path with a pattern. Assumes `color` is in gamma space.
|
||||
/// Fills the area inside the path with a pattern. Assumes `color` is an sRGB hex string.
|
||||
/// Used by the fill tool to show the area to be filled.
|
||||
pub fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &Color) {
|
||||
pub fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {
|
||||
const PATTERN_WIDTH: usize = 4;
|
||||
const PATTERN_HEIGHT: usize = 4;
|
||||
|
||||
@@ -1035,6 +1023,8 @@ impl OverlayContext {
|
||||
// 4x4 pixels, 4 components (RGBA) per pixel
|
||||
let mut data = [0_u8; 4 * PATTERN_WIDTH * PATTERN_HEIGHT];
|
||||
|
||||
let rgba = hex_to_rgba_u8(color);
|
||||
|
||||
// ┌▄▄┬──┬──┬──┐
|
||||
// ├▀▀┼──┼──┼──┤
|
||||
// ├──┼──┼▄▄┼──┤
|
||||
@@ -1043,7 +1033,7 @@ impl OverlayContext {
|
||||
let pixels = [(0, 0), (2, 2)];
|
||||
for &(x, y) in &pixels {
|
||||
let index = (x + y * PATTERN_WIDTH) * 4;
|
||||
data[index..index + 4].copy_from_slice(&color.to_rgba8_srgb());
|
||||
data[index..index + 4].copy_from_slice(&rgba);
|
||||
}
|
||||
|
||||
let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(wasm_bindgen::Clamped(&data), PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::consts::COLOR_OVERLAY_GRAY;
|
||||
use glam::DVec2;
|
||||
use graphene_std::raster::Color;
|
||||
use std::fmt;
|
||||
|
||||
#[repr(transparent)]
|
||||
@@ -216,7 +215,7 @@ pub struct GridSnapping {
|
||||
pub isometric_y_spacing: f64,
|
||||
pub isometric_angle_a: f64,
|
||||
pub isometric_angle_b: f64,
|
||||
pub grid_color: Color,
|
||||
pub color: String,
|
||||
pub dot_display: bool,
|
||||
}
|
||||
|
||||
@@ -229,7 +228,7 @@ impl Default for GridSnapping {
|
||||
isometric_y_spacing: 1.,
|
||||
isometric_angle_a: 30.,
|
||||
isometric_angle_b: 30.,
|
||||
grid_color: Color::from_rgb_hex_for_overlays(COLOR_OVERLAY_GRAY.strip_prefix('#').unwrap()).unwrap(),
|
||||
color: COLOR_OVERLAY_GRAY.to_string(),
|
||||
dot_display: false,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user