Add grid color customization and choice to display as dots (#1743)
* dot grid * fix warning: unreachable pattern * grid color select * add color for all grid types * Dot grid checkbox and remove prefixed Color functions * Display dot grid as grid aligned pixels * Dashed line comment * Code review and UI design widget placement updates * Isometric dotted grid * Early return when cos = 0 * Add spacing, x offset, and color to dot grids --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
1bfbe306be
commit
bc33eabc3c
|
|
@ -2376,6 +2376,7 @@ dependencies = [
|
|||
"graphite-proc-macros",
|
||||
"image 0.24.9",
|
||||
"interpreted-executor",
|
||||
"js-sys",
|
||||
"log",
|
||||
"num_enum 0.6.1",
|
||||
"once_cell",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ quantization = [
|
|||
wasm = ["wasm-bindgen", "graphene-std/wasm", "wasm-bindgen-futures"]
|
||||
|
||||
[dependencies]
|
||||
js-sys = "0.3.67"
|
||||
log = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
use crate::consts::COLOR_OVERLAY_GRAY;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::misc::{GridSnapping, GridType};
|
||||
use crate::messages::prelude::*;
|
||||
use glam::DVec2;
|
||||
|
||||
use graphene_core::raster::color::Color;
|
||||
use graphene_core::renderer::Quad;
|
||||
|
||||
use glam::DVec2;
|
||||
|
||||
fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, spacing: DVec2) {
|
||||
let origin = document.snapping_state.grid.origin;
|
||||
let grid_color: Color = document.snapping_state.grid.grid_color;
|
||||
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.navigation) else {
|
||||
return;
|
||||
};
|
||||
|
|
@ -33,12 +36,60 @@ 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(COLOR_OVERLAY_GRAY));
|
||||
overlay_context.line(
|
||||
document_to_viewport.transform_point2(start),
|
||||
document_to_viewport.transform_point2(end),
|
||||
Some(&("#".to_string() + &grid_color.rgba_hex())),
|
||||
None,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In the best case, where the x distance/total dots is an integer, this will reduce draw requests from the current m(horizontal dots)*n(vertical dots) to m(horizontal lines) * 1(line changes).
|
||||
// In the worst case, where the x distance/total dots is an integer+0.5, then each pixel will require a new line, and requests will be m(horizontal lines)*n(line changes = horizontal dots).
|
||||
// The draw dashed line method will also be not grid aligned for tilted grids.
|
||||
// TODO: Potentially create an image and render the image onto the canvas a single time.
|
||||
// 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_dot(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, spacing: DVec2) {
|
||||
let origin = document.snapping_state.grid.origin;
|
||||
let grid_color = document.snapping_state.grid.grid_color;
|
||||
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.navigation) else {
|
||||
return;
|
||||
};
|
||||
let document_to_viewport = document.metadata().document_to_viewport;
|
||||
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]);
|
||||
|
||||
let min = bounds.0.iter().map(|corner| corner.y).min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();
|
||||
let max = bounds.0.iter().map(|corner| corner.y).max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();
|
||||
|
||||
let mut primary_start = bounds.0.iter().map(|corner| corner.x).min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();
|
||||
let mut primary_end = bounds.0.iter().map(|corner| corner.x).max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();
|
||||
|
||||
primary_start = (primary_start / spacing.x).floor() * spacing.x + origin.x % spacing.x;
|
||||
primary_end = (primary_end / spacing.x).floor() * spacing.x + origin.x % spacing.x;
|
||||
|
||||
// Round to avoid floating point errors
|
||||
let total_dots = ((primary_end - primary_start) / spacing.x).round();
|
||||
|
||||
for line_index in 0..=((max - min) / spacing.y).ceil() as i32 {
|
||||
let secondary_pos = (((min - origin.y) / spacing.y).ceil() + line_index as f64) * spacing.y + origin.y;
|
||||
let start = DVec2::new(primary_start, secondary_pos);
|
||||
let end = DVec2::new(primary_end, secondary_pos);
|
||||
|
||||
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(&("#".to_string() + &grid_color.rgba_hex())),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, y_axis_spacing: f64, angle_a: f64, angle_b: f64) {
|
||||
let grid_color = document.snapping_state.grid.grid_color;
|
||||
let cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap();
|
||||
let origin = document.snapping_state.grid.origin;
|
||||
let document_to_viewport = document.metadata().document_to_viewport;
|
||||
|
|
@ -60,7 +111,12 @@ 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(COLOR_OVERLAY_GRAY));
|
||||
overlay_context.line(
|
||||
document_to_viewport.transform_point2(start),
|
||||
document_to_viewport.transform_point2(end),
|
||||
Some(&("#".to_string() + &grid_color.rgba_hex())),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
for (tan, multiply) in [(tan_a, -1.), (tan_b, 1.)] {
|
||||
|
|
@ -74,15 +130,78 @@ 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(COLOR_OVERLAY_GRAY));
|
||||
overlay_context.line(
|
||||
document_to_viewport.transform_point2(start),
|
||||
document_to_viewport.transform_point2(end),
|
||||
Some(&("#".to_string() + &grid_color.rgba_hex())),
|
||||
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 = document.snapping_state.grid.grid_color;
|
||||
let cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap();
|
||||
let origin = document.snapping_state.grid.origin;
|
||||
let document_to_viewport = document.metadata().document_to_viewport;
|
||||
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]);
|
||||
let tan_a = angle_a.to_radians().tan();
|
||||
let tan_b = angle_b.to_radians().tan();
|
||||
let spacing = DVec2::new(y_axis_spacing / (tan_a + tan_b), y_axis_spacing);
|
||||
let Some(spacing_multiplier) = GridSnapping::compute_isometric_multiplier(y_axis_spacing, tan_a + tan_b, &document.navigation) else {
|
||||
return;
|
||||
};
|
||||
let isometric_spacing = spacing * spacing_multiplier;
|
||||
|
||||
let min_x = bounds.0.iter().map(|&corner| corner.x).min_by(cmp).unwrap_or_default();
|
||||
let max_x = bounds.0.iter().map(|&corner| corner.x).max_by(cmp).unwrap_or_default();
|
||||
let spacing_x = isometric_spacing.x;
|
||||
let tan = tan_a;
|
||||
let multiply = -1.0;
|
||||
let project = |corner: &DVec2| corner.y + multiply * tan * (corner.x - origin.x);
|
||||
let inverse_project = |corner: &DVec2| corner.y - tan * multiply * (corner.x - origin.x);
|
||||
let min_y = bounds.0.into_iter().min_by(|a, b| inverse_project(a).partial_cmp(&inverse_project(b)).unwrap()).unwrap_or_default();
|
||||
let max_y = bounds.0.into_iter().max_by(|a, b| inverse_project(a).partial_cmp(&inverse_project(b)).unwrap()).unwrap_or_default();
|
||||
let spacing_y = isometric_spacing.y;
|
||||
let lines = ((inverse_project(&max_y) - inverse_project(&min_y)) / spacing_y).ceil() as i32;
|
||||
|
||||
let cos_a = angle_a.to_radians().cos();
|
||||
// If cos_a is 0 then there will be no intersections and thus no dots should be drawn
|
||||
if cos_a.abs() <= 0.00001 {
|
||||
return;
|
||||
}
|
||||
let x_offset = (((min_x - origin.x) / spacing_x).ceil()) * spacing_x + origin.x - min_x;
|
||||
for line_index in 0..=lines {
|
||||
let y_pos = (((inverse_project(&min_y) - origin.y) / spacing_y).ceil() + line_index as f64) * spacing_y + origin.y;
|
||||
let start = DVec2::new(min_x + x_offset, project(&DVec2::new(min_x + x_offset, y_pos)));
|
||||
let end = DVec2::new(max_x + x_offset, project(&DVec2::new(max_x + x_offset, y_pos)));
|
||||
|
||||
overlay_context.line(
|
||||
document_to_viewport.transform_point2(start),
|
||||
document_to_viewport.transform_point2(end),
|
||||
Some(&("#".to_string() + &grid_color.rgba_hex())),
|
||||
Some((spacing_x / cos_a) * document_to_viewport.matrix2.x_axis.length()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn grid_overlay(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
|
||||
match document.snapping_state.grid.grid_type {
|
||||
GridType::Rectangle { spacing } => grid_overlay_rectangular(document, overlay_context, spacing),
|
||||
GridType::Isometric { y_axis_spacing, angle_a, angle_b } => grid_overlay_isometric(document, overlay_context, y_axis_spacing, angle_a, angle_b),
|
||||
GridType::Rectangle { spacing } => {
|
||||
if document.snapping_state.grid.dot_display {
|
||||
grid_overlay_dot(document, overlay_context, spacing)
|
||||
} else {
|
||||
grid_overlay_rectangular(document, overlay_context, spacing)
|
||||
}
|
||||
}
|
||||
GridType::Isometric { y_axis_spacing, angle_a, angle_b } => {
|
||||
if document.snapping_state.grid.dot_display {
|
||||
grid_overlay_isometric_dot(document, overlay_context, y_axis_spacing, angle_a, angle_b)
|
||||
} else {
|
||||
grid_overlay_isometric(document, overlay_context, y_axis_spacing, angle_a, angle_b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,9 +224,65 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
|
|||
}
|
||||
})
|
||||
};
|
||||
let update_color = |grid, update: fn(&mut GridSnapping) -> Option<&mut Color>| {
|
||||
update_val::<ColorButton>(grid, move |grid, color| {
|
||||
if let Some(color) = color.value {
|
||||
if let Some(update_color) = update(grid) {
|
||||
*update_color = color;
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
let update_display = |grid, update: fn(&mut GridSnapping) -> Option<&mut bool>| {
|
||||
update_val::<CheckboxInput>(grid, move |grid, checkbox| {
|
||||
if let Some(update) = update(grid) {
|
||||
*update = checkbox.checked;
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
widgets.push(LayoutGroup::Row {
|
||||
widgets: vec![TextLabel::new("Grid").bold(true).widget_holder()],
|
||||
});
|
||||
|
||||
widgets.push(LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
TextLabel::new("Type").table_align(true).widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
RadioInput::new(vec![
|
||||
RadioEntryData::new("rectangular")
|
||||
.label("Rectangular")
|
||||
.on_update(update_val(grid, |grid, _| grid.grid_type = GridType::RECTANGLE)),
|
||||
RadioEntryData::new("isometric")
|
||||
.label("Isometric")
|
||||
.on_update(update_val(grid, |grid, _| grid.grid_type = GridType::ISOMETRIC)),
|
||||
])
|
||||
.min_width(200)
|
||||
.selected_index(Some(match grid.grid_type {
|
||||
GridType::Rectangle { .. } => 0,
|
||||
GridType::Isometric { .. } => 1,
|
||||
}))
|
||||
.widget_holder(),
|
||||
],
|
||||
});
|
||||
|
||||
let mut color_widgets = vec![TextLabel::new("Display").table_align(true).widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder()];
|
||||
color_widgets.extend([
|
||||
CheckboxInput::new(grid.dot_display)
|
||||
.icon("GridDotted")
|
||||
.tooltip("Display as dotted grid")
|
||||
.on_update(update_display(grid, |grid| Some(&mut grid.dot_display)))
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
]);
|
||||
color_widgets.push(
|
||||
ColorButton::new(Some(grid.grid_color))
|
||||
.tooltip("Grid display color")
|
||||
.on_update(update_color(grid, |grid| Some(&mut grid.grid_color)))
|
||||
.widget_holder(),
|
||||
);
|
||||
widgets.push(LayoutGroup::Row { widgets: color_widgets });
|
||||
|
||||
widgets.push(LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
TextLabel::new("Origin").table_align(true).widget_holder(),
|
||||
|
|
@ -127,23 +302,6 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
|
|||
.widget_holder(),
|
||||
],
|
||||
});
|
||||
widgets.push(LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
TextLabel::new("Type").table_align(true).widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
RadioInput::new(vec![
|
||||
RadioEntryData::new("rectangular")
|
||||
.label("Rectangular")
|
||||
.on_update(update_val(grid, |grid, _| grid.grid_type = GridType::RECTANGLE)),
|
||||
RadioEntryData::new("isometric")
|
||||
.label("Isometric")
|
||||
.on_update(update_val(grid, |grid, _| grid.grid_type = GridType::ISOMETRIC)),
|
||||
])
|
||||
.min_width(200)
|
||||
.selected_index(Some(if matches!(grid.grid_type, GridType::Rectangle { .. }) { 0 } else { 1 }))
|
||||
.widget_holder(),
|
||||
],
|
||||
});
|
||||
|
||||
match grid.grid_type {
|
||||
GridType::Rectangle { spacing } => widgets.push(LayoutGroup::Row {
|
||||
|
|
|
|||
|
|
@ -39,12 +39,12 @@ pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut Shape
|
|||
let not_under_anchor = |&position: &DVec2| transform.transform_point2(position).distance_squared(anchor_position) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
|
||||
if let Some(in_handle) = manipulator_group.in_handle.filter(not_under_anchor) {
|
||||
let handle_position = transform.transform_point2(in_handle);
|
||||
overlay_context.line(handle_position, anchor_position, None);
|
||||
overlay_context.line(handle_position, anchor_position, None, None);
|
||||
overlay_context.manipulator_handle(handle_position, is_selected(selected, ManipulatorPointId::new(manipulator_group.id, SelectedType::InHandle)));
|
||||
}
|
||||
if let Some(out_handle) = manipulator_group.out_handle.filter(not_under_anchor) {
|
||||
let handle_position = transform.transform_point2(out_handle);
|
||||
overlay_context.line(handle_position, anchor_position, None);
|
||||
overlay_context.line(handle_position, anchor_position, None, None);
|
||||
overlay_context.manipulator_handle(handle_position, is_selected(selected, ManipulatorPointId::new(manipulator_group.id, SelectedType::OutHandle)));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ use crate::messages::prelude::Message;
|
|||
|
||||
use bezier_rs::Subpath;
|
||||
use graphene_core::renderer::Quad;
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
use core::f64::consts::PI;
|
||||
use core::f64::consts::TAU;
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
pub type OverlayProvider = fn(OverlayContext) -> Message;
|
||||
|
|
@ -38,10 +39,24 @@ impl OverlayContext {
|
|||
self.render_context.stroke();
|
||||
}
|
||||
|
||||
pub fn line(&mut self, start: DVec2, end: DVec2, color: Option<&str>) {
|
||||
pub fn line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, dash_width: Option<f64>) {
|
||||
let start = start.round() - DVec2::splat(0.5);
|
||||
let end = end.round() - DVec2::splat(0.5);
|
||||
|
||||
if let Some(dash_width) = dash_width {
|
||||
let array = js_sys::Array::new();
|
||||
array.push(&JsValue::from(1));
|
||||
array.push(&JsValue::from(dash_width - 1.));
|
||||
self.render_context
|
||||
.set_line_dash(&JsValue::from(array))
|
||||
.map_err(|error| log::debug!("Error drawing dashed line: {:?}", error))
|
||||
.ok();
|
||||
} else {
|
||||
let array = js_sys::Array::new();
|
||||
self.render_context
|
||||
.set_line_dash(&JsValue::from(array))
|
||||
.map_err(|error| log::debug!("Error drawing dashed line: {:?}", error))
|
||||
.ok();
|
||||
}
|
||||
self.render_context.begin_path();
|
||||
self.render_context.move_to(start.x, start.y);
|
||||
self.render_context.line_to(end.x, end.y);
|
||||
|
|
@ -53,7 +68,7 @@ impl OverlayContext {
|
|||
let position = position.round() - DVec2::splat(0.5);
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.render_context.arc(position.x, position.y, MANIPULATOR_GROUP_MARKER_SIZE / 2., 0., PI * 2.).expect("draw circle");
|
||||
self.render_context.arc(position.x, position.y, MANIPULATOR_GROUP_MARKER_SIZE / 2., 0., TAU).expect("draw circle");
|
||||
|
||||
let fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
|
||||
self.render_context.set_fill_style(&wasm_bindgen::JsValue::from_str(fill));
|
||||
|
|
@ -84,13 +99,37 @@ impl OverlayContext {
|
|||
self.render_context.stroke();
|
||||
}
|
||||
|
||||
pub fn pixel(&mut self, position: DVec2, color: Option<&str>) {
|
||||
let size = 1.;
|
||||
let color_fill = color.unwrap_or(COLOR_OVERLAY_WHITE);
|
||||
|
||||
let position = position.round() - DVec2::splat(0.5);
|
||||
let corner = position - DVec2::splat(size) / 2.;
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.render_context.rect(corner.x, corner.y, size, size);
|
||||
self.render_context.set_fill_style(&wasm_bindgen::JsValue::from_str(color_fill));
|
||||
self.render_context.fill();
|
||||
}
|
||||
|
||||
pub fn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||
let color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);
|
||||
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||
let position = position.round();
|
||||
self.render_context.begin_path();
|
||||
self.render_context.arc(position.x, position.y, radius, 0., TAU).expect("draw circle");
|
||||
self.render_context.set_fill_style(&wasm_bindgen::JsValue::from_str(color_fill));
|
||||
self.render_context.set_stroke_style(&wasm_bindgen::JsValue::from_str(color_stroke));
|
||||
self.render_context.fill();
|
||||
self.render_context.stroke();
|
||||
}
|
||||
pub fn pivot(&mut self, position: DVec2) {
|
||||
let (x, y) = (position.round() - DVec2::splat(0.5)).into();
|
||||
|
||||
// Circle
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.render_context.arc(x, y, PIVOT_DIAMETER / 2., 0., PI * 2.).expect("draw circle");
|
||||
self.render_context.arc(x, y, PIVOT_DIAMETER / 2., 0., TAU).expect("draw circle");
|
||||
self.render_context.set_fill_style(&wasm_bindgen::JsValue::from_str(COLOR_OVERLAY_YELLOW));
|
||||
self.render_context.fill();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
use glam::DVec2;
|
||||
use crate::consts::COLOR_OVERLAY_GRAY;
|
||||
|
||||
use graphene_core::raster::Color;
|
||||
|
||||
use glam::DVec2;
|
||||
use std::fmt;
|
||||
|
||||
#[repr(transparent)]
|
||||
|
|
@ -86,6 +89,11 @@ impl Default for SnappingState {
|
|||
grid: GridSnapping {
|
||||
origin: DVec2::ZERO,
|
||||
grid_type: GridType::RECTANGLE,
|
||||
grid_color: COLOR_OVERLAY_GRAY
|
||||
.strip_prefix("#")
|
||||
.and_then(|value| Color::from_rgb_str(value))
|
||||
.expect("Should create Color from prefixed hex string"),
|
||||
dot_display: false,
|
||||
},
|
||||
tolerance: 8.,
|
||||
artboards: true,
|
||||
|
|
@ -192,6 +200,8 @@ impl GridType {
|
|||
pub struct GridSnapping {
|
||||
pub origin: DVec2,
|
||||
pub grid_type: GridType,
|
||||
pub grid_color: Color,
|
||||
pub dot_display: bool,
|
||||
}
|
||||
impl GridSnapping {
|
||||
// Double grid size until it takes up at least 10px.
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ impl Fsm for GradientToolFsmState {
|
|||
let Gradient { start, end, positions, .. } = gradient;
|
||||
let (start, end) = (transform.transform_point2(start), transform.transform_point2(end));
|
||||
|
||||
overlay_context.line(start, end, None);
|
||||
overlay_context.line(start, end, None, None);
|
||||
overlay_context.manipulator_handle(start, dragging == Some(GradientDragTarget::Start));
|
||||
overlay_context.manipulator_handle(end, dragging == Some(GradientDragTarget::End));
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
|
||||
<rect width="2" height="2" x="1" y="1" />
|
||||
<rect width="2" height="2" x="5" y="1" />
|
||||
<rect width="2" height="2" x="9" y="1" />
|
||||
<rect width="2" height="2" x="1" y="5" />
|
||||
<rect width="2" height="2" x="5" y="5" />
|
||||
<rect width="2" height="2" x="9" y="5" />
|
||||
<rect width="2" height="2" x="1" y="9" />
|
||||
<rect width="2" height="2" x="5" y="9" />
|
||||
<rect width="2" height="2" x="9" y="9" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 455 B |
|
|
@ -18,6 +18,7 @@ import Empty12px from "@graphite-frontend/assets/icon-12px-solid/empty-12px.svg"
|
|||
import Failure from "@graphite-frontend/assets/icon-12px-solid/failure.svg";
|
||||
import FullscreenEnter from "@graphite-frontend/assets/icon-12px-solid/fullscreen-enter.svg";
|
||||
import FullscreenExit from "@graphite-frontend/assets/icon-12px-solid/fullscreen-exit.svg";
|
||||
import GridDotted from "@graphite-frontend/assets/icon-12px-solid/grid-dotted.svg";
|
||||
import Grid from "@graphite-frontend/assets/icon-12px-solid/grid.svg";
|
||||
import Info from "@graphite-frontend/assets/icon-12px-solid/info.svg";
|
||||
import KeyboardArrowDown from "@graphite-frontend/assets/icon-12px-solid/keyboard-arrow-down.svg";
|
||||
|
|
@ -58,6 +59,7 @@ const SOLID_12PX = {
|
|||
FullscreenEnter: { svg: FullscreenEnter, size: 12 },
|
||||
FullscreenExit: { svg: FullscreenExit, size: 12 },
|
||||
Grid: { svg: Grid, size: 12 },
|
||||
GridDotted: { svg: GridDotted, size: 12 },
|
||||
Info: { svg: Info, size: 12 },
|
||||
KeyboardArrowDown: { svg: KeyboardArrowDown, size: 12 },
|
||||
KeyboardArrowLeft: { svg: KeyboardArrowLeft, size: 12 },
|
||||
|
|
|
|||
Loading…
Reference in New Issue