1use crate::{Color, DataSource, observable_value, Point, Result, Store, Time, Value};
2use gdk::prelude::*;
3use glib::source::Continue;
4use gtk::prelude::*;
5use std::{
6 cell::{Cell, RefCell, RefMut},
7 rc::Rc,
8 time::Instant,
9};
10
11const BYTES_PER_PIXEL: usize = 4;
12const BACKGROUND_COLOR: (f64, f64, f64) = (0.4, 0.4, 0.4);
13const DRAWN_AREA_BACKGROUND_COLOR: (f64, f64, f64) = (0.0, 0.0, 0.0);
14
15struct State {
16 backing_surface: RefCell<cairo::Surface>,
17 temp_surface: RefCell<cairo::Surface>,
18
19 store: RefCell<Store>,
20
21 drawing_area: gtk::DrawingArea,
22
23 view_write: RefCell<observable_value::WriteHalf<View>>,
24 view_read: RefCell<observable_value::ReadHalf<View>>,
25
26 fps_count: Cell<u16>,
27 fps_timer: Cell<Instant>,
28
29 config: Config,
30
31 tick_id: RefCell<TickId>,
32}
33
34enum TickId {
35 IngestOnly(glib::source::SourceId),
36 EveryFrame(gtk::TickCallbackId),
37 None,
38}
39
40#[derive(Clone, Debug)]
42pub struct View {
43 pub zoom_x: f64,
45
46 pub last_drawn_t: Time,
48
49 pub last_drawn_x: u32,
51
52 pub min_t: Time,
55
56 pub max_t: Time,
58
59 pub mode: ViewMode,
61}
62
63#[derive(Clone, Debug, Eq, PartialEq)]
65pub enum ViewMode {
66 Following,
68
69 Scrolled,
71}
72
73impl View {
74 fn default_from_config(c: &Config) -> View {
75 View {
76 zoom_x: c.base_zoom_x,
77 last_drawn_t: 0,
78 last_drawn_x: 0,
79 min_t: 0,
80 max_t: 0,
81 mode: ViewMode::Following,
82 }
83 }
84}
85
86#[derive(Builder, Debug)]
101#[builder(pattern = "owned")]
102pub struct Config {
103 #[builder(default = "1000.0")]
105 base_zoom_x: f64,
106
107 #[builder(default = "1.0")]
109 max_zoom_x: f64,
110
111 #[builder(default = "800")]
113 graph_width: u32,
114
115 #[builder(default = "200")]
117 graph_height: u32,
118
119 #[builder(private, setter(name = "data_source_internal"))]
120 data_source: RefCell<Box<dyn DataSource>>,
121
122 #[builder(default = "100")]
124 windows_to_store: u32,
125
126 #[builder(default = "PointStyle::Point")]
128 point_style: PointStyle,
129}
130
131#[derive(Clone, Copy, Debug)]
133pub enum PointStyle {
134 Point,
136
137 Cross,
139}
140
141impl ConfigBuilder {
142 pub fn data_source<T: DataSource + 'static>(self, ds: T) -> Self {
144 self.data_source_internal(RefCell::new(Box::new(ds)))
145 }
146}
147
148pub struct Graph {
154 s: Rc<State>,
155}
156
157impl Graph {
158 pub fn build_ui<C>(config: Config, container: &C, gdk_window: &gdk::Window) -> Graph
160 where C: IsA<gtk::Container> + IsA<gtk::Widget>
161 {
162
163 let drawing_area = gtk::DrawingAreaBuilder::new()
164 .height_request(config.graph_height as i32)
165 .width_request(config.graph_width as i32)
166 .build();
167 container.add(&drawing_area);
168
169 let backing_surface = create_backing_surface(gdk_window,
172 config.graph_width, config.graph_height);
173 let temp_surface = create_backing_surface(gdk_window,
174 config.graph_width, config.graph_height);
175 let store = Store::new(config.data_source.borrow().get_num_values().unwrap() as u8);
176 let view = View::default_from_config(&config);
177 let (view_read, view_write) =
178 observable_value::ObservableValue::new(view.clone()).split();
179 let s = Rc::new(State {
180 backing_surface: RefCell::new(backing_surface),
181 temp_surface: RefCell::new(temp_surface),
182
183 store: RefCell::new(store),
184
185 drawing_area: drawing_area.clone(),
186
187 view_read: RefCell::new(view_read),
188 view_write: RefCell::new(view_write),
189
190 fps_count: Cell::new(0),
191 fps_timer: Cell::new(Instant::now()),
192
193 config,
194
195 tick_id: RefCell::new(TickId::None),
196 });
197 let graph = Graph {
198 s: s.clone(),
199 };
200
201 let sc = s.clone();
203 drawing_area.connect_draw(move |ctrl, ctx| {
204 graph_draw(ctrl, ctx, &*sc)
205 });
206
207 graph.set_frame_tick();
208
209 drawing_area.show_all();
211
212 graph
213 }
214
215 fn set_frame_tick(&self) {
216 let old_tick_id = self.s.tick_id.replace(TickId::None);
218 match old_tick_id {
219 TickId::IngestOnly(id) => {
220 glib::source::source_remove(id);
221 },
222 TickId::EveryFrame(id) => {
223 self.s.tick_id.replace(TickId::EveryFrame(id));
225 return;
226 },
227 TickId::None => (),
228 }
229
230 let sc = self.s.clone();
231 let frame_tick_id = self.s.drawing_area.add_tick_callback(move |_ctrl, _clock| {
232 tick(&*sc);
233 Continue(true)
234 });
235 *self.s.tick_id.borrow_mut() = TickId::EveryFrame(frame_tick_id);
236 }
237
238 fn set_ingest_tick(&self) {
239 let old_tick_id = self.s.tick_id.replace(TickId::None);
241 match old_tick_id {
242 TickId::EveryFrame(id) => {
243 id.remove();
244 },
245 TickId::IngestOnly(id) => {
246 self.s.tick_id.replace(TickId::IngestOnly(id));
248 return;
249 }
250 TickId::None => (),
251 }
252
253 let sc = self.s.clone();
254 let ingest_tick_id =
255 glib::source::timeout_add_seconds_local(
256 1 ,
257 move || {
258 tick(&*sc);
259 Continue(true)
260 });
261 *self.s.tick_id.borrow_mut() = TickId::IngestOnly(ingest_tick_id);
262 }
263
264 pub fn show(&self) {
266 self.set_frame_tick();
267 self.s.drawing_area.show();
268 }
269
270 pub fn hide(&self) {
272 self.set_ingest_tick();
273 self.s.drawing_area.hide();
274 }
275
276 pub fn width(&self) -> u32 {
278 self.s.config.graph_width
279 }
280
281 pub fn height(&self) -> u32 {
283 self.s.config.graph_height
284 }
285
286 pub fn base_zoom_x(&self) -> f64 {
289 self.s.config.base_zoom_x
290 }
291
292 pub fn max_zoom_x(&self) -> f64 {
295 self.s.config.max_zoom_x
296 }
297
298 pub fn view(&self) -> View {
300 self.s.view_read.borrow().get()
301 }
302
303 pub fn last_t(&self) -> Time {
305 self.s.store.borrow().last_t()
306 }
307
308 pub fn first_t(&self) -> Time {
311 self.s.store.borrow().first_t()
312 }
313
314 fn _clone(&self) -> Graph {
315 Graph {
316 s: self.s.clone()
317 }
318 }
319
320 pub fn set_zoom_x(&self, new_zoom_x: f64) {
324 debug!("set_zoom_x new_zoom_x={}", new_zoom_x);
325 let new_zoom_x = new_zoom_x.min(self.s.config.base_zoom_x)
326 .max(self.s.config.max_zoom_x);
327 {
328 let new_view = View {
330 zoom_x: new_zoom_x,
331 .. self.s.view_read.borrow().get()
332 };
333 self.s.view_write.borrow_mut().set(&new_view);
334 }
335
336 redraw_graph(&*self.s);
337 }
338
339 pub fn set_follow(&self) {
341 debug!("set_follow");
342 {
343 let new_view = View {
345 mode: ViewMode::Following,
346 last_drawn_t: self.s.store.borrow().last_t(),
347 .. self.s.view_read.borrow().get()
348 };
349 self.s.view_write.borrow_mut().set(&new_view);
350 }
351 redraw_graph(&*self.s);
352 }
353
354 pub fn scroll(&self, new_val: f64) {
356 debug!("scroll new_val={}", new_val);
357 {
358 let mut view = self.s.view_read.borrow().get();
360 view.mode = ViewMode::Scrolled;
361 let new_t = (new_val as u32 +
362 ((view.zoom_x * self.s.config.graph_width as f64) as u32))
363 .min(self.s.store.borrow().last_t());
364 let new_t = (((new_t as f64) / view.zoom_x).floor() * view.zoom_x) as u32;
366 view.last_drawn_t = new_t;
367 view.last_drawn_x = 0;
368 self.s.view_write.borrow_mut().set(&view);
369 debug!("scroll_change, v={:?} view={:?}", new_val, view);
370 }
371 redraw_graph(&self.s);
373 }
374
375 pub fn view_observable(&mut self) -> RefMut<observable_value::ReadHalf<View>> {
378 self.s.view_read.borrow_mut()
379 }
380
381 pub fn drawing_area(&self) -> gtk::DrawingArea {
384 self.s.drawing_area.clone()
385 }
386
387 pub fn drawing_area_pos_to_point(&self, x: f64, _y: f64) -> Option<Point> {
394 let view = self.s.view_read.borrow().get();
395 let t = (view.last_drawn_t as i64 +
396 ((x - (view.last_drawn_x as f64)) * view.zoom_x) as i64)
397 .max(0).min(view.last_drawn_t as i64)
398 as u32;
399 let pt = self.s.store.borrow().query_point(t).unwrap()?;
400
401 let pt: Option<Point> = if (pt.t - t) >= (view.zoom_x * 10.0) as u32 {
404 None
405 } else {
406 Some(pt)
407 };
408
409 pt
410 }
411}
412
413fn graph_draw(_ctrl: >k::DrawingArea, ctx: &cairo::Context, s: &State) -> Inhibit {
415 trace!("graph_draw");
416
417 ctx.rectangle(0.0, 0.0, s.config.graph_width as f64, s.config.graph_height as f64);
419 ctx.set_source_surface(&s.backing_surface.borrow(),
420 0.0 , 0.0 );
421 ctx.fill();
422
423 s.fps_count.set(s.fps_count.get() + 1);
425 let now = Instant::now();
426 if (now - s.fps_timer.get()).as_secs() >= 1 {
427 debug!("fps: {}", s.fps_count.get());
428 s.fps_count.set(0);
429 s.fps_timer.set(now);
430 }
431
432 Inhibit(false)
433}
434
435fn redraw_graph(s: &State) {
437 trace!("redraw_graph");
438 let backing_surface = s.backing_surface.borrow();
439 {
440 let c = cairo::Context::new(&*backing_surface);
442 c.set_source_rgb(BACKGROUND_COLOR.0,
443 BACKGROUND_COLOR.1,
444 BACKGROUND_COLOR.2);
445 c.rectangle(0.0, 0.0, s.config.graph_width as f64, s.config.graph_height as f64);
446 c.fill();
447 }
448
449 let mut view = s.view_read.borrow().get();
450 let cols = s.config.data_source.borrow().get_colors().unwrap();
451 let t1: u32 = view.last_drawn_t;
452 let t0: u32 = (t1 as i64 - (s.config.graph_width as f64 * view.zoom_x) as i64).max(0) as u32;
453 let patch_dims = ((((t1-t0) as f64 / view.zoom_x).floor() as u32)
454 .min(s.config.graph_width) as usize,
455 s.config.graph_height as usize);
456 if patch_dims.0 > 0 {
457 let x = match view.mode {
458 ViewMode::Following => (s.config.graph_width as usize) - patch_dims.0,
459 ViewMode::Scrolled => 0,
460 };
461 render_patch(&*backing_surface,
462 &s.store.borrow(),
463 &cols,
464 patch_dims.0 , patch_dims.1 ,
465 x , 0 ,
466 t0, t1,
467 0 , std::u16::MAX ,
468 s.config.point_style);
469 view.last_drawn_x = (x + patch_dims.0) as u32;
470 view.last_drawn_t = t1;
471 s.view_write.borrow_mut().set(&view);
472 }
473 s.drawing_area.queue_draw();
474}
475
476fn tick(s: &State) {
477 trace!("tick");
478 let new_data = s.config.data_source.borrow_mut().get_data().unwrap();
480
481
482 if new_data.len() > 0 {
483 s.store.borrow_mut().ingest(&*new_data).unwrap();
484 let t_latest = s.store.borrow().last_t();
485
486 let window_base_dt = (s.config.graph_width as f64 * s.config.base_zoom_x) as u32;
488 let keep_window = s.config.windows_to_store * window_base_dt;
489 let discard_start = if t_latest >= keep_window { t_latest - keep_window } else { 0 };
490 if discard_start > 0 {
491 s.store.borrow_mut().discard(0, discard_start).unwrap();
492 }
493
494 let mut view = s.view_read.borrow().get();
495
496 view.min_t = s.store.borrow().first_t();
497 view.max_t = t_latest;
498 s.view_write.borrow_mut().set(&view);
499
500 if view.mode == ViewMode::Following ||
501 (view.mode == ViewMode::Scrolled && view.last_drawn_x < s.config.graph_width) {
502
503 let patch_dims =
509 ((((t_latest - view.last_drawn_t) as f64 / view.zoom_x)
510 .floor() as usize)
511 .min(s.config.graph_width as usize),
512 s.config.graph_height as usize);
513 if patch_dims.0 > 0 {
516 let new_t = view.last_drawn_t + (patch_dims.0 as f64 * view.zoom_x) as u32;
517
518 let patch_offset_x = match view.mode {
519 ViewMode::Following => s.config.graph_width - (patch_dims.0 as u32),
520 ViewMode::Scrolled => view.last_drawn_x,
521 };
522
523 if view.mode == ViewMode::Following {
524 let c = cairo::Context::new(&*s.temp_surface.borrow());
526 c.set_source_surface(&*s.backing_surface.borrow(),
527 -(patch_dims.0 as f64) , 0.0 );
528 c.rectangle(0.0, 0.0, patch_offset_x as f64, s.config.graph_height as f64); c.fill();
533
534 s.backing_surface.swap(&s.temp_surface);
536 }
537
538 let cols = s.config.data_source.borrow().get_colors().unwrap();
539 render_patch(&s.backing_surface.borrow(),
540 &s.store.borrow(),
541 &cols,
542 patch_dims.0 , patch_dims.1 ,
543 patch_offset_x as usize, 0 ,
544 view.last_drawn_t, new_t,
545 0 , std::u16::MAX ,
546 s.config.point_style);
547
548 view.last_drawn_t = new_t;
549 view.last_drawn_x = (patch_offset_x + patch_dims.0 as u32)
550 .min(s.config.graph_width);
551 s.view_write.borrow_mut().set(&view);
552 }
553
554 s.drawing_area.queue_draw();
556 }
557 }
558}
559
560fn render_patch(
561 surface: &cairo::Surface,
562 store: &Store, cols: &[Color],
563 pw: usize, ph: usize,
564 x: usize, y: usize,
565 t0: Time, t1: Time, v0: Value, v1: Value,
566 point_style: PointStyle,
567) {
568 trace!("render_patch: pw={}, ph={} x={} y={}", pw, ph, x, y);
569 let mut patch_bytes = vec![0u8; pw * ph * BYTES_PER_PIXEL];
570 render_patch_to_bytes(store, cols, &mut patch_bytes,
571 pw, ph,
572 t0, t1,
573 v0, v1,
574 point_func_select(point_style)
575 ).unwrap();
576 copy_patch(surface, patch_bytes,
577 pw, ph,
578 x, y);
579}
580
581fn point_func_select(s: PointStyle) -> &'static dyn Fn(usize, usize, usize, usize, &mut [u8], Color) {
582 match s {
583 PointStyle::Point => &point_func_point,
584 PointStyle::Cross => &point_func_cross,
585 }
586}
587
588fn point_func_point(x: usize, y: usize, pbw: usize, pbh: usize, pb: &mut [u8], col: Color) {
589 if x < pbw && y < pbh {
590 let i = BYTES_PER_PIXEL * (pbw * y + x);
591 pb[i+2] = col.0; pb[i+1] = col.1; pb[i+0] = col.2; pb[i+3] = 255; }
596}
597
598fn point_func_cross(x: usize, y: usize, pbw: usize, pbh: usize, pb: &mut [u8], col: Color) {
599 let mut pixel = |px: usize, py: usize| {
600 if px < pbw && py < pbh {
601 let i = BYTES_PER_PIXEL * (pbw * py + px);
602 pb[i+2] = col.0; pb[i+1] = col.1; pb[i+0] = col.2; pb[i+3] = 255; }
607 };
608
609 pixel(x+1, y+1);
610 if y >= 1 {
611 pixel(x+1, y-1);
612 }
613 pixel(x , y );
614 if x >= 1 {
615 if y >= 1 {
616 pixel(x-1, y-1);
617 }
618 pixel(x-1, y+1);
619 }
620}
621
622fn render_patch_to_bytes(
623 store: &Store, cols: &[Color],
624 pb: &mut [u8], pbw: usize, pbh: usize,
625 t0: Time, t1: Time, v0: Value, v1: Value,
626 point_func: &dyn Fn(usize, usize, usize, usize, &mut [u8], Color),
627) -> Result<()>
628{
629 trace!("render_patch_to_bytes: pbw={}", pbw);
630 assert!(pbw >= 1);
631
632 let points = store.query_range(t0, t1)?;
633 for p in points {
634 assert!(p.t >= t0 && p.t <= t1);
635
636 let x = (((p.t-t0) as f32 / (t1-t0) as f32) * pbw as f32) as usize;
637 if !(x < pbw) {
638 panic!("x < pbw: x={} pbw={}", x, pbw);
640 }
641
642 for ch in 0..store.val_len() {
643 let col = cols[ch as usize % cols.len()];
644 let y = (((p.vals()[ch as usize]-v0) as f32 / (v1-v0) as f32) * pbh as f32) as usize;
645 if y >= pbh {
646 continue;
648 }
649 let y = pbh - y;
651
652 point_func(x, y, pbw, pbh, pb, col);
653 }
654 }
655
656 Ok(())
657}
658
659fn copy_patch(
660 backing_surface: &cairo::Surface,
661 bytes: Vec<u8>,
662 w: usize, h: usize,
663 x: usize, y: usize
664) {
665
666 trace!("copy_patch w={} x={}", w, x);
667
668 let patch_surface = cairo::ImageSurface::create_for_data(
670 bytes,
671 cairo::Format::ARgb32,
672 w as i32,
673 h as i32,
674 (w * BYTES_PER_PIXEL) as i32 ).unwrap();
676
677 let c = cairo::Context::new(&backing_surface);
679 c.rectangle(x as f64,
681 y as f64,
682 w as f64, h as f64 );
684 c.set_source_rgb(DRAWN_AREA_BACKGROUND_COLOR.0,
685 DRAWN_AREA_BACKGROUND_COLOR.1,
686 DRAWN_AREA_BACKGROUND_COLOR.2);
687 c.fill_preserve();
688 c.set_source_surface(&patch_surface,
690 x as f64,
691 y as f64);
692 c.fill();
693}
694
695fn create_backing_surface(win: &gdk::Window, w: u32, h: u32) -> cairo::Surface {
696 let surface =
697 win.create_similar_image_surface(
698 cairo::Format::Rgb24.into(),
699 w as i32 ,
700 h as i32 ,
701 1 ).unwrap();
702 {
703 let c = cairo::Context::new(&surface);
705 c.set_source_rgb(BACKGROUND_COLOR.0,
706 BACKGROUND_COLOR.1,
707 BACKGROUND_COLOR.2);
708 c.rectangle(0.0, 0.0, w as f64, h as f64);
709 c.fill();
710 }
711 surface
712}