aboutsummaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/graph.rs237
-rw-r--r--src/ui/mod.rs8
2 files changed, 245 insertions, 0 deletions
diff --git a/src/ui/graph.rs b/src/ui/graph.rs
new file mode 100644
index 0000000..2f3855d
--- /dev/null
+++ b/src/ui/graph.rs
@@ -0,0 +1,237 @@
+use std::sync::Mutex;
+
+use super::Message;
+use iced::{
+ event,
+ mouse::{self, Interaction},
+ widget::canvas::{self, stroke, Cache, Frame, Geometry, LineCap, Path, Stroke},
+ 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,
+ pub func: Mutex<FunctionCache>,
+ pub ctx: Context,
+}
+
+impl GraphCanvas {
+ pub fn new(fstr: &String) -> Self {
+ let ctx = Context::commonsense();
+ let func = ExpressionFunction::from_string(fstr.clone(), ctx.operations());
+ Self {
+ graph: Cache::default(),
+ func: Mutex::new(FunctionCache::new(func)),
+ ctx,
+ }
+ }
+
+ pub fn clear(&self) {
+ self.graph.clear();
+ }
+}
+
+#[derive(Copy, Clone)]
+pub struct GraphState {
+ interaction: Interaction,
+ center: Point,
+ last_position: Option<Point>,
+ scale: f32,
+}
+
+impl GraphState {
+ fn view_rectangle(&self, frame: &Frame) -> Rectangle {
+ let s = Size::new(frame.width() / self.scale + 2.0, frame.height() / self.scale + 2.0);
+ let p = Point::new(-self.center.x / self.scale - s.width / 2.0, -self.center.y / self.scale - s.height / 2.0);
+ Rectangle::new(p, s)
+ }
+
+ 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<Message, Renderer> for GraphCanvas {
+ type State = GraphState;
+
+ fn draw(
+ &self,
+ state: &Self::State,
+ renderer: &Renderer,
+ _theme: &Theme,
+ bounds: Rectangle,
+ _cursor: mouse::Cursor,
+ ) -> Vec<Geometry> {
+ let graph = self.graph.draw(renderer, bounds.size(), |frame| {
+ let unitline_whole = Stroke {
+ width: 1.0,
+ style: stroke::Style::Solid(Color::new(0.5, 0.5, 0.5, 1.0)),
+ line_cap: LineCap::Round,
+ ..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::Round,
+ ..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::Round,
+ ..Stroke::default()
+ };
+
+ frame.translate(Vector {
+ x: frame.center().x + state.center.x,
+ y: frame.center().y + state.center.y,
+ });
+ frame.scale(state.scale);
+
+ let rect = state.view_rectangle(frame);
+ let x_end = (rect.x + rect.width) as i64;
+ let y_end = (rect.y + rect.height) as i64;
+
+ for y in (rect.y as i64..y_end).step_by(1) {
+ 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(1) {
+ 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| {
+ frame.translate(Vector { x: 1.0, y: 0.0 });
+ frame.fill_text("1");
+ });
+
+ let mut y1 = self
+ .func
+ .lock()
+ .unwrap()
+ .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 {
+ for d in 0..10 {
+ let d = d as f32 / 10.0;
+ let x1 = x as f32 + d;
+ let x2 = x as f32 + d + 0.1;
+ let y2 = self
+ .func
+ .lock()
+ .unwrap()
+ .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<Message>) {
+ 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::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.center.y += position.y - lp.y;
+ }
+ 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;
+ }
+
+ 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::UpdateScreen));
+ }
+ _ => {}
+ }
+ }
+ (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,
+ }
+ }
+}
diff --git a/src/ui/mod.rs b/src/ui/mod.rs
new file mode 100644
index 0000000..1e7fffe
--- /dev/null
+++ b/src/ui/mod.rs
@@ -0,0 +1,8 @@
+pub mod graph;
+
+#[derive(Debug, Clone)]
+pub enum Message {
+ InputChanged(String),
+ UpdateFunction,
+ UpdateScreen,
+}