This commit is contained in:
jess 2026-06-05 22:17:08 -07:00
parent a5d5f01674
commit 3645df0dac
5 changed files with 48 additions and 23 deletions

View File

@ -10,13 +10,13 @@ pub(crate) fn format_spice(n: f64, unit: &str) -> String {
let (prefix, scale): (&str, f64) = if abs_n >= 1.0 { let (prefix, scale): (&str, f64) = if abs_n >= 1.0 {
("", 1.0) ("", 1.0)
} else if abs_n >= 1e-3 { } else if abs_n >= 1e-3 {
("M", 1e-3) ("m", 1e-3)
} else if abs_n >= 1e-6 { } else if abs_n >= 1e-6 {
("U", 1e-6) ("U", 1e-6)
} else if abs_n >= 1e-9 { } else if abs_n >= 1e-9 {
("N", 1e-9) ("N", 1e-9)
} else { } else {
("P", 1e-12) ("p", 1e-12)
}; };
let mantissa = n / scale; let mantissa = n / scale;
let mag = mantissa.abs().log10().floor() as i32; let mag = mantissa.abs().log10().floor() as i32;

View File

@ -2,10 +2,15 @@ pub(crate) const SPICE_UNITS: &[&str] = &["F", "H", "HZ", "V", "A", "W", "R", "O
pub(crate) fn spice_prefix_scale(c: char) -> Option<f64> { pub(crate) fn spice_prefix_scale(c: char) -> Option<f64> {
match c { match c {
'm' | 'M' => Some(1e-3), 'P' => Some(1e15),
't' | 'T' => Some(1e12),
'b' | 'B' => Some(1e9),
'M' => Some(1e6),
'k' | 'K' => Some(1e3),
'm' => Some(1e-3),
'u' | 'U' | 'µ' | 'μ' => Some(1e-6), 'u' | 'U' | 'µ' | 'μ' => Some(1e-6),
'n' | 'N' => Some(1e-9), 'n' | 'N' => Some(1e-9),
'p' | 'P' => Some(1e-12), 'p' => Some(1e-12),
_ => None, _ => None,
} }
} }

View File

@ -87,7 +87,28 @@ use super::helpers::*;
let mut i = Interpreter::new(); let mut i = Interpreter::new();
i.exec("use spice").unwrap(); i.exec("use spice").unwrap();
let v = i.eval("0.5nF").unwrap(); let v = i.eval("0.5nF").unwrap();
assert_eq!(v.display(), "500PF"); assert_eq!(v.display(), "500pF");
}
#[test]
fn spice_large_prefixes_case_sensitive() {
let mut i = Interpreter::new();
i.exec("use spice").unwrap();
let mut scale = |src: &str| match i.eval(src).unwrap() {
Value::Spice(n, _) => n,
Value::Number(n) => n,
other => panic!("expected scalar, got {:?}", other),
};
assert!(approx(scale("100kHz"), 1e5));
assert!(approx(scale("100KHz"), 1e5));
assert!(approx(scale("100Mhz"), 1e8));
assert!(approx(scale("100mhz"), 0.1));
assert!(approx(scale("2bHz"), 2e9));
assert!(approx(scale("2BHz"), 2e9));
assert!(approx(scale("3THz"), 3e12));
assert!(approx(scale("3tHz"), 3e12));
assert!(approx(scale("5PHz"), 5e15));
assert!(approx(scale("5pHz"), 5e-12));
} }
#[test] #[test]

View File

