Colour vision modes: Protan, Deutan and Tritan in Preferences › Graphics (#778) (#1007)

Colour vision modes (Protan, Deutan, Tritan) in Preferences > Graphics, with palettes, shader and icon colours adapted per mode, translations and tests.
This commit is contained in:
CamWight authored and GitHub committed 2026-10-14 17:35:56 -04:00
1 parent 289b521a65
commit f6a47a49d7
30 files changed
+2077 -166

No files matched your search

+32 -7
View File
@@ -13,7 +13,9 @@ use egui_wgpu::wgpu::util::DeviceExt;
pub const TRI_SIZE: usize = 28;
/// Line instance: a, b (3 × f32 each), colour (4 × u8), width in pixels (f32).
pub const LINE_SIZE: usize = 32;
const UNIFORM_SIZE: u64 = 208;
/// The shader's `U`: the view-projection matrix, 9 `vec4`s, the 8 analysis colours and their
/// flags (`uniform_bytes`).
const UNIFORM_SIZE: u64 = 352;
/// A body drawn from its display plan, evaluated on the GPU: the plan, and a colour per face
/// (the plan's face numbering; faces past the end take the last colour).
@@ -199,6 +201,8 @@ pub struct ViewportCallback {
/// Surface analysis: (mode: 0 none, 1 zebra, 2 draft, 3 curvature, 4 environment,
/// 5 accessibility; parameter; scale; size) and the pull / access direction.
pub analysis: [f32; 8],
/// The analysis colours and the stripe flag ([`analysis_colors`]).
pub analysis_colors: [f32; 36],
/// Accessibility: the depth map seen from the access direction.
pub access: Option<AccessMap>,
/// Draw silhouette outlines around the model (visual styles with edges).
@@ -1010,11 +1014,7 @@ impl egui_wgpu::CallbackTrait for ViewportCallback {
}
}
/// Render `scene` offscreen on a device of its own, through the viewport's real resources and
/// frame callback (`prepare`, then `paint`), and read the pixels back: straight RGBA, `size` ×
/// `size`. `None` without a GPU adapter. With `tess` (the tessellation shader), planned bodies
/// are evaluated on the GPU where the device builds that pipeline, as [`install`] sets it up.
/// `highlight` is drawn as the viewport's highlight scene (hover and selection).
/// A device of its own for an offscreen render or check (`None` without a GPU adapter).
#[cfg(all(test, not(target_arch = "wasm32")))]
fn offscreen_device() -> Option<(wgpu::Adapter, wgpu::Device, wgpu::Queue)> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
@@ -1035,7 +1035,15 @@ pub(crate) fn offscreen_tessellates(shader: &str) -> Option<bool> {
Some(tessellation(&adapter, &device, &queue, shader).is_some())
}
/// Render `scene` offscreen on a device of its own, through the viewport's real resources and
/// frame callback (`prepare`, then `paint`), and read the pixels back: straight RGBA, `size` ×
/// `size`. `None` without a GPU adapter. With `tess` (the tessellation shader), planned bodies
/// are evaluated on the GPU where the device builds that pipeline, as [`install`] sets it up.
/// `highlight` is drawn as the viewport's highlight scene (hover and selection). `analysis` is the
/// surface analysis uniform (`viewport::analysis_uniform`; zeros for none) and `colors` its
/// colours (`analysis_colors`).
#[cfg(all(test, not(target_arch = "wasm32")))]
#[allow(clippy::too_many_arguments)]
pub(crate) fn render_offscreen(
scene: GpuScene,
highlight: Option<GpuScene>,
@@ -1044,6 +1052,8 @@ pub(crate) fn render_offscreen(
eye: [f32; 4],
size: u32,
tess: Option<&str>,
analysis: [f32; 8],
colors: [f32; 36],
) -> Option<Vec<u8>> {
let (adapter, device, queue) = offscreen_device()?;
let (format, depth) = (wgpu::TextureFormat::Rgba8Unorm, wgpu::TextureFormat::Depth24PlusStencil8);
@@ -1067,7 +1077,8 @@ pub(crate) fn render_offscreen(
clip: [0.0; 4],
cap: [0.0; 4],
images: Vec::new(),
analysis: [0.0; 8],
analysis,
analysis_colors: colors,
access: None,
outline: false,
eye,
@@ -1152,9 +1163,23 @@ pub fn uniform_bytes(cb: &ViewportCallback, w: f32, h: f32, linear: bool) -> Vec
None => v.extend([0.0; 8]),
}
v.extend(cb.eye);
v.extend(cb.analysis_colors);
v.iter().flat_map(|x| x.to_le_bytes()).collect()
}
/// The shader's analysis colours (`ana_c`: draft positive, between, negative; reachable,
/// blocked; curvature flat, middle, tight; alpha 1) and flags (`ana_f.x`: 1 to stripe the bad
/// faces).
pub fn analysis_colors(c: &crate::theme::AnalysisColors) -> [f32; 36] {
let mut v = [0.0; 36];
let colors = c.draft.iter().chain(&c.access).chain(&c.curvature).flat_map(|rgb| [rgb[0], rgb[1], rgb[2], 1.0]);
for (slot, x) in v.iter_mut().zip(colors) {
*slot = x;
}
v[32] = if c.hatch { 1.0 } else { 0.0 };
v
}
#[cfg(test)]
mod tests {
use egui_wgpu::wgpu::naga;