Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions editor/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ pub const COLOR_OVERLAY_YELLOW_DULL: &str = "#d7ba8b";
pub const COLOR_OVERLAY_GREEN: &str = "#63ce63";
pub const COLOR_OVERLAY_RED: &str = "#ef5454";
pub const COLOR_OVERLAY_GRAY: &str = "#cccccc";
pub const COLOR_OVERLAY_GRAY_DARK: &str = "#555555";
pub const COLOR_OVERLAY_GRAY_25: &str = "#cccccc40";
pub const COLOR_OVERLAY_WHITE: &str = "#ffffff";
pub const COLOR_OVERLAY_BLACK_75: &str = "#000000bf";
Expand Down
229 changes: 178 additions & 51 deletions editor/src/messages/portfolio/document/overlays/grid_overlays.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ 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 glam::{DVec2, UVec2};
use graphene_std::raster::color::Color;
use graphene_std::renderer::Quad;
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 Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {
let grid_color_minor = "#".to_string() + &document.snapping_state.grid.grid_color_minor.to_rgba_hex_srgb();
let Some(scaled_spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.snapping_state.grid.rectangular_major_interval, &document.document_ptz) else {
return;
};
let scale_is_adjusted = scaled_spacing != spacing;
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);

let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]);
Expand All @@ -23,8 +25,17 @@ fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context:
let max = bounds.0.iter().map(|&corner| corner[secondary]).max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();
let primary_start = bounds.0.iter().map(|&corner| corner[primary]).min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();
let primary_end = bounds.0.iter().map(|&corner| corner[primary]).max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();
let spacing = spacing[secondary];
let spacing = scaled_spacing[secondary];
let first_index = ((min - origin[secondary]) / spacing).ceil() as i32;
for line_index in 0..=((max - min) / spacing).ceil() as i32 {
let is_major = is_major_line(
line_index + first_index,
if primary == 1 {
document.snapping_state.grid.rectangular_major_interval.x
} else {
document.snapping_state.grid.rectangular_major_interval.y
},
) || scale_is_adjusted;
let secondary_pos = (((min - origin[secondary]) / spacing).ceil() + line_index as f64) * spacing + origin[secondary];
let start = if primary == 0 {
DVec2::new(primary_start, secondary_pos)
Expand All @@ -36,7 +47,12 @@ 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),
if is_major { Some(&grid_color) } else { Some(&grid_color_minor) },
if is_major && document.snapping_state.grid.major_is_thick { Some(3.) } else { Some(1.) },
);
}
}
}
Expand All @@ -49,7 +65,8 @@ fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context:
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 Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {
let grid_color_minor = "#".to_string() + &document.snapping_state.grid.grid_color_minor.to_rgba_hex_srgb();
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.snapping_state.grid.rectangular_major_interval, &document.document_ptz) else {
return;
};
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
Expand Down Expand Up @@ -174,9 +191,13 @@ fn grid_overlay_isometric_dot(document: &DocumentMessageHandler, overlay_context
}
}

fn is_major_line(line_index: i32, major_interval: u32) -> bool {
line_index % major_interval as i32 == 0
}

