pub mod math; use iced::event; use iced::executor; use iced::mouse; use iced::mouse::Interaction; 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, func: ExpressionFunction, ctx: Context, input_state: String, } #[derive(Debug, Clone)] enum Message { InputChanged(String), UpdateScreen, } 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(), ..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::UpdateScreen => { 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::UpdateScreen) .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() } } #[derive(Copy, Clone)] struct MouseState { interaction: Interaction, center: Point, last_position: Option, scale: f32, } impl MouseState { fn map_coords(&self, x: f32, y: f32) -> Point { Point::new( (self.center.x + x) * self.scale, (self.center.y - y) * self.scale, ) } } impl Default for MouseState { fn default() -> Self { Self { interaction: Interaction::default(), center: Point::default(), last_position: None, scale: 50.0 } } } impl canvas::Program for Graph { type State = MouseState; 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 = ((state.center.y - frame.height() / 2.0) / state.scale) as i64 * state.scale as i64; let start_x = ((state.center.x - frame.width() / 2.0) / state.scale) as i64 * state.scale as i64; let end_y = ((state.center.y + frame.height() / 2.0) / state.scale) as i64 * state.scale as i64; let end_x = ((state.center.x + frame.width() / 2.0) / state.scale) as i64 * state.scale as i64; for y in (start_y..end_y + state.scale as i64).step_by(1) { let line = Path::line( state.map_coords(start_x as f32, y as f32), state.map_coords(end_x as f32, y as f32), ); frame.stroke(&line, unitline_whole.clone()); } for x in (start_x..end_x + state.scale as i64).step_by(1) { let line = Path::line( state.map_coords(x as f32, start_y as f32), state.map_coords(x as f32, end_y as f32), ); frame.stroke(&line, unitline_whole.clone()); } let start_y_100 = ((state.center.y - frame.height() / 2.0) / state.scale * 10.0) as i64 * 10 * state.scale as i64; let start_x_100 = ((state.center.x - frame.width() / 2.0) / state.scale * 10.0) as i64 * 10 * state.scale as i64; for y in (start_y_100..end_y).step_by(10) { let line = Path::line( state.map_coords(start_x as f32, y as f32), state.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( state.map_coords(x as f32, start_y as f32), state.map_coords(x as f32, end_y as f32), ); frame.stroke(&line, unitline.clone()); } let line = Path::line( state.map_coords(0.0, start_y as f32), state.map_coords(0.0, end_y as f32), ); frame.stroke(&line, zeroline.clone()); let line = Path::line( state.map_coords(start_x as f32, 0.0), state.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(state.map_coords(x1, y1), state.map_coords(x2, y2)); frame.stroke(&line, graphline.clone()) } } } }); }); vec![graph] } fn update( &self, state: &mut Self::State, event: canvas::Event, _bounds: Rectangle, cursor: iced::advanced::mouse::Cursor, ) -> (canvas::event::Status, Option) { if let canvas::Event::Mouse(mouse::Event::ButtonPressed(_)) = event { state.interaction = Interaction::Grabbing; state.last_position = cursor.position(); return (event::Status::Captured, Some(Message::UpdateScreen)); } 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.scale; state.center.y += (position.y - lp.y) / state.scale; } state.last_position = Some(position); return (event::Status::Captured, Some(Message::UpdateScreen)); } } if let canvas::Event::Mouse(mouse::Event::ButtonReleased(_)) = event { state.interaction = Interaction::Pointer; } (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 } } } }