rt_graph/
graph_with_controls.rs

1use crate::{Config, Graph, View, ViewMode};
2use gdk::prelude::*;
3use gtk::prelude::*;
4use std::{rc::Rc, cell::RefCell};
5
6/// A GTK widget that contains a graph and controls to navigate it.
7///
8/// If you want a customised graph with your own controls, you might
9/// want to try using `Graph`, which is designed for customisation.
10pub struct GraphWithControls {
11    s: Rc<State>,
12}
13
14struct State {
15    controls_box: gtk::Box,
16
17    scrollbar: gtk::Scrollbar,
18    btn_zoom_x_out: gtk::Button,
19    btn_zoom_x_in: gtk::Button,
20    btn_follow: gtk::Button,
21
22    graph: RefCell<Graph>,
23}
24
25impl GraphWithControls {
26    /// Build and show a `GraphWithControls` widget in the target `gtk::Container`.
27    pub fn build_ui<C>(config: Config, container: &C, gdk_window: &gdk::Window
28    ) -> GraphWithControls
29        where C: IsA<gtk::Container> + IsA<gtk::Widget>
30    {
31        // Create the controls
32
33        let controls_box = gtk::BoxBuilder::new()
34            .orientation(gtk::Orientation::Vertical)
35            .spacing(0)
36            .build();
37        container.add(&controls_box);
38
39        let graph = Graph::build_ui(config, &controls_box, gdk_window);
40
41        let scrollbar = gtk::ScrollbarBuilder::new()
42            .orientation(gtk::Orientation::Horizontal)
43            .halign(gtk::Align::Start)
44            .build();
45        scrollbar.set_property_width_request(graph.width() as i32);
46        controls_box.add(&scrollbar);
47
48        let buttons_box = gtk::BoxBuilder::new()
49            .orientation(gtk::Orientation::Horizontal)
50            .height_request(35)
51            .build();
52        controls_box.add(&buttons_box);
53
54        let btn_follow = gtk::ButtonBuilder::new()
55            .label("Follow")
56            .build();
57        buttons_box.add(&btn_follow);
58
59        let btn_zoom_x_in = gtk::ButtonBuilder::new()
60            .label("Zoom X in")
61            .build();
62        buttons_box.add(&btn_zoom_x_in);
63
64        let btn_zoom_x_out = gtk::ButtonBuilder::new()
65            .label("Zoom X out")
66            .sensitive(false)
67            .build();
68        buttons_box.add(&btn_zoom_x_out);
69
70        // Set up the state
71
72        let s = Rc::new(State {
73            controls_box: controls_box.clone(),
74
75            scrollbar: scrollbar.clone(),
76            btn_zoom_x_out: btn_zoom_x_out.clone(),
77            btn_zoom_x_in: btn_zoom_x_in.clone(),
78            btn_follow: btn_follow.clone(),
79
80            graph: RefCell::new(graph),
81        });
82        let g = GraphWithControls {
83            s: s.clone(),
84        };
85
86        update_controls(&g, &g.s.graph.borrow().view());
87
88        // Event handlers that require state.
89
90        let gc = g.clone();
91        scrollbar.connect_change_value(move |_ctrl, _scroll_type, v| {
92            gc.s.graph.borrow().scroll(v);
93            Inhibit(false)
94        });
95
96        let gc = g.clone();
97        btn_follow.connect_clicked(move |_btn| {
98            gc.s.graph.borrow().set_follow()
99        });
100
101        let gc = g.clone();
102        btn_zoom_x_in.connect_clicked(move |_btn| {
103            let new = gc.s.graph.borrow().view().zoom_x / 2.0;
104            gc.s.graph.borrow().set_zoom_x(new);
105        });
106
107        let gc = g.clone();
108        btn_zoom_x_out.connect_clicked(move |_btn| {
109            let new = gc.s.graph.borrow().view().zoom_x * 2.0;
110            gc.s.graph.borrow().set_zoom_x(new);
111        });
112
113        {
114            // Scope the borrow on view_observable.
115            let gc = g.clone();
116            s.graph.borrow_mut().view_observable().connect(move |view| {
117                update_controls(&gc, &view);
118            });
119        }
120
121        let gc = g.clone();
122        g.s.graph.borrow().drawing_area().add_events(gdk::EventMask::BUTTON_PRESS_MASK);
123        g.s.graph.borrow().drawing_area().connect_button_press_event(move |_ctrl, ev| {
124            drawing_area_button_press(&gc, ev)
125        });
126
127        // Show everything recursively
128        controls_box.show_all();
129
130        g
131    }
132
133    fn clone(&self) -> GraphWithControls {
134        GraphWithControls {
135            s: self.s.clone()
136        }
137    }
138
139    /// Show the graph and controls.
140    pub fn show(&self) {
141        self.s.controls_box.show();
142        self.s.graph.borrow().show();
143    }
144
145    /// Hide the graph and controls.
146    pub fn hide(&self) {
147        self.s.controls_box.hide();
148        self.s.graph.borrow().hide();
149    }
150}
151
152/// Update the controls (GTK widgets) from the current state.
153fn update_controls(g: &GraphWithControls, view: &View) {
154    trace!("update_controls view={:?}", view);
155    let s = &g.s;
156    let adj = s.scrollbar.get_adjustment();
157    let window_width_t = (s.graph.borrow().width() as f64) * view.zoom_x;
158
159    adj.set_upper(s.graph.borrow().last_t() as f64);
160    adj.set_lower(s.graph.borrow().first_t() as f64);
161    adj.set_step_increment(window_width_t / 4.0);
162    adj.set_page_increment(window_width_t / 2.0);
163    adj.set_page_size(window_width_t);
164
165    match view.mode {
166        ViewMode::Following =>
167            adj.set_value(s.graph.borrow().last_t() as f64),
168        ViewMode::Scrolled => adj.set_value(view.last_drawn_t as f64 -
169                                            ((s.graph.borrow().width() as f64) * view.zoom_x)),
170    }
171
172    s.btn_zoom_x_in.set_sensitive(view.zoom_x > s.graph.borrow().max_zoom_x());
173    s.btn_zoom_x_out.set_sensitive(view.zoom_x < s.graph.borrow().base_zoom_x());
174    s.btn_follow.set_sensitive(view.mode == ViewMode::Scrolled);
175}
176
177fn drawing_area_button_press(g: &GraphWithControls, ev: &gdk::EventButton) -> Inhibit {
178    let pos = ev.get_position();
179    let pt = g.s.graph.borrow().drawing_area_pos_to_point(pos.0, pos.1);
180    debug!("drawing_area button_press pos={:?} pt={:?}", pos, pt);
181
182    if let Some(pta) = pt {
183        let info_bar = gtk::InfoBarBuilder::new()
184            .halign(gtk::Align::Start)
185            .build();
186        g.s.controls_box.add(&info_bar);
187        info_bar.set_property_width_request(g.s.graph.borrow().width() as i32);
188
189        info_bar.get_content_area().add(&gtk::Label::new(Some("Time, [Values]:")));
190
191        let entry = gtk::EntryBuilder::new()
192            .text(&*format!("{}, {:?}", pta.t, pta.vals()))
193            .editable(false)
194            .hexpand(true)
195            .build();
196        info_bar.get_content_area().add(&entry);
197
198        let close_btn = gtk::ButtonBuilder::new()
199            .label("Close")
200            .build();
201        info_bar.get_action_area().unwrap().add(&close_btn);
202
203        let ibc = info_bar.clone();
204        let cbc = g.s.controls_box.clone();
205        close_btn.connect_clicked(move |_btn| {
206            cbc.remove(&ibc);
207        });
208
209        info_bar.show_all();
210    }
211
212    Inhibit(false)
213}