pub fn grid_overlay(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
match document.snapping_state.grid.grid_type {
GridType::Rectangular { spacing } => {
GridType::Rectangular { spacing, .. } => {
if document.snapping_state.grid.dot_display {
grid_overlay_rectangular_dot(document, overlay_context, spacing)
} else {
Expand Down Expand Up @@ -205,10 +226,8 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
}
let update_origin = |grid, update: fn(&mut GridSnapping) -> Option<&mut f64>| {
update_val::<NumberInput, _>(grid, move |grid, val| {
if let Some(val) = val.value {
if let Some(update) = update(grid) {
*update = val;
}
if let (Some(val), Some(update)) = (val.value, update(grid)) {
*update = val;
}
})
};
Expand All @@ -219,7 +238,7 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
}
})
};
let update_display = |grid, update: fn(&mut GridSnapping) -> Option<&mut bool>| {
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;
Expand All @@ -230,7 +249,54 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
widgets.push(LayoutGroup::Row {
widgets: vec![TextLabel::new("Grid").bold(true).widget_holder()],
});
let mut color_widgets = vec![TextLabel::new("Color").table_align(true).widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder()];
color_widgets.push(
ColorInput::new(FillChoice::Solid(grid.grid_color.to_gamma_srgb()))
.tooltip("Grid display color")
.allow_none(false)
.on_update(update_color(grid, |grid| Some(&mut grid.grid_color)))
.widget_holder(),
);
if grid.has_minor_lines() {
color_widgets.push(Separator::new(SeparatorType::Related).widget_holder());
color_widgets.push(
ColorInput::new(FillChoice::Solid(grid.grid_color_minor.to_gamma_srgb()))
.tooltip("Minor grid line display color")
.allow_none(false)
.on_update(update_color(grid, |grid| Some(&mut grid.grid_color_minor)))
.widget_holder(),
);
}
widgets.push(LayoutGroup::Row { widgets: color_widgets });

widgets.push(LayoutGroup::Row {
widgets: vec![
TextLabel::new("Display").table_align(true).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
RadioInput::new(vec![
RadioEntryData::new("small").icon("Dot").on_update(update_val(grid, |grid, _| {
grid.major_is_thick = false;
})),
RadioEntryData::new("large").icon("DotLarge").on_update(update_val(grid, |grid, _| {
grid.major_is_thick = true;
})),
])
.selected_index(Some(if grid.major_is_thick { 1 } else { 0 }))
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
RadioInput::new(vec![
RadioEntryData::new("lines").label("Lines").icon("Grid").on_update(update_val(grid, |grid, _| {
grid.dot_display = false;
})),
RadioEntryData::new("dots").label("Dots").icon("GridDotted").on_update(update_val(grid, |grid, _| {
grid.dot_display = true;
})),
])
// .min_width(200)
.selected_index(Some(if grid.dot_display { 1 } else { 0 }))
.widget_holder(),
],
});
widgets.push(LayoutGroup::Row {
widgets: vec![
TextLabel::new("Type").table_align(true).widget_holder(),
Expand All @@ -245,7 +311,7 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
grid.grid_type = GridType::Rectangular { spacing: grid.rectangular_spacing };
})),
RadioEntryData::new("isometric").label("Isometric").on_update(update_val(grid, |grid, _| {
if let GridType::Rectangular { spacing } = grid.grid_type {
if let GridType::Rectangular { spacing, .. } = grid.grid_type {
grid.rectangular_spacing = spacing;
}
grid.grid_type = GridType::Isometric {
Expand All @@ -264,24 +330,6 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
],
});

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(
ColorInput::new(FillChoice::Solid(grid.grid_color.to_gamma_srgb()))
.tooltip("Grid display color")
.allow_none(false)
.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(),
Expand All @@ -303,27 +351,58 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
});

match grid.grid_type {
GridType::Rectangular { spacing } => widgets.push(LayoutGroup::Row {
widgets: vec![
TextLabel::new("Spacing").table_align(true).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
NumberInput::new(Some(spacing.x))
.label("X")
.unit(" px")
.min(0.)
.min_width(98)
.on_update(update_origin(grid, |grid| grid.grid_type.rectangular_spacing().map(|spacing| &mut spacing.x)))
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(spacing.y))
.label("Y")
.unit(" px")
.min(0.)
.min_width(98)
.on_update(update_origin(grid, |grid| grid.grid_type.rectangular_spacing().map(|spacing| &mut spacing.y)))
.widget_holder(),
],
}),
GridType::Rectangular { spacing, .. } => {
widgets.push(LayoutGroup::Row {
widgets: vec![
TextLabel::new("Spacing").table_align(true).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
NumberInput::new(Some(spacing.x))
.label("X")
.unit(" px")
.min(0.)
.min_width(98)
.on_update(update_origin(grid, |grid| grid.grid_type.rectangular_spacing().map(|spacing| &mut spacing.x)))
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(spacing.y))
.label("Y")
.unit(" px")
.min(0.)
.min_width(98)
.on_update(update_origin(grid, |grid| grid.grid_type.rectangular_spacing().map(|spacing| &mut spacing.y)))
.widget_holder(),
],
});
widgets.push(LayoutGroup::Row {
widgets: vec![
TextLabel::new("Mark Every").table_align(true).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
NumberInput::new(Some(grid.rectangular_major_interval.x as f64))
.unit(" col")
.int()
.min(1.)
.min_width(98)
.on_update(update_val(grid, |grid, val: &NumberInput| {
if let Some(val) = val.value {
grid.rectangular_major_interval.x = val as u32;
}
}))
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(grid.rectangular_major_interval.y as f64))
.unit(" row")
.int()
.min(1.)
.min_width(98)
.on_update(update_val(grid, |grid, val: &NumberInput| {
if let Some(val) = val.value {
grid.rectangular_major_interval.y = val as u32;
}
}))
.widget_holder(),
],
});
}
GridType::Isometric { y_axis_spacing, angle_a, angle_b } => {
widgets.push(LayoutGroup::Row {
widgets: vec![
Expand All @@ -342,18 +421,66 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
TextLabel::new("Angles").table_align(true).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
NumberInput::new(Some(angle_a))
.label("A")
.unit("°")
.min_width(98)
.on_update(update_origin(grid, |grid| grid.grid_type.angle_a()))
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(angle_b))
.label("B")
.unit("°")
.min_width(98)
.on_update(update_origin(grid, |grid| grid.grid_type.angle_b()))
.widget_holder(),
],
});
widgets.push(LayoutGroup::Row {
widgets: vec![
TextLabel::new("Mark Every").table_align(true).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
NumberInput::new(Some(grid.isometric_major_interval.z as f64))
.label("A")
.int()
.min(1.)
.min_width(98)
.on_update(update_val(grid, |grid, val: &NumberInput| {
if let Some(val) = val.value {
grid.isometric_major_interval.z = val as u32;
}
}))
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(grid.isometric_major_interval.y as f64))
.label("B")
.int()
.min(1.)
.min_width(98)
.on_update(update_val(grid, |grid, val: &NumberInput| {
if let Some(val) = val.value {
grid.isometric_major_interval.y = val as u32;
}
}))
.widget_holder(),
],
});
widgets.push(LayoutGroup::Row {
widgets: vec![
TextLabel::new("").table_align(true).widget_holder(),
Separator::new(SeparatorType::Unrelated).widget_holder(),
NumberInput::new(Some(grid.isometric_major_interval.x as f64))
.label("X")
.int()
.min(1.)
.min_width(200)
.on_update(update_val(grid, |grid, val: &NumberInput| {
if let Some(val) = val.value {
grid.isometric_major_interval.x = val as u32;
}
}))
.widget_holder(),
],
});
}
}

Expand Down
Loading
Loading