refactor: 迁移 shader.wgsl 至 static/wgsl/,新增 model 模块
This commit is contained in:
11
src/app.rs
11
src/app.rs
@@ -7,7 +7,8 @@ use crate::theme::{ONE_DARK_PRO, apply_fonts, apply_theme};
|
||||
use crate::{
|
||||
matrix::{MATRIX_COLS, MATRIX_ROWS},
|
||||
render::{
|
||||
BackgroundRenderResources, PRESSURE_CELL_COUNT, PressureFrame, WgpuBackgroundCallback,
|
||||
BackgroundRenderResources, MarkerMode, PRESSURE_CELL_COUNT, PressureFrame,
|
||||
WgpuBackgroundCallback,
|
||||
},
|
||||
ui::{
|
||||
ConfigPanelState, ConnectPanelState, FloatingPanelState, MatrixConfigState,
|
||||
@@ -90,6 +91,7 @@ impl EskinDesktopApp {
|
||||
width,
|
||||
height,
|
||||
pressure: self.pressure_matrix,
|
||||
marker_mode: MarkerMode::Number,
|
||||
},
|
||||
));
|
||||
}
|
||||
@@ -243,7 +245,12 @@ impl EskinDesktopApp {
|
||||
draw_scene_panel(ctx, &mut self.scene_panel);
|
||||
draw_config_panel(ctx, &mut self.config_panel, &mut self.config_state);
|
||||
draw_stats_panel(ctx, &mut self.stats_panel);
|
||||
draw_export_panel(ctx, &mut self.export_panel, &self.recorder, &mut self.export_path);
|
||||
draw_export_panel(
|
||||
ctx,
|
||||
&mut self.export_panel,
|
||||
&self.recorder,
|
||||
&mut self.export_path,
|
||||
);
|
||||
draw_matrix_config_panel(ctx, &mut self.matrix_config_panel, &mut self.matrix_config);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB |
@@ -1,12 +1,13 @@
|
||||
mod app;
|
||||
mod connection;
|
||||
mod matrix;
|
||||
mod recording;
|
||||
mod render;
|
||||
mod serial_core;
|
||||
mod theme;
|
||||
mod ui;
|
||||
mod recording;
|
||||
mod utils;
|
||||
mod model;
|
||||
use app::EskinDesktopApp;
|
||||
use eframe::egui;
|
||||
|
||||
|
||||
15
src/model.rs
Normal file
15
src/model.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use eframe::{
|
||||
egui,
|
||||
egui_wgpu::{self, wgpu},
|
||||
wgpu::util::DeviceExt,
|
||||
};
|
||||
|
||||
pub trait Vertex {
|
||||
fn desc<'a>() -> wgpu::VertexBufferLayout<'a>;
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct ModelVertex {
|
||||
pub position: [f32; 3],
|
||||
}
|
||||
@@ -292,9 +292,11 @@ impl Recorder {
|
||||
|
||||
let mut pressures = Vec::with_capacity(n_channels);
|
||||
for f in &fields[..n_channels] {
|
||||
pressures.push(f.parse::<u32>().with_context(|| {
|
||||
format!("line {}: bad channel value '{}'", lineno + 2, f)
|
||||
})?);
|
||||
pressures.push(
|
||||
f.parse::<u32>().with_context(|| {
|
||||
format!("line {}: bad channel value '{}'", lineno + 2, f)
|
||||
})?,
|
||||
);
|
||||
}
|
||||
let raw_ts = fields[n_channels]
|
||||
.parse::<u64>()
|
||||
@@ -320,7 +322,8 @@ impl Recorder {
|
||||
// Pretend the recording happened `last.timestamp_ms` ago
|
||||
// so that elapsed_ms() would return that value.
|
||||
// We store a "fake" start by noting the offset.
|
||||
r.start = Some(Instant::now() - std::time::Duration::from_millis(last.timestamp_ms));
|
||||
r.start =
|
||||
Some(Instant::now() - std::time::Duration::from_millis(last.timestamp_ms));
|
||||
r.paused_duration_ms = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,13 @@ pub struct WgpuBackgroundCallback {
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
pub pressure: PressureFrame,
|
||||
pub marker_mode: MarkerMode,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum MarkerMode {
|
||||
Number,
|
||||
Dot,
|
||||
}
|
||||
|
||||
impl egui_wgpu::CallbackTrait for WgpuBackgroundCallback {
|
||||
@@ -26,7 +33,13 @@ impl egui_wgpu::CallbackTrait for WgpuBackgroundCallback {
|
||||
resources: &mut egui_wgpu::CallbackResources,
|
||||
) -> Vec<wgpu::CommandBuffer> {
|
||||
let resources: &mut BackgroundRenderResources = resources.get_mut().unwrap();
|
||||
resources.prepare(queue, self.width, self.height, &self.pressure);
|
||||
resources.prepare(
|
||||
queue,
|
||||
self.width,
|
||||
self.height,
|
||||
&self.pressure,
|
||||
self.marker_mode,
|
||||
);
|
||||
|
||||
Vec::new()
|
||||
}
|
||||
@@ -51,9 +64,11 @@ pub struct BackgroundRenderResources {
|
||||
uniform_bind_group: wgpu::BindGroup,
|
||||
background_pipeline: wgpu::RenderPipeline,
|
||||
glyph_pipeline: wgpu::RenderPipeline,
|
||||
dot_pipeline: wgpu::RenderPipeline,
|
||||
glyph_vertex_buffer: wgpu::Buffer,
|
||||
glyph_instance_buffer: wgpu::Buffer,
|
||||
glyph_instances: Vec<GlyphInstance>,
|
||||
marker_mode: MarkerMode,
|
||||
}
|
||||
|
||||
impl BackgroundRenderResources {
|
||||
@@ -96,10 +111,20 @@ impl BackgroundRenderResources {
|
||||
});
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("Pressure Matrix Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
|
||||
label: Some("Pressure Matrix Background Shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("../static/wgsl/shader.wgsl").into()),
|
||||
});
|
||||
|
||||
// let glyph_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
// label: Some("Pressure Matrix Glyph Shader"),
|
||||
// source: wgpu::ShaderSource::Wgsl(include_str!("../static/wgsl/shader.wgsl").into()),
|
||||
// });
|
||||
|
||||
// let dot_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
// label: Some("Pressure Matrix Dot Shader"),
|
||||
// source: wgpu::ShaderSource::Wgsl(include_str!("../static/wgsl/shader.wgsl").into()),
|
||||
// });
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("Pressure Matrix Pipeline Layout"),
|
||||
bind_group_layouts: &[Some(&uniform_bind_group_layout)],
|
||||
@@ -110,6 +135,8 @@ impl BackgroundRenderResources {
|
||||
create_background_pipeline(device, target_format, &shader, &pipeline_layout);
|
||||
let glyph_pipeline =
|
||||
create_glyph_pipeline(device, target_format, &shader, &pipeline_layout);
|
||||
let dot_pipeline =
|
||||
create_dot_pipeline(device, target_format, &shader, &pipeline_layout);
|
||||
|
||||
let glyph_vertices = [
|
||||
GlyphVertex {
|
||||
@@ -144,16 +171,26 @@ impl BackgroundRenderResources {
|
||||
uniform_bind_group,
|
||||
background_pipeline,
|
||||
glyph_pipeline,
|
||||
dot_pipeline,
|
||||
glyph_vertex_buffer,
|
||||
glyph_instance_buffer,
|
||||
glyph_instances,
|
||||
marker_mode: MarkerMode::Number,
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare(&mut self, queue: &wgpu::Queue, width: f32, height: f32, pressure: &PressureFrame) {
|
||||
fn prepare(
|
||||
&mut self,
|
||||
queue: &wgpu::Queue,
|
||||
width: f32,
|
||||
height: f32,
|
||||
pressure: &PressureFrame,
|
||||
marker_mode: MarkerMode,
|
||||
) {
|
||||
let aspect = width / height.max(1.0);
|
||||
self.uniform =
|
||||
MatrixUniform::new(width, height, build_view_projection(aspect, &self.layout));
|
||||
self.marker_mode = marker_mode;
|
||||
queue.write_buffer(
|
||||
&self.uniform_buffer,
|
||||
0,
|
||||
@@ -180,7 +217,13 @@ impl BackgroundRenderResources {
|
||||
render_pass.set_pipeline(&self.background_pipeline);
|
||||
render_pass.draw(0..3, 0..1);
|
||||
|
||||
render_pass.set_pipeline(&self.glyph_pipeline);
|
||||
// let marker_pipeline = match self.marker_mode {
|
||||
// MarkerMode::Number => &self.glyph_pipeline,
|
||||
// MarkerMode::Dot => &self.dot_pipeline,
|
||||
// };
|
||||
let marker_pipeline = &self.dot_pipeline;
|
||||
|
||||
render_pass.set_pipeline(marker_pipeline);
|
||||
render_pass.set_vertex_buffer(0, self.glyph_vertex_buffer.slice(..));
|
||||
render_pass.set_vertex_buffer(1, self.glyph_instance_buffer.slice(..));
|
||||
render_pass.draw(0..6, 0..self.glyph_instances.len() as u32);
|
||||
@@ -253,6 +296,39 @@ fn create_glyph_pipeline(
|
||||
})
|
||||
}
|
||||
|
||||
fn create_dot_pipeline(
|
||||
device: &wgpu::Device,
|
||||
target_format: &wgpu::TextureFormat,
|
||||
shader: &wgpu::ShaderModule,
|
||||
layout: &wgpu::PipelineLayout,
|
||||
) -> wgpu::RenderPipeline {
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("Pressure Matrix Dot Pipeline"),
|
||||
layout: Some(layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: shader,
|
||||
entry_point: Some("vs_dot"),
|
||||
compilation_options: Default::default(),
|
||||
buffers: &[GlyphVertex::desc(), GlyphInstance::desc()],
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: shader,
|
||||
entry_point: Some("fs_dot"),
|
||||
compilation_options: Default::default(),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format: *target_format,
|
||||
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
primitive: wgpu::PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn build_glyph_instances(
|
||||
rows: u32,
|
||||
cols: u32,
|
||||
|
||||
258
src/shader.wgsl
258
src/shader.wgsl
@@ -1,258 +0,0 @@
|
||||
struct MatrixUniform {
|
||||
view_proj: mat4x4f,
|
||||
viewport: vec4f,
|
||||
glyph: vec4f,
|
||||
color: vec4f,
|
||||
}
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<uniform> u: MatrixUniform;
|
||||
|
||||
struct BackgroundVertexOutput {
|
||||
@builtin(position) clip_position: vec4f,
|
||||
}
|
||||
|
||||
struct GlyphVertexInput {
|
||||
@location(0) local: vec2f,
|
||||
}
|
||||
|
||||
struct GlyphInstanceInput {
|
||||
@location(1) world_position: vec4f,
|
||||
@location(2) style: vec4f,
|
||||
}
|
||||
|
||||
struct GlyphVertexOutput {
|
||||
@builtin(position) clip_position: vec4f,
|
||||
@location(0) local: vec2f,
|
||||
@location(1) intensity: f32,
|
||||
@location(2) display_value: f32,
|
||||
}
|
||||
|
||||
fn saturate(value: f32) -> f32 {
|
||||
return clamp(value, 0.0, 1.0);
|
||||
}
|
||||
|
||||
fn capsule_distance(point: vec2f, start: vec2f, end: vec2f) -> f32 {
|
||||
let segment = end - start;
|
||||
let h = saturate(dot(point - start, segment) / dot(segment, segment));
|
||||
return length(point - start - segment * h);
|
||||
}
|
||||
|
||||
fn rotate2(point: vec2f, angle: f32) -> vec2f {
|
||||
let s = sin(angle);
|
||||
let c = cos(angle);
|
||||
return vec2f(point.x * c - point.y * s, point.x * s + point.y * c);
|
||||
}
|
||||
|
||||
fn rect_alpha(point: vec2f, center: vec2f, half_size: vec2f) -> f32 {
|
||||
let delta = abs(point - center) - half_size;
|
||||
let outside = length(max(delta, vec2f(0.0, 0.0)));
|
||||
let inside = min(max(delta.x, delta.y), 0.0);
|
||||
let dist = outside + inside;
|
||||
return 1.0 - smoothstep(0.015, 0.045, dist);
|
||||
}
|
||||
|
||||
fn range_stop_color(index: u32) -> vec3f {
|
||||
switch index {
|
||||
case 0u: {
|
||||
return vec3f(0.140, 0.690, 0.890);
|
||||
}
|
||||
case 1u: {
|
||||
return vec3f(0.250, 0.760, 0.380);
|
||||
}
|
||||
case 2u: {
|
||||
return vec3f(1.000, 0.670, 0.180);
|
||||
}
|
||||
default: {
|
||||
return vec3f(1.000, 0.255, 0.190);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_range_color(value: f32) -> vec3f {
|
||||
let t = saturate(value);
|
||||
|
||||
if (t <= 0.33) {
|
||||
let local = smoothstep(0.0, 0.33, t);
|
||||
return mix(range_stop_color(0u), range_stop_color(1u), local);
|
||||
}
|
||||
|
||||
if (t <= 0.66) {
|
||||
let local = smoothstep(0.33, 0.66, t);
|
||||
return mix(range_stop_color(1u), range_stop_color(2u), local);
|
||||
}
|
||||
|
||||
let local = smoothstep(0.66, 1.0, t);
|
||||
return mix(range_stop_color(2u), range_stop_color(3u), local);
|
||||
}
|
||||
|
||||
fn digit_segment_on(digit: u32, segment: u32) -> bool {
|
||||
switch digit {
|
||||
case 0u: { return segment != 6u; }
|
||||
case 1u: { return segment == 1u || segment == 2u; }
|
||||
case 2u: { return segment == 0u || segment == 1u || segment == 6u || segment == 4u || segment == 3u; }
|
||||
case 3u: { return segment == 0u || segment == 1u || segment == 6u || segment == 2u || segment == 3u; }
|
||||
case 4u: { return segment == 5u || segment == 6u || segment == 1u || segment == 2u; }
|
||||
case 5u: { return segment == 0u || segment == 5u || segment == 6u || segment == 2u || segment == 3u; }
|
||||
case 6u: { return segment == 0u || segment == 5u || segment == 4u || segment == 3u || segment == 2u || segment == 6u; }
|
||||
case 7u: { return segment == 0u || segment == 1u || segment == 2u; }
|
||||
case 8u: { return true; }
|
||||
default: { return segment == 0u || segment == 1u || segment == 2u || segment == 3u || segment == 5u || segment == 6u; }
|
||||
}
|
||||
}
|
||||
|
||||
fn seven_segment_digit_alpha(local: vec2f, digit: u32) -> f32 {
|
||||
var alpha = 0.0;
|
||||
if (digit_segment_on(digit, 0u)) {
|
||||
alpha = max(alpha, rect_alpha(local, vec2f(0.0, 0.70), vec2f(0.38, 0.078)));
|
||||
}
|
||||
if (digit_segment_on(digit, 1u)) {
|
||||
alpha = max(alpha, rect_alpha(local, vec2f(0.39, 0.36), vec2f(0.078, 0.335)));
|
||||
}
|
||||
if (digit_segment_on(digit, 2u)) {
|
||||
alpha = max(alpha, rect_alpha(local, vec2f(0.39, -0.36), vec2f(0.078, 0.335)));
|
||||
}
|
||||
if (digit_segment_on(digit, 3u)) {
|
||||
alpha = max(alpha, rect_alpha(local, vec2f(0.0, -0.70), vec2f(0.38, 0.078)));
|
||||
}
|
||||
if (digit_segment_on(digit, 4u)) {
|
||||
alpha = max(alpha, rect_alpha(local, vec2f(-0.39, -0.36), vec2f(0.078, 0.335)));
|
||||
}
|
||||
if (digit_segment_on(digit, 5u)) {
|
||||
alpha = max(alpha, rect_alpha(local, vec2f(-0.39, 0.36), vec2f(0.078, 0.335)));
|
||||
}
|
||||
if (digit_segment_on(digit, 6u)) {
|
||||
alpha = max(alpha, rect_alpha(local, vec2f(0.0, 0.0), vec2f(0.35, 0.075)));
|
||||
}
|
||||
return alpha;
|
||||
}
|
||||
|
||||
fn digit_count(value: u32) -> u32 {
|
||||
if (value >= 1000u) {
|
||||
return 4u;
|
||||
}
|
||||
if (value >= 100u) {
|
||||
return 3u;
|
||||
}
|
||||
if (value >= 10u) {
|
||||
return 2u;
|
||||
}
|
||||
return 1u;
|
||||
}
|
||||
|
||||
fn digit_at(value: u32, slot: u32, count: u32) -> u32 {
|
||||
if (count == 4u) {
|
||||
switch slot {
|
||||
case 0u: { return (value / 1000u) % 10u; }
|
||||
case 1u: { return (value / 100u) % 10u; }
|
||||
case 2u: { return (value / 10u) % 10u; }
|
||||
default: { return value % 10u; }
|
||||
}
|
||||
}
|
||||
if (count == 3u) {
|
||||
switch slot {
|
||||
case 0u: { return (value / 100u) % 10u; }
|
||||
case 1u: { return (value / 10u) % 10u; }
|
||||
default: { return value % 10u; }
|
||||
}
|
||||
}
|
||||
if (count == 2u) {
|
||||
return select(value % 10u, (value / 10u) % 10u, slot == 0u);
|
||||
}
|
||||
return value % 10u;
|
||||
}
|
||||
|
||||
fn number_alpha(local: vec2f, display_value: f32) -> f32 {
|
||||
let value = min(u32(max(display_value + 0.5, 0.0)), 9999u);
|
||||
let count = digit_count(value);
|
||||
let count_f = f32(count);
|
||||
let slot_width = 1.74 / count_f;
|
||||
let start_x = -slot_width * (count_f - 1.0) * 0.5;
|
||||
var alpha = 0.0;
|
||||
|
||||
for (var slot = 0u; slot < 4u; slot = slot + 1u) {
|
||||
if (slot < count) {
|
||||
let center_x = start_x + f32(slot) * slot_width;
|
||||
let digit_local = vec2f((local.x - center_x) / (slot_width * 0.78), local.y / 0.92);
|
||||
let digit = digit_at(value, slot, count);
|
||||
let in_slot = step(abs(local.x - center_x), slot_width * 0.48);
|
||||
alpha = max(alpha, seven_segment_digit_alpha(digit_local, digit) * in_slot);
|
||||
}
|
||||
}
|
||||
|
||||
return alpha;
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_background(@builtin(vertex_index) vertex_index: u32) -> BackgroundVertexOutput {
|
||||
let positions = array<vec2f, 3>(
|
||||
vec2f(-1.0, -3.0),
|
||||
vec2f(3.0, 1.0),
|
||||
vec2f(-1.0, 1.0),
|
||||
);
|
||||
|
||||
var out: BackgroundVertexOutput;
|
||||
out.clip_position = vec4f(positions[vertex_index], 0.0, 1.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_background(@builtin(position) frag_coord: vec4f) -> @location(0) vec4f {
|
||||
let pixel = frag_coord.xy;
|
||||
let viewport = u.viewport.xy;
|
||||
let uv = pixel / max(viewport, vec2f(1.0, 1.0));
|
||||
var color = mix(vec3f(0.112, 0.126, 0.160), vec3f(0.145, 0.160, 0.198), 1.0 - uv.y);
|
||||
let vignette = smoothstep(0.18, 0.92, length((uv - vec2f(0.52, 0.48)) * vec2f(viewport.x / viewport.y, 1.0)));
|
||||
color *= 1.0 - vignette * 0.30;
|
||||
color += vec3f(0.035, 0.070, 0.090) * (1.0 - smoothstep(0.0, 0.9, abs(uv.y - 0.52)));
|
||||
|
||||
let track_width = clamp(viewport.x * 0.42, 260.0, 560.0);
|
||||
let track_height = 12.0;
|
||||
let track_center = vec2f(viewport.x * 0.5, viewport.y - 38.0);
|
||||
let local = pixel - track_center;
|
||||
let half_size = vec2f(track_width * 0.5, track_height * 0.5);
|
||||
let inside_track = step(abs(local.x), half_size.x) * step(abs(local.y), half_size.y);
|
||||
let border = step(abs(local.x), half_size.x + 1.0) * step(abs(local.y), half_size.y + 1.0) - inside_track;
|
||||
let t = saturate((local.x + half_size.x) / track_width);
|
||||
|
||||
if (inside_track > 0.5) {
|
||||
let shade = mix(0.72, 1.0, 1.0 - saturate((local.y + half_size.y) / track_height));
|
||||
color = mix(color, sample_range_color(t) * shade, 0.96);
|
||||
}
|
||||
|
||||
color = max(color, vec3f(0.34, 0.41, 0.46) * border);
|
||||
|
||||
let tick_area = step(abs(local.x), half_size.x) * step(abs(local.y), half_size.y + 8.0);
|
||||
if (tick_area > 0.5) {
|
||||
let ticks = array<f32, 4>(0.0, 0.33, 0.66, 1.0);
|
||||
for (var index = 0u; index < 4u; index = index + 1u) {
|
||||
let tick_x = (ticks[index] - 0.5) * track_width;
|
||||
let tick = step(abs(local.x - tick_x), 1.0) * step(abs(local.y), half_size.y + 7.0);
|
||||
color = max(color, vec3f(0.78, 0.84, 0.90) * tick);
|
||||
}
|
||||
}
|
||||
|
||||
return vec4f(color, 1.0);
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vs_glyph(vertex: GlyphVertexInput, instance: GlyphInstanceInput) -> GlyphVertexOutput {
|
||||
let center = u.view_proj * vec4f(instance.world_position.xyz, 1.0);
|
||||
let shaped = pow(saturate(instance.style.x), 0.9);
|
||||
let pixel_size = u.glyph.x * mix(1.08, 2.20, shaped);
|
||||
let ndc_offset = vertex.local * vec2f(pixel_size / u.viewport.x, pixel_size / u.viewport.y) * 2.0;
|
||||
|
||||
var out: GlyphVertexOutput;
|
||||
out.clip_position = vec4f(center.xy + ndc_offset * center.w, center.z, center.w);
|
||||
out.local = vertex.local;
|
||||
out.intensity = instance.style.x;
|
||||
out.display_value = instance.style.y;
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_glyph(in: GlyphVertexOutput) -> @location(0) vec4f {
|
||||
let alpha = number_alpha(in.local, in.display_value);
|
||||
let color = sample_range_color(in.intensity) * mix(0.82, 1.16, saturate(in.intensity));
|
||||
return vec4f(color, alpha);
|
||||
}
|
||||
24
src/theme.rs
24
src/theme.rs
@@ -16,24 +16,24 @@ pub struct AppTheme {
|
||||
}
|
||||
|
||||
pub const ONE_DARK_PRO: AppTheme = AppTheme {
|
||||
bg: egui::Color32::from_rgb(40, 44, 52), // #282C34 editor background
|
||||
panel: egui::Color32::from_rgb(33, 37, 43), // #21252B sidebar/darker
|
||||
bg: egui::Color32::from_rgb(40, 44, 52), // #282C34 editor background
|
||||
panel: egui::Color32::from_rgb(33, 37, 43), // #21252B sidebar/darker
|
||||
panel_strong: egui::Color32::from_rgb(44, 49, 58), // #2C313A slightly lighter
|
||||
panel_deep: egui::Color32::from_rgb(27, 31, 39), // #1B1F27 darkest
|
||||
border: egui::Color32::from_rgb(62, 68, 81), // #3E4451 border
|
||||
border_soft: egui::Color32::from_rgb(44, 49, 58), // #2C313A soft border
|
||||
text: egui::Color32::from_rgb(171, 178, 191), // #ABB2BF default text
|
||||
text_dim: egui::Color32::from_rgb(92, 99, 112), // #5C6370 dimmed text
|
||||
accent: egui::Color32::from_rgb(198, 120, 221), // #C678DD purple (signature)
|
||||
panel_deep: egui::Color32::from_rgb(27, 31, 39), // #1B1F27 darkest
|
||||
border: egui::Color32::from_rgb(62, 68, 81), // #3E4451 border
|
||||
border_soft: egui::Color32::from_rgb(44, 49, 58), // #2C313A soft border
|
||||
text: egui::Color32::from_rgb(171, 178, 191), // #ABB2BF default text
|
||||
text_dim: egui::Color32::from_rgb(92, 99, 112), // #5C6370 dimmed text
|
||||
accent: egui::Color32::from_rgb(198, 120, 221), // #C678DD purple (signature)
|
||||
accent_hot: egui::Color32::from_rgb(229, 192, 123), // #E5C07B yellow/gold
|
||||
radius: 3,
|
||||
};
|
||||
|
||||
// Additional One Dark Pro accent colors (standalone constants)
|
||||
pub const ACCENT_GREEN: egui::Color32 = egui::Color32::from_rgb(152, 195, 121); // #98C379
|
||||
pub const ACCENT_RED: egui::Color32 = egui::Color32::from_rgb(224, 108, 117); // #E06C75
|
||||
pub const ACCENT_BLUE: egui::Color32 = egui::Color32::from_rgb(97, 175, 239); // #61AFEF
|
||||
pub const ACCENT_CYAN: egui::Color32 = egui::Color32::from_rgb(86, 182, 194); // #56B6C2
|
||||
pub const ACCENT_GREEN: egui::Color32 = egui::Color32::from_rgb(152, 195, 121); // #98C379
|
||||
pub const ACCENT_RED: egui::Color32 = egui::Color32::from_rgb(224, 108, 117); // #E06C75
|
||||
pub const ACCENT_BLUE: egui::Color32 = egui::Color32::from_rgb(97, 175, 239); // #61AFEF
|
||||
pub const ACCENT_CYAN: egui::Color32 = egui::Color32::from_rgb(86, 182, 194); // #56B6C2
|
||||
pub const ACCENT_ORANGE: egui::Color32 = egui::Color32::from_rgb(209, 154, 102); // #D19A66
|
||||
|
||||
pub fn apply_theme(ctx: &egui::Context, theme: &AppTheme) {
|
||||
|
||||
48
src/ui.rs
48
src/ui.rs
@@ -3,7 +3,10 @@ use eframe::egui;
|
||||
use crate::{
|
||||
connection::{ConnectionManager, ConnectionState},
|
||||
recording::Recorder,
|
||||
theme::{ONE_DARK_PRO, ACCENT_BLUE, ACCENT_GREEN, ACCENT_ORANGE, ACCENT_RED, accent_text, dim_text, group_frame, panel_frame, tag_button},
|
||||
theme::{
|
||||
ACCENT_BLUE, ACCENT_GREEN, ACCENT_ORANGE, ACCENT_RED, ONE_DARK_PRO, accent_text, dim_text,
|
||||
group_frame, panel_frame, tag_button,
|
||||
},
|
||||
utils::serial_enum,
|
||||
};
|
||||
|
||||
@@ -977,12 +980,7 @@ fn paint_integrated_center_panel(
|
||||
// ── Signal Chart (sparkline) ───────────────────────────────────────────
|
||||
|
||||
/// Draw a mini sparkline chart with current/min/max labels.
|
||||
pub fn draw_signal_chart(
|
||||
ui: &mut egui::Ui,
|
||||
values: &[f32],
|
||||
label: &str,
|
||||
color: egui::Color32,
|
||||
) {
|
||||
pub fn draw_signal_chart(ui: &mut egui::Ui, values: &[f32], label: &str, color: egui::Color32) {
|
||||
let chart_height = 48.0;
|
||||
let chart_width = ui.available_width().min(200.0);
|
||||
|
||||
@@ -991,16 +989,20 @@ pub fn draw_signal_chart(
|
||||
ui.colored_label(color, label);
|
||||
if let (Some(&last), Some(&min), Some(&max)) = (
|
||||
values.last(),
|
||||
values.iter().min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)),
|
||||
values.iter().max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)),
|
||||
values
|
||||
.iter()
|
||||
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)),
|
||||
values
|
||||
.iter()
|
||||
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)),
|
||||
) {
|
||||
ui.colored_label(ONE_DARK_PRO.text, format!("{:.0}", last));
|
||||
ui.colored_label(ONE_DARK_PRO.text_dim, format!("↓{:.0} ↑{:.0}", min, max));
|
||||
}
|
||||
});
|
||||
|
||||
let (rect, _response) = ui
|
||||
.allocate_exact_size(egui::vec2(chart_width, chart_height), egui::Sense::hover());
|
||||
let (rect, _response) =
|
||||
ui.allocate_exact_size(egui::vec2(chart_width, chart_height), egui::Sense::hover());
|
||||
|
||||
if values.len() < 2 {
|
||||
return;
|
||||
@@ -1038,11 +1040,7 @@ pub fn draw_signal_chart(
|
||||
// ── Recording Toolbar ──────────────────────────────────────────────────
|
||||
|
||||
/// Draw recording controls inside the connect panel.
|
||||
pub fn draw_recording_toolbar(
|
||||
ui: &mut egui::Ui,
|
||||
recorder: &Recorder,
|
||||
export_path: &mut String,
|
||||
) {
|
||||
pub fn draw_recording_toolbar(ui: &mut egui::Ui, recorder: &Recorder, export_path: &mut String) {
|
||||
let is_recording = recorder.is_recording();
|
||||
let frame_count = recorder.frame_count();
|
||||
let duration_ms = recorder.duration_ms();
|
||||
@@ -1203,9 +1201,15 @@ pub fn draw_matrix_config_panel(
|
||||
ui.colored_label(dim_text(), "矩阵尺寸");
|
||||
ui.add_space(8.0);
|
||||
ui.colored_label(ONE_DARK_PRO.text, "行");
|
||||
ui.add_sized(egui::vec2(56.0, 20.0), egui::DragValue::new(&mut config.rows).range(1..=128));
|
||||
ui.add_sized(
|
||||
egui::vec2(56.0, 20.0),
|
||||
egui::DragValue::new(&mut config.rows).range(1..=128),
|
||||
);
|
||||
ui.colored_label(ONE_DARK_PRO.text, "列");
|
||||
ui.add_sized(egui::vec2(56.0, 20.0), egui::DragValue::new(&mut config.cols).range(1..=128));
|
||||
ui.add_sized(
|
||||
egui::vec2(56.0, 20.0),
|
||||
egui::DragValue::new(&mut config.cols).range(1..=128),
|
||||
);
|
||||
});
|
||||
|
||||
ui.add_space(4.0);
|
||||
@@ -1235,12 +1239,16 @@ pub fn draw_matrix_config_panel(
|
||||
ui.colored_label(ONE_DARK_PRO.text, "最小");
|
||||
ui.add_sized(
|
||||
egui::vec2(72.0, 20.0),
|
||||
egui::DragValue::new(&mut config.color_min).range(0.0..=10000.0).speed(100.0),
|
||||
egui::DragValue::new(&mut config.color_min)
|
||||
.range(0.0..=10000.0)
|
||||
.speed(100.0),
|
||||
);
|
||||
ui.colored_label(ONE_DARK_PRO.text, "最大");
|
||||
ui.add_sized(
|
||||
egui::vec2(72.0, 20.0),
|
||||
egui::DragValue::new(&mut config.color_max).range(1.0..=10000.0).speed(100.0),
|
||||
egui::DragValue::new(&mut config.color_max)
|
||||
.range(1.0..=10000.0)
|
||||
.speed(100.0),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user