Skip to content
Merged
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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

138 changes: 138 additions & 0 deletions crates/app/src/ui/canvas/chrome.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use egui::{Color32, Stroke, Visuals};

/// Colours used by editor-only canvas chrome. Figure content and exports never
/// consume this table.
#[derive(Clone, Copy, Debug)]
pub(crate) struct ChromeStyle {
pub selection_fill: Color32,
pub selection_stroke: Color32,
pub selection_active: Color32,
pub layout_grid: Color32,
pub margin_guide: Color32,
pub snap_guide: Color32,
pub tile_existing_fill: Color32,
pub tile_existing_stroke: Color32,
pub tile_target_fill: Color32,
pub tile_target_stroke: Color32,
pub pivot: Color32,
pub integral: Color32,
pub peak: Color32,
}

impl ChromeStyle {
pub fn from_visuals(visuals: &Visuals, accent: Option<[u8; 3]>) -> Self {
let source = accent
.map(|[r, g, b]| Color32::from_rgb(r, g, b))
.unwrap_or(visuals.selection.bg_fill);
let background = visuals.panel_fill;
let normal = contrast_adjusted(source, background, 3.0);
let weak = blend(normal, background, 0.42);
let outline = contrast_adjusted(normal, background, 4.5);
let alternate = contrast_adjusted(
if visuals.dark_mode {
Color32::from_rgb(0xff, 0x72, 0xb6)
} else {
Color32::from_rgb(0xa2, 0x00, 0x59)
},
background,
3.0,
);
Self {
selection_fill: with_alpha(normal, 32),
selection_stroke: outline,
selection_active: contrast_adjusted(
Color32::from_rgb(0x1f, 0x9d, 0x74),
background,
3.0,
),
layout_grid: weak,
margin_guide: contrast_adjusted(blend(normal, background, 0.25), background, 2.0),
snap_guide: alternate,
tile_existing_fill: with_alpha(normal, 15),
tile_existing_stroke: weak,
tile_target_fill: with_alpha(normal, 26),
tile_target_stroke: outline,
pivot: contrast_adjusted(Color32::from_rgb(0xe0, 0x6c, 0x22), background, 3.0),
integral: contrast_adjusted(Color32::from_rgb(0x2b, 0x6c, 0xb0), background, 3.0),
peak: contrast_adjusted(Color32::from_rgb(0x8a, 0x1c, 0x1c), background, 3.0),
}
}

pub fn tile_existing_stroke(self) -> Stroke {
Stroke::new(1.0_f32, self.tile_existing_stroke)
}

pub fn tile_target_stroke(self) -> Stroke {
Stroke::new(2.0_f32, self.tile_target_stroke)
}
}

fn with_alpha(color: Color32, alpha: u8) -> Color32 {
Color32::from_rgba_unmultiplied(color.r(), color.g(), color.b(), alpha)
}

fn blend(foreground: Color32, background: Color32, amount: f32) -> Color32 {
let mix = |a: u8, b: u8| (a as f32 * (1.0 - amount) + b as f32 * amount).round() as u8;
Color32::from_rgb(
mix(foreground.r(), background.r()),
mix(foreground.g(), background.g()),
mix(foreground.b(), background.b()),
)
}

fn contrast_adjusted(mut color: Color32, background: Color32, minimum: f32) -> Color32 {
let target = if relative_luminance(background) > 0.45 {
Color32::BLACK
} else {
Color32::WHITE
};
for _ in 0..12 {
if contrast_ratio(color, background) >= minimum {
break;
}
color = blend(color, target, 0.12);
}
color
}

fn contrast_ratio(a: Color32, b: Color32) -> f32 {
let (lighter, darker) = if relative_luminance(a) >= relative_luminance(b) {
(relative_luminance(a), relative_luminance(b))
} else {
(relative_luminance(b), relative_luminance(a))
};
(lighter + 0.05) / (darker + 0.05)
}

