React to Clicks
The code of this stage can be found in branch 3-click.
In this stage we will address two annoyances. First of all, moving around the focus by typing in values in the entries cx and cy is cumbersome. We alter the drawing area such that if you left click on a spot, that will become the center of the picture.
#![allow(unused)] fn main() { let gesture = gtk::GestureClick::new(); gesture.set_button(GDK_BUTTON_PRIMARY as u32); gesture.connect_pressed(clone!(@strong state => move |gesture, _, wx, wy| on_clicked(&state, gesture, wx, wy, &cx_value, &cy_value))); canvas.add_controller(gesture); }
We create a GestureClick object, that reacts to the simple click gesture. Gestures can be much more complicated than that, but this will do for our purpose. We state that it must only react to the primary button (left click, usually).
Next, we connect a signal handler with the press event.
Finally, we add the gesture as an event controller to the canvas widget.
#![allow(unused)] fn main() { fn on_clicked( state: &Rc<RefCell<State>>, gesture: &GestureClick, wx: f64, wy: f64, cx_value: >k::Entry, cy_value: >k::Entry, ) { gesture.set_state(gtk::EventSequenceState::Claimed); let (new_cx, new_cy) = state.borrow().win_to_mandel(wx, wy); cx_value.set_text(&new_cx.to_string()); cy_value.set_text(&new_cy.to_string()); } }
In the handler, we claim the event, which means that it won't be handled by other handlers. We convert the window coordinates to coordinates in the mandelbrot space and set the text in Entry cx_value and Entry cy_value. The latter will emit their changed signals, and the drawing area is redrawn because of that.
#![allow(unused)] fn main() { impl State { // ... fn win_to_mandel(&self, wx: f64, wy: f64) -> (f64, f64) { WinToMandel::from_mapping(&self.mapping).cvt(wx as usize, wy as usize) } } }
We add a convenience method to the state in order to convert a drawing area coordinate pair to a mandelbrot space coordinate pair. This functionality already existed in the file mandel_image.rs, where it was needed to compute a mandelvalue for a pixel.
Arguably it would be nicer to drag the picture instead of centering it on a click point. You could implement that by using GestureDrag. Because computing the picture takes a long time, you would probably not want to do that during dragging. You could change the mandel_draw function and state in order to show the movement, but it is getting a bit complicated this way.
Color of max depth
The second annoyance is that if we increase or decrease the iteration depth by one (or any odd number), the picture changes a lot. This is because the whole area where the number of iterations is bigger than the maximum changes color. In this area, it is unknown if the number of iterations is odd, even or infinite. We will use a grey value for those pixels.
We make the changes in file mandel_image.rs.
#![allow(unused)] fn main() { fn color_from_mandel(mv: u32, max: u32) -> u32 { if mv == max { 0x808080 } else { if mv % 2 == 0 { 0 } else { 0xffffff } } } }
The function color_from_mandel now takes an extra parameter, the maximum value of mv. We check for this value first and return a grey value if it is equal.
#![allow(unused)] fn main() { fn fill_mandel_image(data: &mut [u8], ustride: usize, mparams: &Mapping) { // ... let mv = mandel_value(x, y, max); let color = color_from_mandel(mv, max); let bytes = color.to_ne_bytes(); //... } }
At the call site in function fill_mandel_image we have to supply the extra parameter.