pub mod math; use iced::executor; use iced::mouse; use iced::widget::canvas::{stroke, Cache, Geometry, LineCap, Path, Stroke}; use iced::widget::column; use iced::widget::text_input; use iced::widget::{canvas, container}; use iced::{ Application, Color, Command, Element, Length, Point, Rectangle, Renderer, Settings, Theme, Vector, }; use math::context::Context; use math::expression_function::ExpressionFunction; use math::function::Function; use crate::math::complex::Complex; use crate::math::function::FunctionArgument; pub fn main() -> iced::Result { Graph::run(Settings { antialiasing: true, ..Settings::default() }) } #[derive(Default)] struct Graph { graph: Cache, center_x: f32, center_y: f32, scale: f32, func: ExpressionFunction, ctx: Context, input_state: String, } #[derive(Debug, Clone)] enum Message { InputChanged(String), FunctionRun, } impl Graph { fn map_coords(&self, x: f32, y: f32) -> Point { Point::new( (self.center_x + x) * self.scale, (self.center_y - y) * self.scale, ) } } impl Application for Graph { type Executor = executor::Default; type Message = Message; type Theme = Theme; type Flags = (); fn new(_flags: ()) -> (Self, Command) { ( Graph { input_state: "f(x) = x^2".to_string(), func: ExpressionFunction::from_string("f(x) = x^2".to_string()), ctx: Context::commonsense(), scale: 50.0, ..Default::default() }, Command::none(), ) } fn title(&self) -> String { String::from("MathEval") } fn update(&mut self, message: Message) -> Command { match message { Message::InputChanged(s) => { self.input_state = s; } Message::FunctionRun => { self.func = ExpressionFunction::from_string(self.input_state.clone()); self.graph.clear(); } } Command::none() } fn view(&self) -> Element { let input = text_input("x", &self.input_state) .on_input(Message::InputChanged) .on_submit(Message::FunctionRun) .padding(15) .size(30); let canvas = canvas(self as &Self) .width(Length::Fill) .height(Length::Fill); let container = container(canvas) .width(Length::Fill) .height(Length::Fill) .padding(20); column![input, container].into() } } impl canvas::Program for Graph { type State = (); 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 unitline = Stroke { width: 2.0, style: stroke::Style::Solid(Color::new(0.0, 0.0, 0.0, 1.0)), line_cap: LineCap::Square, ..Stroke::default() }; let unitline_whole = Stroke { width: 1.0, style: stroke::Style::Solid(Color::new(0.5, 0.5, 0.5, 1.0)), line_cap: LineCap::Square, ..Stroke::default() }; let zeroline = Stroke { width: 3.0, style: stroke::Style::Solid(Color::new(0.0, 0.0, 0.8, 1.0)), line_cap: LineCap::Square, ..Stroke::default() }; let graphline = Stroke { width: 3.0, style: stroke::Style::Solid(Color::new(0.8, 0.0, 0.0, 1.0)), line_cap: LineCap::Square, ..Stroke::default() }; frame.translate(Vector { x: frame.center().x, y: frame.center().y, }); frame.with_save(|frame| { let start_y = ((self.center_y - frame.height() / 2.0) / self.scale) as i64 * self.scale as i64; let start_x = ((self.center_x - frame.width() / 2.0) / self.scale) as i64 * self.scale as i64; let end_y = ((self.center_y + frame.height() / 2.0) / self.scale) as i64 * self.scale as i64; let end_x = ((self.center_x + frame.width() / 2.0) / self.scale) as i64 * self.scale as i64; for y in (start_y..end_y + self.scale as i64).step_by(1) { let line = Path::line( self.map_coords(start_x as f32, y as f32), self.map_coords(end_x as f32, y as f32), ); frame.stroke(&line, unitline_whole.clone()); } for x in (start_x..end_x + self.scale as i64).step_by(1) { let line = Path::line( self.map_coords(x as f32, start_y as f32), self.map_coords(x as f32, end_y as f32), ); frame.stroke(&line, unitline_whole.clone()); } let start_y_100 = ((self.center_y - frame.height() / 2.0) / self.scale * 10.0) as i64 * 10 * self.scale as i64; let start_x_100 = ((self.center_x - frame.width() / 2.0) / self.scale * 10.0) as i64 * 10 * self.scale as i64; for y in (start_y_100..end_y).step_by(10) { let line = Path::line( self.map_coords(start_x as f32, y as f32), self.map_coords(end_x as f32, y as f32), ); frame.stroke(&line, unitline.clone()); } for x in (start_x_100..end_x).step_by(10) { let line = Path::line( self.map_coords(x as f32, start_y as f32), self.map_coords(x as f32, end_y as f32), ); frame.stroke(&line, unitline.clone()); } let line = Path::line( self.map_coords(0.0, start_y as f32), self.map_coords(0.0, end_y as f32), ); frame.stroke(&line, zeroline.clone()); let line = Path::line( self.map_coords(start_x as f32, 0.0), self.map_coords(end_x as f32, 0.0), ); frame.stroke(&line, zeroline.clone()); for x in start_x..end_x { for d in 0..10 { let d = d as f32 / 10.0; let x1 = x as f32 + d; let y1 = self .func .eval( FunctionArgument::new(vec![Complex::new(x1 as f64, 0.0)]), &self.ctx, ) .unwrap() .real as f32; let x2 = x as f32 + d + 0.1; let y2 = self .func .eval( FunctionArgument::new(vec![Complex::new(x2 as f64, 0.0)]), &self.ctx, ) .unwrap() .real as f32; if y1.is_finite() && !y1.is_nan() && y2.is_finite() && !y2.is_nan() { let line = Path::line(self.map_coords(x1, y1), self.map_coords(x2, y2)); frame.stroke(&line, graphline.clone()) } } } }); }); vec![graph] } }