use std::sync::Mutex; use super::Message; use iced::{ event, mouse::{self, Interaction}, widget::canvas::{self, stroke, Cache, Frame, Geometry, LineCap, Path, Stroke, Text}, Color, Point, Rectangle, Renderer, Size, Theme, Vector, }; use crate::{ function_cache::FunctionCache, math::{complex::Complex, context::Context, expression_function::ExpressionFunction}, }; #[derive(Default)] pub struct GraphCanvas { graph: Cache, funcs: Mutex>>, ctx: Context, } impl GraphCanvas { pub fn new() -> Self { let ctx = Context::commonsense(); Self { graph: Cache::default(), funcs: Mutex::new(Vec::new()), ctx, } } pub fn clear(&self) { self.graph.clear(); } pub fn push(&mut self, f: &str) { if let Ok(f) = ExpressionFunction::from_string(f.to_string(), self.ctx.operations()) { self.funcs.lock().unwrap().push(Some(FunctionCache::new(f))); } else { self.funcs.lock().unwrap().push(None); } } pub fn set(&mut self, i: usize, f: &str) { if let Ok(f) = ExpressionFunction::from_string(f.to_string(), self.ctx.operations()) { self.funcs.lock().unwrap()[i] = Some(FunctionCache::new(f)); } else { self.funcs.lock().unwrap().push(None); } } pub fn remove(&mut self, i: usize) { self.funcs.lock().unwrap().remove(i); } } #[derive(Copy, Clone)] pub struct GraphState { interaction: Interaction, center: Point, last_position: Option, scale: f32, } impl GraphState { fn view_rectangle(&self, frame: &Frame, step: usize) -> Rectangle { let s = Size::new( self.map_to_step(frame.width() / self.scale + 2.0 * step as f32, step), self.map_to_step(frame.height() / self.scale + 2.0 * step as f32, step), ); let p = Point::new( self.map_to_step(-self.center.x / self.scale - s.width / 2.0, step), self.map_to_step(-self.center.y / self.scale - s.height / 2.0, step), ); Rectangle::new(p, s) } fn map_to_step(&self, n: f32, step: usize) -> f32 { ((n as i64 / step as i64) * step as i64) as f32 } fn map_coords(&self, x: f32, y: f32) -> Point { Point::new(x, -y) } } impl Default for GraphState { fn default() -> Self { Self { interaction: Interaction::default(), center: Point::default(), last_position: None, scale: 50.0, } } } impl canvas::Program for GraphCanvas { type State = GraphState; fn draw( &self, state: &Self::State, renderer: &Renderer, _theme: &Theme, bounds: Rectangle, _cursor: mouse::Cursor, ) -> Vec { let graph = self.graph.draw(renderer, bounds.size(), |frame| { let zcolor = Color::new(0.27, 0.52, 0.53, 1.0); let lcolor = Color::new(0.49, 0.43, 0.39, 1.0); let gcolor = Color::new(0.8, 0.14, 0.11, 1.0); let step = 10_usize.pow((314.1 / state.scale).log10() as u32); let unitline_whole = Stroke { width: 1.0, style: stroke::Style::Solid(lcolor), line_cap: LineCap::Round, ..Stroke::default() }; let zeroline = Stroke { width: 3.0, style: stroke::Style::Solid(zcolor), line_cap: LineCap::Round, ..Stroke::default() }; let graphline = Stroke { width: 3.0, style: stroke::Style::Solid(gcolor), line_cap: LineCap::Round, ..Stroke::default() }; let rect = state.view_rectangle(frame, step); let x_end = (rect.x + rect.width) as i64; let y_end = (rect.y + rect.height) as i64; frame.translate(Vector { x: frame.center().x + state.center.x, y: frame.center().y + state.center.y, }); frame.scale(state.scale); for y in (rect.y as i64..y_end).step_by(step) { let line = Path::line( Point::new(rect.x, y as f32), Point::new(rect.x + rect.width, y as f32), ); frame.stroke(&line, unitline_whole.clone()); } for x in (rect.x as i64..x_end).step_by(step) { let line = Path::line( Point::new(x as f32, rect.y), Point::new(x as f32, rect.y + rect.height), ); frame.stroke(&line, unitline_whole.clone()); } frame.stroke( &Path::line( Point::new(0.0, rect.y), Point::new(0.0, rect.y + rect.height), ), zeroline.clone(), ); frame.stroke( &Path::line( Point::new(rect.x, 0.0), Point::new(rect.x + rect.width, 0.0), ), zeroline.clone(), ); frame.with_save(|frame| { for x in (rect.x as i64..x_end).step_by(step) { let p = Point::new(x as f32, 0.0); frame.fill(&Path::circle(p, 10.0 / state.scale), zcolor); frame.fill_text(Text { content: x.to_string(), position: p, horizontal_alignment: iced::alignment::Horizontal::Center, vertical_alignment: iced::alignment::Vertical::Center, color: Color::WHITE, ..Text::default() }); } for y in (rect.y as i64..y_end).step_by(step) { if y == 0 { continue; } let p = Point::new(0.0, y as f32); frame.fill(&Path::circle(p, 10.0 / state.scale), zcolor); frame.fill_text(Text { content: (-y).to_string(), position: p, horizontal_alignment: iced::alignment::Horizontal::Center, vertical_alignment: iced::alignment::Vertical::Center, color: Color::WHITE, ..Text::default() }); } }); let step = (step / 10).max(1); for func in self .funcs .lock() .unwrap() .iter_mut() .filter(|f| f.is_some()) .map(|f| f.as_mut().unwrap()) { let mut y1 = func.eval(Complex::new(rect.x as f64, 0.0), &self.ctx).real as f32; for x in (rect.x as i64..(rect.x + rect.width) as i64).step_by(step) { for d in 0..10 { let d = d as f32 / 10.0; let x1 = x as f32 + d * step as f32; let x2 = x as f32 + (d + 0.1) * step as f32; let y2 = func.eval(Complex::new(x2 as f64, 0.0), &self.ctx).real as f32; if y1.is_finite() && !y1.is_nan() && y2.is_finite() && !y2.is_nan() { let line = Path::line(state.map_coords(x1, y1), state.map_coords(x2, y2)); frame.stroke(&line, graphline.clone()) } y1 = y2; } } } }); vec![graph] } fn update( &self, state: &mut Self::State, event: canvas::Event, bounds: Rectangle, cursor: iced::advanced::mouse::Cursor, ) -> (canvas::event::Status, Option) { if cursor.position_in(bounds).is_none() { return (event::Status::Ignored, None); } if let canvas::Event::Mouse(mouse::Event::ButtonPressed(_)) = event { state.interaction = Interaction::Grabbing; state.last_position = cursor.position(); return (event::Status::Captured, Some(Message::RefreshGraphCanvas)); } if let canvas::Event::Mouse(mouse::Event::CursorMoved { position }) = event { if state.interaction == Interaction::Grabbing { if let Some(lp) = state.last_position { state.center.x += position.x - lp.x; state.center.y += position.y - lp.y; } state.last_position = Some(position); return (event::Status::Captured, Some(Message::RefreshGraphCanvas)); } } if let canvas::Event::Mouse(mouse::Event::ButtonReleased(_)) = event { state.interaction = Interaction::Pointer; } if let canvas::Event::Mouse(mouse::Event::WheelScrolled { delta }) = event { match delta { mouse::ScrollDelta::Lines { y, .. } => { if y > 0.0 { state.scale /= 1.1; } else { state.scale *= 1.1; } state.scale = state.scale.max(0.000001); return (event::Status::Captured, Some(Message::RefreshGraphCanvas)); } _ => {} } } (event::Status::Ignored, None) } fn mouse_interaction( &self, state: &Self::State, _bounds: Rectangle, _cursor: mouse::Cursor, ) -> mouse::Interaction { match state.interaction { Interaction::Grabbing => mouse::Interaction::Grabbing, _ => mouse::Interaction::Grab, } } }