//! Point Style (DDPTYPE) dialog — pick a PDMODE glyph from a grid and set the //! point size (PDSIZE), relative to the screen or in absolute units. //! //! Changes apply live to the active document header; the renderer rebuilds the //! point glyphs (see `entities::point`). Command entry (`PDMODE` / `PDSIZE`) //! does the same without the dialog. use crate::app::Message; use crate::t; use crate::ui::style::common::muted_style; use crate::ui::style::form::{dialog_button, field_style_with_radius, form_radio}; use iced::widget::{button, canvas, column, container, row, text, text_input, Space}; use iced::{mouse, Background, Border, Element, Length, Point, Rectangle, Size, Theme}; use std::borrow::Cow; const CELL_PX: f32 = 44.0; /// Glyph-grid columns (low-nibble shape) × rows (enclosure bits): /// shapes: 0=dot, 1=none, 2='+', 3='×', 4='|' /// enclosures: 0=none, 32=circle, 64=square, 96=both const ENCLOSURES: [i16; 4] = [0, 32, 64, 96]; const SHAPES: [i16; 5] = [0, 1, 2, 3, 4]; /// Canvas that renders a single PDMODE glyph inside a grid cell. struct GlyphCanvas { mode: i16, } impl canvas::Program for GlyphCanvas { type State = (); fn draw( &self, _state: &(), renderer: &iced::Renderer, theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec { let mut frame = canvas::Frame::new(renderer, bounds.size()); let glyph = theme.palette().background.base.text; let (cx, cy) = (bounds.width * 0.5, bounds.height * 0.5); let r = bounds.width.min(bounds.height) * 0.30; let stroke = canvas::Stroke { width: 1.4, style: canvas::Style::Solid(glyph), ..Default::default() }; let line = |a: Point, b: Point| canvas::Path::line(a, b); match self.mode & 0x0F { 0 => frame.fill(&canvas::Path::circle(Point::new(cx, cy), 2.4), glyph), 1 => {} 2 => { frame.stroke( &line(Point::new(cx - r, cy), Point::new(cx + r, cy)), stroke.clone(), ); frame.stroke( &line(Point::new(cx, cy - r), Point::new(cx, cy + r)), stroke.clone(), ); } 3 => { frame.stroke( &line(Point::new(cx - r, cy - r), Point::new(cx + r, cy + r)), stroke.clone(), ); frame.stroke( &line(Point::new(cx - r, cy + r), Point::new(cx + r, cy - r)), stroke.clone(), ); } 4 => frame.stroke( &line(Point::new(cx, cy), Point::new(cx, cy - r)), stroke.clone(), ), _ => {} } if self.mode & 32 != 0 { frame.stroke(&canvas::Path::circle(Point::new(cx, cy), r), stroke.clone()); } if self.mode & 64 != 0 { let sq = canvas::Path::rectangle(Point::new(cx - r, cy - r), Size::new(2.0 * r, 2.0 * r)); frame.stroke(&sq, stroke.clone()); } vec![frame.into_geometry()] } } fn cell<'a>(value: i16, selected: bool) -> Element<'a, Message> { let glyph = canvas(GlyphCanvas { mode: value }) .width(Length::Fixed(CELL_PX)) .height(Length::Fixed(CELL_PX)); button(glyph) .padding(0) .on_press(Message::PointStyleSetMode(value)) .style(move |theme: &Theme, status| { let palette = theme.palette(); let pair = if selected { palette.primary.strong } else if matches!(status, button::Status::Hovered | button::Status::Pressed) { palette.background.strong } else { palette.background.weak }; button::Style { background: Some(Background::Color(pair.color)), border: Border { color: palette.background.neutral.color, width: 1.0, radius: 3.0.into(), }, text_color: pair.text, ..Default::default() } }) .into() } pub fn view_window<'a>( pdmode: i16, relative: bool, size_buf: &str, sizing: crate::ui::modal::ModalSizing, ) -> Element<'a, Message> { let width = sizing.width; let height = sizing.height; // Glyph grid: a row per enclosure, a cell per shape. let mut grid = column![].spacing(6); for enc in ENCLOSURES { let mut r = row![].spacing(6); for sh in SHAPES { let value = enc + sh; r = r.push(cell(value, value == pdmode)); } grid = grid.push(r); } let size_row = row![ text(t!("Point Size:")).size(13), Space::new().width(10), text_input("0", size_buf) .on_input(Message::PointStyleSizeInput) .on_submit(Message::PointStyleApplySize) .style(|theme, status| field_style_with_radius(theme, status, 4.0)) .size(13) .width(110), Space::new().width(6), text(if relative { Cow::Borrowed("%") } else { t!("units") }) .size(12) .style(muted_style), ] .align_y(iced::Center); let radios = column![ form_radio( t!("Set Size Relative to Screen"), true, Some(relative), Message::PointStyleSizeRelative, ), form_radio( t!("Set Size in Absolute Units"), false, Some(relative), Message::PointStyleSizeRelative, ), ] .spacing(6); let actions = row![ Space::new().width(width), dialog_button(t!("OK"), Message::PointStyleOk, true), dialog_button(t!("Cancel"), Message::CloseModal, false), ] .spacing(8) .align_y(iced::Center); container( column![ text(t!("Point Style")).size(18), Space::new().height(6), grid, Space::new().height(12), size_row, Space::new().height(8), radios, Space::new().height(12), actions.width(width), ] .spacing(4) .padding(20) .width(width) .height(height), ) .style(|theme: &Theme| container::Style { background: Some(Background::Color(theme.palette().background.base.color)), ..Default::default() }) .width(width) .height(height) .into() }