Split large files: ribbon widgets and scene render/selection

- ribbon.rs → ribbon/mod.rs + ribbon/widgets.rs
  Extracted all rendering helpers, button styles, colour constants,
  layout constants and dropdown position calculators into widgets.rs.

- scene/mod.rs → scene/mod.rs + scene/selection.rs + scene/render.rs
  Moved SelectionState into selection.rs; moved CameraState, Primitive,
  shader::Program/Primitive impls, render_style and resolve_pattern
  into render.rs, reducing mod.rs from 1323 to ~1030 lines.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Sonnet 4.6 committed 2026-03-22 23:07:43 +03:00
1 parent ed9cbc2092
commit bdde862597
6 files changed
+1916 -2053

No files matched your search

+7 -266
View File
@@ -11,6 +11,8 @@ pub mod mesh_model;
pub mod object;
pub mod pipeline;
pub mod properties;
mod render;
mod selection;
pub mod tessellate;
pub mod transform;
pub mod truck_tess;
@@ -23,56 +25,24 @@ pub use mesh_model::MeshModel;
pub use object::{GripApply, GripDef};
pub use pipeline::uniforms::Uniforms;
pub use pipeline::viewcube::{
hit_test, hover_id, CubeRegion, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
hit_test, CubeRegion, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX,
};
use pipeline::Pipeline;
pub use selection::SelectionState;
pub use wire_model::WireModel;
use crate::command::EntityTransform;
use acadrust::entities::{BoundaryEdge, BoundaryPath, Hatch as DxfHatch, PolylineEdge};
use acadrust::entities::{Block, BlockEnd, Insert as DxfInsert};
use acadrust::objects::ObjectType;
use acadrust::tables::LineType;
use acadrust::types::{Color as AcadColor, LineWeight, Vector2};
use acadrust::types::Vector2;
use acadrust::{CadDocument, EntityType, Handle, TableEntry};
use glam::{self, Mat4};
use glam;
use iced::mouse;
use iced::time::{Duration, Instant};
use iced::wgpu;
use iced::widget::shader::{self, Viewport};
use iced::{Event, Point, Rectangle, Size};
use iced::time::Duration;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
#[derive(Clone, Default)]
pub struct SelectionState {
pub vp_size: (f32, f32),
pub box_anchor: Option<Point>,
pub box_current: Option<Point>,
pub box_last: Option<(Point, Point)>,
pub box_crossing: bool,
pub box_last_crossing: bool,
pub poly_active: bool,
pub poly_points: Vec<Point>,
pub poly_crossing: bool,
pub poly_last_crossing: bool,
pub context_menu: Option<Point>,
pub last_move_pos: Option<Point>,
pub left_down: bool,
pub left_press_pos: Option<Point>,
pub left_press_time: Option<Instant>,
pub left_dragging: bool,
pub right_down: bool,
pub right_press_pos: Option<Point>,
pub right_dragging: bool,
pub right_last_pos: Option<Point>,
pub middle_down: bool,
pub middle_last_pos: Option<Point>,
pub middle_last_press_time: Option<Instant>,
}
pub struct Scene {
pub camera: Rc<RefCell<Camera>>,
pub selection: Rc<RefCell<SelectionState>>,
@@ -1092,232 +1062,3 @@ impl Default for Scene {
Self::new()
}
}
impl Scene {
fn render_style(&self, e: &EntityType) -> ([f32; 4], f32, [f32; 8], f32) {
let layer_name = &e.common().layer;
let entity_color = {
let ec = &e.common().color;
let resolved = if *ec == AcadColor::ByLayer {
self.document
.layers
.get(layer_name)
.map(|l| &l.color)
.unwrap_or(&AcadColor::WHITE)
} else {
ec
};
let [r, g, b, _] = tessellate::aci_to_rgba(resolved);
let alpha = 1.0 - e.common().transparency.as_percent() as f32;
[r, g, b, alpha]
};
let lt_name = self.resolved_linetype_name(e);
let lt_scale = e.common().linetype_scale as f32;
let (pattern_length, pattern) =
resolve_pattern(&self.document.line_types, lt_name, lt_scale);
let line_weight_px = {
let ew = &e.common().line_weight;
let resolved = match ew {
LineWeight::ByLayer | LineWeight::ByBlock | LineWeight::Default => self
.document
.layers
.get(layer_name)
.map(|l| &l.line_weight)
.unwrap_or(&LineWeight::Default),
_ => ew,
};
const MM_TO_PX: f32 = 96.0 / 25.4;
resolved
.millimeters()
.map(|mm| (mm as f32 * MM_TO_PX).max(1.0))
.unwrap_or(1.0)
};
(entity_color, pattern_length, pattern, line_weight_px)
}
fn resolved_linetype_name<'a>(&'a self, e: &'a EntityType) -> &'a str {
let elt = &e.common().linetype;
if elt.is_empty() || elt.eq_ignore_ascii_case("bylayer") {
self.document
.layers
.get(&e.common().layer)
.map(|l| l.line_type.as_str())
.unwrap_or("Continuous")
} else {
elt.as_str()
}
}
}
// ── Linetype helper ────────────────────────────────────────────────────────
fn resolve_pattern(
table: &acadrust::tables::Table<LineType>,
name: &str,
scale: f32,
) -> (f32, [f32; 8]) {
let solid = (0.0, [0.0f32; 8]);
if name.eq_ignore_ascii_case("continuous")
|| name.eq_ignore_ascii_case("bylayer")
|| name.eq_ignore_ascii_case("byblock")
|| name.is_empty()
{
return solid;
}
let lt = match table.get(name) {
Some(lt) => lt,
None => return solid,
};
if lt.is_continuous() || lt.elements.is_empty() {
return solid;
}
let mut pat = [0.0f32; 8];
let mut pat_len = 0.0f32;
for (i, el) in lt.elements.iter().take(8).enumerate() {
let raw = el.length as f32 * scale;
let encoded = if raw == 0.0 {
0.01 * scale.max(0.01)
} else {
raw
};
pat[i] = encoded;
pat_len += encoded.abs();
}
if pat_len < 1e-6 {
return solid;
}
(pat_len, pat)
}
#[derive(Clone, Default)]
pub struct CameraState {
pub hover_region: Option<usize>,
}
impl<Msg: std::fmt::Debug + Clone> shader::Program<Msg> for Scene {
type State = CameraState;
type Primitive = Primitive;
fn draw(
&self,
state: &Self::State,
_cursor: mouse::Cursor,
bounds: Rectangle,
) -> Self::Primitive {
let cam = self.camera.borrow();
self.selection.borrow_mut().vp_size = (bounds.width, bounds.height);
let mut all_wires = self.entity_wires();
if let Some(iw) = &self.interim_wire {
all_wires.push(iw.clone());
}
all_wires.extend(self.preview_wires.iter().cloned());
Primitive {
wires: all_wires,
hatches: self.synced_hatch_models(),
meshes: self.meshes.values().cloned().collect(),
uniforms: Uniforms::new(&cam, bounds),
// Pass the quaternion-derived rotation matrix directly.
// This is what the ViewCube shader uses, no yaw/pitch reconstruction.
cam_rotation: cam.view_rotation_mat(),
hover_region: state.hover_region,
}
}
fn update(
&self,
state: &mut Self::State,
event: &Event,
bounds: Rectangle,
cursor: mouse::Cursor,
) -> Option<iced::widget::Action<Msg>> {
let pos = cursor.position_in(bounds);
// Use the quaternion rotation matrix for hover detection — same as the shader.
let cam_rotation = { self.camera.borrow().view_rotation_mat() };
if let Some(p) = pos {
state.hover_region = hover_id(
p.x,
p.y,
bounds.width,
bounds.height,
cam_rotation,
VIEWCUBE_PX,
);
} else {
state.hover_region = None;
}
let _ = event;
None
}
fn mouse_interaction(
&self,
state: &Self::State,
_b: Rectangle,
_c: mouse::Cursor,
) -> mouse::Interaction {
if state.hover_region.is_some() {
return mouse::Interaction::Pointer;
}
mouse::Interaction::default()
}
}
#[derive(Debug)]
pub struct Primitive {
wires: Vec<WireModel>,
hatches: Vec<HatchModel>,
meshes: Vec<MeshModel>,
uniforms: Uniforms,
/// Camera rotation matrix derived from the quaternion.
/// Used by the ViewCube pipeline — no gimbal lock.
cam_rotation: Mat4,
hover_region: Option<usize>,
}
impl shader::Primitive for Primitive {
type Pipeline = Pipeline;
fn prepare(
&self,
pipeline: &mut Pipeline,
device: &wgpu::Device,
queue: &wgpu::Queue,
_bounds: &Rectangle,
viewport: &Viewport,
) {
let phys = viewport.physical_size();
let size = Size::new(phys.width, phys.height);
pipeline.ensure_depth_texture(device, size);
pipeline.viewcube.ensure_depth_texture(device, size);
pipeline.upload_uniforms(queue, &self.uniforms);
pipeline.upload_hatches(device, &self.hatches);
pipeline.upload_meshes(device, &self.meshes);
pipeline.upload_wires(device, &self.wires);
let logical = viewport.logical_size();
// Pass the quaternion rotation matrix — fixes gimbal lock at top/bottom views.
pipeline.viewcube.upload(
queue,
self.cam_rotation,
logical.width as u32,
logical.height as u32,
self.hover_region,
);
}
fn render(
&self,
pipeline: &Pipeline,
encoder: &mut wgpu::CommandEncoder,
target: &wgpu::TextureView,
clip: &Rectangle<u32>,
) {
pipeline.render(encoder, target, *clip);
pipeline.viewcube.render(encoder, target, *clip);
}
}
+250
View File
@@ -0,0 +1,250 @@
// GPU rendering primitives, shader::Program / shader::Primitive impls,
// and entity render-style helpers for the Scene.
use acadrust::tables::LineType;
use acadrust::types::{Color as AcadColor, LineWeight};
use acadrust::EntityType;
use glam::Mat4;
use iced::mouse;
use iced::widget::shader::{self, Viewport};
use iced::{Event, Rectangle, Size};
use super::pipeline::viewcube::{hover_id, VIEWCUBE_PX};
use super::pipeline::Pipeline;
use super::tessellate;
use super::{HatchModel, MeshModel, Scene, Uniforms, WireModel};
// ── Camera hover state (shader::Program::State) ───────────────────────────
#[derive(Clone, Default)]
pub struct CameraState {
pub hover_region: Option<usize>,
}
// ── GPU primitive ─────────────────────────────────────────────────────────
#[derive(Debug)]
pub struct Primitive {
pub(super) wires: Vec<WireModel>,
pub(super) hatches: Vec<HatchModel>,
pub(super) meshes: Vec<MeshModel>,
pub(super) uniforms: Uniforms,
/// Camera rotation matrix derived from the quaternion.
/// Used by the ViewCube pipeline — no gimbal lock.
pub(super) cam_rotation: Mat4,
pub(super) hover_region: Option<usize>,
}
// ── shader::Program impl ──────────────────────────────────────────────────
impl<Msg: std::fmt::Debug + Clone> shader::Program<Msg> for Scene {
type State = CameraState;
type Primitive = Primitive;
fn draw(
&self,
state: &Self::State,
_cursor: mouse::Cursor,
bounds: Rectangle,
) -> Self::Primitive {
let cam = self.camera.borrow();
self.selection.borrow_mut().vp_size = (bounds.width, bounds.height);
let mut all_wires = self.entity_wires();
if let Some(iw) = &self.interim_wire {
all_wires.push(iw.clone());
}
all_wires.extend(self.preview_wires.iter().cloned());
Primitive {
wires: all_wires,
hatches: self.synced_hatch_models(),
meshes: self.meshes.values().cloned().collect(),
uniforms: Uniforms::new(&cam, bounds),
cam_rotation: cam.view_rotation_mat(),
hover_region: state.hover_region,
}
}
fn update(
&self,
state: &mut Self::State,
event: &Event,
bounds: Rectangle,
cursor: mouse::Cursor,
) -> Option<iced::widget::Action<Msg>> {
let pos = cursor.position_in(bounds);
let cam_rotation = { self.camera.borrow().view_rotation_mat() };
if let Some(p) = pos {
state.hover_region = hover_id(
p.x,
p.y,
bounds.width,
bounds.height,
cam_rotation,
VIEWCUBE_PX,
);
} else {
state.hover_region = None;
}
let _ = event;
None
}
fn mouse_interaction(
&self,
state: &Self::State,
_b: Rectangle,
_c: mouse::Cursor,
) -> mouse::Interaction {
if state.hover_region.is_some() {
return mouse::Interaction::Pointer;
}
mouse::Interaction::default()
}
}
// ── shader::Primitive impl ────────────────────────────────────────────────
impl shader::Primitive for Primitive {
type Pipeline = Pipeline;
fn prepare(
&self,
pipeline: &mut Pipeline,
device: &iced::wgpu::Device,
queue: &iced::wgpu::Queue,
_bounds: &Rectangle,
viewport: &Viewport,
) {
let phys = viewport.physical_size();
let size = Size::new(phys.width, phys.height);
pipeline.ensure_depth_texture(device, size);
pipeline.viewcube.ensure_depth_texture(device, size);
pipeline.upload_uniforms(queue, &self.uniforms);
pipeline.upload_hatches(device, &self.hatches);
pipeline.upload_meshes(device, &self.meshes);
pipeline.upload_wires(device, &self.wires);
let logical = viewport.logical_size();
pipeline.viewcube.upload(
queue,
self.cam_rotation,
logical.width as u32,
logical.height as u32,
self.hover_region,
);
}
fn render(
&self,
pipeline: &Pipeline,
encoder: &mut iced::wgpu::CommandEncoder,
target: &iced::wgpu::TextureView,
clip: &Rectangle<u32>,
) {
pipeline.render(encoder, target, *clip);
pipeline.viewcube.render(encoder, target, *clip);
}
}
// ── Render-style helpers (impl Scene) ────────────────────────────────────
impl Scene {
pub(super) fn render_style(&self, e: &EntityType) -> ([f32; 4], f32, [f32; 8], f32) {
let layer_name = &e.common().layer;
let entity_color = {
let ec = &e.common().color;
let resolved = if *ec == AcadColor::ByLayer {
self.document
.layers
.get(layer_name)
.map(|l| &l.color)
.unwrap_or(&AcadColor::WHITE)
} else {
ec
};
let [r, g, b, _] = tessellate::aci_to_rgba(resolved);
let alpha = 1.0 - e.common().transparency.as_percent() as f32;
[r, g, b, alpha]
};
let lt_name = self.resolved_linetype_name(e);
let lt_scale = e.common().linetype_scale as f32;
let (pattern_length, pattern) =
resolve_pattern(&self.document.line_types, lt_name, lt_scale);
let line_weight_px = {
let ew = &e.common().line_weight;
let resolved = match ew {
LineWeight::ByLayer | LineWeight::ByBlock | LineWeight::Default => self
.document
.layers
.get(layer_name)
.map(|l| &l.line_weight)
.unwrap_or(&LineWeight::Default),
_ => ew,
};
const MM_TO_PX: f32 = 96.0 / 25.4;
resolved
.millimeters()
.map(|mm| (mm as f32 * MM_TO_PX).max(1.0))
.unwrap_or(1.0)
};
(entity_color, pattern_length, pattern, line_weight_px)
}
pub(super) fn resolved_linetype_name<'a>(&'a self, e: &'a EntityType) -> &'a str {
let elt = &e.common().linetype;
if elt.is_empty() || elt.eq_ignore_ascii_case("bylayer") {
self.document
.layers
.get(&e.common().layer)
.map(|l| l.line_type.as_str())
.unwrap_or("Continuous")
} else {
elt.as_str()
}
}
}
// ── Linetype pattern helper ───────────────────────────────────────────────
pub(super) fn resolve_pattern(
table: &acadrust::tables::Table<LineType>,
name: &str,
scale: f32,
) -> (f32, [f32; 8]) {
let solid = (0.0, [0.0f32; 8]);
if name.eq_ignore_ascii_case("continuous")
|| name.eq_ignore_ascii_case("bylayer")
|| name.eq_ignore_ascii_case("byblock")
|| name.is_empty()
{
return solid;
}
let lt = match table.get(name) {
Some(lt) => lt,
None => return solid,
};
if lt.is_continuous() || lt.elements.is_empty() {
return solid;
}
let mut pat = [0.0f32; 8];
let mut pat_len = 0.0f32;
for (i, el) in lt.elements.iter().take(8).enumerate() {
let raw = el.length as f32 * scale;
let encoded = if raw == 0.0 {
0.01 * scale.max(0.01)
} else {
raw
};
pat[i] = encoded;
pat_len += encoded.abs();
}
if pat_len < 1e-6 {
return solid;
}
(pat_len, pat)
}
+30
View File
@@ -0,0 +1,30 @@
use iced::time::Instant;
use iced::Point;
/// Mouse / selection interaction state for the viewport.
#[derive(Clone, Default)]
pub struct SelectionState {
pub vp_size: (f32, f32),
pub box_anchor: Option<Point>,
pub box_current: Option<Point>,
pub box_last: Option<(Point, Point)>,
pub box_crossing: bool,
pub box_last_crossing: bool,
pub poly_active: bool,
pub poly_points: Vec<Point>,
pub poly_crossing: bool,
pub poly_last_crossing: bool,
pub context_menu: Option<Point>,
pub last_move_pos: Option<Point>,
pub left_down: bool,
pub left_press_pos: Option<Point>,
pub left_press_time: Option<Instant>,
pub left_dragging: bool,
pub right_down: bool,
pub right_press_pos: Option<Point>,
pub right_dragging: bool,
pub right_last_pos: Option<Point>,
pub middle_down: bool,
pub middle_last_pos: Option<Point>,
pub middle_last_press_time: Option<Instant>,
}