fn relative_luminance(color: Color32) -> f32 {
let channel = |value: u8| {
let value = value as f32 / 255.0;
if value <= 0.04045 {
value / 12.92
} else {
((value + 0.055) / 1.055).powf(2.4)
}
};
0.2126 * channel(color.r()) + 0.7152 * channel(color.g()) + 0.0722 * channel(color.b())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn translucent_roles_keep_unmultiplied_rgb() {
let style = ChromeStyle::from_visuals(&Visuals::light(), Some([120, 80, 40]));
let [r, g, b, alpha] = style.tile_existing_fill.to_srgba_unmultiplied();
assert_eq!(alpha, 15);
assert_eq!(style.tile_target_fill.to_srgba_unmultiplied()[3], 26);
assert_eq!(
style.tile_existing_fill,
Color32::from_rgba_unmultiplied(r, g, b, alpha)
);
assert_ne!(
style.tile_existing_fill,
Color32::from_rgba_premultiplied(r, g, b, alpha)
);
}
}
7 changes: 4 additions & 3 deletions crates/app/src/ui/canvas/integrals2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ pub(crate) fn paint_integrals_2d(
dataset: usize,
plot: PlotRect,
painter: &egui::Painter,
chrome: ChromeStyle,
) {
let Some(n) = app
.doc
Expand Down Expand Up @@ -487,7 +488,7 @@ pub(crate) fn paint_integrals_2d(
if r.width() < 1.0 || r.height() < 1.0 {
continue;
}
let color = INTEGRAL_COLOR;
let color = chrome.integral;
let [red, green, blue, _] = color.to_array();
painter.rect_filled(
r,
Expand Down Expand Up @@ -548,11 +549,11 @@ pub(crate) fn paint_integrals_2d(
baseline: BaselineMode::None,
};
let r = integral_screen_rect(&preview, plot, x, y).intersect(plot_rect(plot));
painter.rect_filled(r, 0.0, SELECT_FILL);
painter.rect_filled(r, 0.0, chrome.selection_fill);
painter.rect_stroke(
r,
0.0,
Stroke::new(1.0_f32, SELECT_STROKE),
Stroke::new(1.0_f32, chrome.selection_stroke),
StrokeKind::Inside,
);
}
Expand Down
32 changes: 32 additions & 0 deletions crates/app/src/ui/canvas/interactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,38 @@ pub(crate) fn arrange_context_menu(app: &mut PlotxApp, ci: usize, ui: &mut Ui) {
}
}
});
if ui.button("Simplify inner axes").clicked() {
app.simplify_inner_axes();
ui.close();
}
ui.menu_button("Spacing basis", |ui| {
for (label, mode) in [
("Frame", layout::SpacingMode::Frame),
("Visual", layout::SpacingMode::Visual),
] {
let checked = app.doc.canvases[ci].layout.spacing_mode == mode;
if ui.selectable_label(checked, label).clicked() {
app.set_spacing_mode(mode);
ui.close();
}
}
});
ui.menu_button("Minimum spacing", |ui| {
for preset in layout::GutterPreset::ALL {
let checked =
(app.doc.canvases[ci].layout.gutter_mm - preset.millimetres()).abs() < 0.001;
if ui
.selectable_label(
checked,
format!("{} ({} mm)", preset.label(), preset.millimetres()),
)
.clicked()
{
app.set_gutter_preset(preset);
ui.close();
}
}
});
if !app.session.ui.selection.objects().is_empty() {
ui.menu_button("Order", |ui| {
for (label, op) in [
Expand Down
43 changes: 19 additions & 24 deletions crates/app/src/ui/canvas/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,15 @@ const PIVOT_GRAB_PX: f32 = 6.0;
const SELECT_MIN_PX: f32 = 6.0;
const DRAG_START_PX: f32 = 5.0;
const WHEEL_ZOOM_SPEED: f32 = 0.0015;
const PIVOT_COLOR: Color32 = Color32::from_rgb(0xE0, 0x6C, 0x22);
const SELECT_FILL: Color32 = Color32::from_rgba_premultiplied(0x1f, 0x6f, 0xeb, 32);
const SELECT_STROKE: Color32 = Color32::from_rgb(0x1f, 0x6f, 0xeb);
const SELECT_ACCENT: Color32 = Color32::from_rgb(0x1f, 0x9d, 0x74);
const HANDLE_SIZE_PX: f32 = 8.0;
const MIN_OBJECT_SIZE_PT: f32 = 24.0;
const PANEL_LABEL_HIT_PAD_PX: f32 = 4.0;
const SNAP_PX: f32 = 6.0;
const GRID_COLOR: Color32 = Color32::from_rgb(0x5a, 0xa9, 0xc4);
const GUIDE_COLOR: Color32 = Color32::from_rgb(0xff, 0x2d, 0x92);
const INTEGRAL_COLOR: Color32 = Color32::from_rgb(0x2b, 0x6c, 0xb0);
const PEAK_COLOR: Color32 = Color32::from_rgb(0x8a, 0x1c, 0x1c);

mod authoring;
mod board;
mod board_notes;
mod chrome;
mod geometry;
mod integrals;
mod integrals2d;
Expand All @@ -54,6 +47,7 @@ mod tiling;
pub(crate) use authoring::*;
pub(crate) use board::*;
pub(crate) use board_notes::*;
pub(crate) use chrome::*;
pub(crate) use geometry::*;
pub(crate) use integrals::*;
pub(crate) use integrals2d::*;
Expand Down Expand Up @@ -84,6 +78,7 @@ pub fn render_central(app: &mut PlotxApp, ui: &mut Ui) {
let avail = ui.available_rect_before_wrap();
let (resp, painter) = ui.allocate_painter(avail.size(), Sense::click_and_drag());
let rect = resp.rect;
let chrome = ChromeStyle::from_visuals(ui.visuals(), app.session.canvas_accent);
ensure_board_view(app, rect);
drive_board_fit(app, ui, rect);

Expand Down Expand Up @@ -178,13 +173,13 @@ pub fn render_central(app: &mut PlotxApp, ui: &mut Ui) {
paint_frame_captions(app, rect, ui, &painter);
render_inline_panel_note_editor(app, rect, ui);
paint_sheet_frames(app, rect, ui, &painter);
paint_layout_overlay(app, ci, rect, &painter);
paint_axis_zoom(app, ci, rect, &painter);
paint_author_drag(app, ci, rect, &painter);
paint_marquee(app, ci, rect, &painter);
paint_panel_label_selection(app, ci, rect, &painter);
paint_object_selection(app, ci, rect, page, &painter);
paint_tile_preview(app, rect, &painter);
paint_layout_overlay(app, ci, rect, &painter, chrome);
paint_axis_zoom(app, ci, rect, &painter, chrome);
paint_author_drag(app, ci, rect, &painter, chrome);
paint_marquee(app, ci, rect, &painter, chrome);
paint_panel_label_selection(app, ci, rect, &painter, chrome);
paint_object_selection(app, ci, rect, page, &painter, chrome);
paint_tile_preview(app, rect, &painter, chrome);
super::canvas_size::page_size_chrome(app, ci, page, rect, ui);
if pointer_owned {
canvas_cursor(app, ci, rect, ui);
Expand Down Expand Up @@ -245,7 +240,7 @@ pub fn render_central(app: &mut PlotxApp, ui: &mut Ui) {
.clamp(plot.left, plot.right());
painter.line_segment(
[Pos2::new(px, plot.top), Pos2::new(px, plot.bottom())],
Stroke::new(1.5_f32, PIVOT_COLOR),
Stroke::new(1.5_f32, chrome.pivot),
);
}
PhaseOrient::Horizontal => {
Expand All @@ -254,7 +249,7 @@ pub fn render_central(app: &mut PlotxApp, ui: &mut Ui) {
.clamp(plot.top, plot.bottom());
painter.line_segment(
[Pos2::new(plot.left, py), Pos2::new(plot.right(), py)],
Stroke::new(1.5_f32, PIVOT_COLOR),
Stroke::new(1.5_f32, chrome.pivot),
);
}
}
Expand Down Expand Up @@ -290,14 +285,14 @@ pub fn render_central(app: &mut PlotxApp, ui: &mut Ui) {
}
}

paint_zoom_drag(app, ci, object_id, plot, &painter);
paint_regions(app, ci, object_id, di, plot, &painter);
paint_integrals(app, ci, object_id, di, plot, &painter);
paint_integrals_2d(app, ci, object_id, di, plot, &painter);
paint_peaks(app, ci, object_id, di, plot, &painter);
paint_zoom_drag(app, ci, object_id, plot, &painter, chrome);
paint_regions(app, ci, object_id, di, plot, &painter, chrome);
paint_integrals(app, ci, object_id, di, plot, &painter, chrome);
paint_integrals_2d(app, ci, object_id, di, plot, &painter, chrome);
paint_peaks(app, ci, object_id, di, plot, &painter, chrome);
paint_slice(app, ci, object_id, di, plot, &painter);
paint_analysis_selection(app, ci, object_id, plot, &painter);
paint_selection_drag(app, ci, object_id, plot, &painter);
paint_analysis_selection(app, ci, object_id, plot, &painter, chrome);
paint_selection_drag(app, ci, object_id, plot, &painter, chrome);
}

fn welcome_page(app: &mut PlotxApp, ui: &mut Ui) {
Expand Down
Loading
Loading