@ -397,7 +397,7 @@ impl super::EditorState {
let Some(inner) = inner_opt else { continue }; let Some(inner) = inner_opt else { continue };
let is_active = self.active_free.as_ref() == Some(id); let is_active = self.active_free.as_ref() == Some(id);
let band = (self.font_size * 1.3) * 0.5; let band = (self.font_size * 1.3).max(18.0);
let body: Element<'_, Message, Theme, iced_wgpu::Renderer> = if is_active { let body: Element<'_, Message, Theme, iced_wgpu::Renderer> = if is_active {
let right = self.build_resize_band( let right = self.build_resize_band(
id.clone(), id.clone(),

View File

@ -85,12 +85,18 @@ impl EditorState {
.into() .into()
} else { } else {
let editor = self.view_blocks(); let editor = self.view_blocks();
match self.build_free_overlay() { let mut layers: Vec<Element<'_, Message, Theme, iced_wgpu::Renderer>> = vec![editor];
Some(overlay) => iced_widget::stack![editor, overlay] if let Some(overlay) = self.build_free_overlay() {
.width(Length::Fill) layers.push(overlay);
.height(Length::Fill) }
.into(), // context menu renders above the free overlay
None => editor, if let Some(menu_state) = &self.context_menu {
layers.push(self.context_menu_view(menu_state));
}
if layers.len() == 1 {
layers.pop().unwrap()
} else {
iced_widget::stack(layers).width(Length::Fill).height(Length::Fill).into()
} }
}; };
@ -167,12 +173,9 @@ impl EditorState {
} }
fn view_blocks(&self) -> Element<'_, Message, Theme, iced_wgpu::Renderer> { fn view_blocks(&self) -> Element<'_, Message, Theme, iced_wgpu::Renderer> {
let has_computed_layers = !self.eval_results.is_empty() // a lone text block always renders through the compositor
|| !self.computed_tables.is_empty()
|| !self.computed_trees.is_empty();
let single_text_block = self.block_count() == 1 let single_text_block = self.block_count() == 1
&& self.block_at(0).map(|b| b.as_any().is::<TextBlock>()).unwrap_or(false) && self.block_at(0).map(|b| b.as_any().is::<TextBlock>()).unwrap_or(false);
&& !has_computed_layers;
#[cfg(any(target_os = "linux", target_os = "windows"))] #[cfg(any(target_os = "linux", target_os = "windows"))]
let title_bar_h = 0.0_f32; let title_bar_h = 0.0_f32;
@ -379,15 +382,11 @@ impl EditorState {
}; };
let minimap_layer = self.minimap_overlay().unwrap_or_else(empty_layer); let minimap_layer = self.minimap_overlay().unwrap_or_else(empty_layer);
let ctx_layer = match &self.context_menu {
Some(menu_state) => self.context_menu_view(menu_state),
None => empty_layer(),
};
let spillover_layer = self.spillover_view().unwrap_or_else(empty_layer); let spillover_layer = self.spillover_view().unwrap_or_else(empty_layer);
// the document scrollable stays at stack layer 0, never re-wrapped by an overlay. // the document scrollable stays at stack layer 0, never re-wrapped by an overlay.
// a Fill stack gives the scrollable the viewport height, not the zero intrinsic of a Shrink stack // a Fill stack gives the scrollable the viewport height, not the zero intrinsic of a Shrink stack
iced_widget::stack![inner, selection_tint, minimap_layer, ctx_layer, spillover_layer] iced_widget::stack![inner, selection_tint, minimap_layer, spillover_layer]
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
.into() .into()
@ -867,7 +866,7 @@ impl EditorState {
CtxTarget::Object(node_id) => { CtxTarget::Object(node_id) => {
let placed = self.free_placements.contains_key(node_id); let placed = self.free_placements.contains_key(node_id);
let mode = self.object_mode(node_id); let mode = self.object_mode(node_id);
// a bulleted radio row that sets this object's mode // one object-mode option, bulleted when active
let mode_item = |label: &str, m: LayoutMode| -> Element<'_, Message, Theme, iced_wgpu::Renderer> { let mode_item = |label: &str, m: LayoutMode| -> Element<'_, Message, Theme, iced_wgpu::Renderer> {
item( item(
&format!("{} {}", if mode == m { "" } else { " " }, label), &format!("{} {}", if mode == m { "" } else { " " }, label),