use boostvoronoi::prelude::*;
use fltk::{app, button, draw, enums, frame, group, menu, prelude::*, window};
use fltk_gui::visualizer::VoronoiVisualizer;
use num_traits::AsPrimitive;
use std::cell::{RefCell, RefMut};
use std::rc::Rc;
#[macro_use]
extern crate bitflags;
const FH: i32 = 790;
const FW: i32 = 790;
const WH: i32 = 800;
const WW: i32 = FW + 180;
const COORD_X_OFFSET: i32 = 5;
const COORD_Y_OFFSET: i32 = 5;
bitflags! {
pub struct ColorFlag: ColorType {
const EXTERNAL = 0b00000001;
const PRIMARY = 0b00000010;
const CURVE = 0b00000100;
const CELL_SEGMENT = 0b00001000;
const CELL_POINT = 0b00010000;
const INFINITE = 0b00100000;
const SITE_VERTEX = 0b01000000;
}
}
fn cast<F: AsPrimitive<T> + Copy + 'static, T: Copy + 'static>(f: F) -> T {
f.as_()
}
bitflags! {
#[derive(Debug,Clone, Copy)]
pub struct DrawFilterFlag: u32 {
const EXTERNAL = 0b000000000000001;
const PRIMARY = 0b000000000000010;
const CURVE = 0b000000000000100;
const VERTICES= 0b000000000001000;
const EDGES= 0b000000000010000;
const SECONDARY = 0b000000000100000;
const INPUT_POINT = 0b000000001000000;
const INPUT_SEGMENT = 0b000000010000000;
const INFINITE = 0b001000000000000;
const CURVE_LINE = 0b010000000000000;
const SITE_VERTEX = 0b100000000000000;
const DRAW_ALL = 0b111111111111111;
}
}
#[derive(Debug, Clone, Copy)]
pub enum Example {
File,
Simple,
Complex,
Clean,
}
#[derive(Debug, Clone, Copy)]
pub enum GuiMessage {
Filter(DrawFilterFlag),
MenuChoice(Example),
}
type IType = i32;
struct SharedData {
draw_flag: DrawFilterFlag,
last_message: Option<GuiMessage>,
visualizer: VoronoiVisualizer<IType>,
last_click: Option<Point<IType>>,
}
#[inline(always)]
fn offscreen_event_coords() -> (i32, i32) {
let pos = app::event_coords();
(pos.0 - COORD_X_OFFSET, pos.1 - COORD_Y_OFFSET)
}
fn main() -> Result<(), BvError> {
let app = app::App::default();
let mut wind = window::Window::default()
.with_size(WW, WH)
.center_screen()
.with_label("Boost voronoi ported to Rust");
let mut frame = frame::Frame::new(COORD_X_OFFSET, COORD_Y_OFFSET, FW, FH, "");
frame.set_color(enums::Color::Black);
frame.set_frame(enums::FrameType::DownBox);
let mut pack = group::Pack::new(COORD_X_OFFSET + FW, COORD_Y_OFFSET, 170, WH, "");
pack.set_spacing(5);
let mut menu_but = menu::MenuButton::default()
.with_size(170, 25)
.with_label("Menu");
menu_but.set_frame(enums::FrameType::PlasticUpBox);
let mut input_points_button = button::RoundButton::default()
.with_size(180, 25)
.with_label("input points");
input_points_button.toggle(true);
input_points_button.set_frame(enums::FrameType::PlasticUpBox);
let mut input_segments_button = button::RoundButton::default()
.with_size(180, 25)
.with_label("input segments");
input_segments_button.toggle(true);
input_segments_button.set_frame(enums::FrameType::PlasticUpBox);
let mut external_button = button::RoundButton::default()
.with_size(180, 25)
.with_label("externals");
external_button.toggle(true);
external_button.set_frame(enums::FrameType::PlasticUpBox);
let mut vertices_button = button::RoundButton::default()
.with_size(180, 25)
.with_label("vertices (all)");
vertices_button.toggle(true);
vertices_button.set_frame(enums::FrameType::PlasticUpBox);
let mut site_vertices_button = button::RoundButton::default()
.with_size(180, 25)
.with_label("site vertices");
site_vertices_button.toggle(true);
site_vertices_button.set_frame(enums::FrameType::PlasticUpBox);
let mut edges_button = button::RoundButton::default()
.with_size(180, 25)
.with_label("edges (all)");
edges_button.toggle(true);
edges_button.set_frame(enums::FrameType::PlasticUpBox);
let mut infinite_button = button::RoundButton::default()
.with_size(180, 25)
.with_label("infinite edges");
infinite_button.toggle(true);
infinite_button.set_frame(enums::FrameType::PlasticUpBox);
let mut curved_button = button::RoundButton::default()
.with_size(180, 25)
.with_label("arc edges");
curved_button.toggle(true);
curved_button.set_frame(enums::FrameType::PlasticUpBox);
let mut curved_as_lines_button = button::RoundButton::default()
.with_size(180, 25)
.with_label("arc as lines");
curved_as_lines_button.toggle(false);
curved_as_lines_button.set_frame(enums::FrameType::PlasticUpBox);
let mut primary_button = button::RoundButton::default()
.with_size(180, 25)
.with_label("primary edges");
primary_button.toggle(true);
primary_button.set_frame(enums::FrameType::PlasticUpBox);
let mut secondary_button = button::RoundButton::default()
.with_size(180, 25)
.with_label("secondary edges");
secondary_button.toggle(true);
secondary_button.set_frame(enums::FrameType::PlasticUpBox);
let mut pack_x = group::Pack::default()
.with_size(180, 25)
.below_of(&secondary_button, 25);
pack_x.set_type(group::PackType::Horizontal);
let _ = frame::Frame::default().with_size(10, 25).with_label(" x:");
let mut x_label = frame::Frame::default().with_size(160, 25).with_label("");
x_label.set_align(enums::Align::Right | enums::Align::Inside);
pack_x.end();
let mut pack_y = group::Pack::default()
.with_size(180, 25)
.below_of(&pack_x, 25);
pack_y.set_type(group::PackType::Horizontal);
let _ = frame::Frame::default().with_size(10, 25).with_label(" y:");
let mut y_label = frame::Frame::default().with_size(160, 25).with_label("");
y_label.set_align(enums::Align::Right | enums::Align::Inside);
pack_y.end();
pack.end();
wind.set_color(enums::Color::White);
wind.end();
wind.show();
let offs = draw::Offscreen::new(frame.width(), frame.height()).unwrap();
#[cfg(not(target_os = "macos"))]
{
offs.begin();
draw::set_draw_color(enums::Color::White);
draw::draw_rectf(0, 0, FW, FH);
offs.end();
}
let offs = Rc::from(RefCell::from(offs));
let offs_rc = Rc::clone(&offs);
let shared_data_rc = Rc::new(RefCell::new(SharedData {
draw_flag: DrawFilterFlag::DRAW_ALL ^ DrawFilterFlag::CURVE_LINE,
last_message: None,
visualizer: VoronoiVisualizer::default(),
last_click: None,
}));
let (sender, receiver) = app::channel::<GuiMessage>();
menu_but.add_emit(
"From file",
enums::Shortcut::None,
menu::MenuFlag::Normal,
sender,
GuiMessage::MenuChoice(Example::File),
);
menu_but.add_emit(
"Simple",
enums::Shortcut::None,
menu::MenuFlag::Normal,
sender,
GuiMessage::MenuChoice(Example::Simple),
);
menu_but.add_emit(
"Complex",
enums::Shortcut::None,
menu::MenuFlag::Normal,
sender,
GuiMessage::MenuChoice(Example::Complex),
);
menu_but.add_emit(
"Clean",
enums::Shortcut::None,
menu::MenuFlag::Normal,
sender,
GuiMessage::MenuChoice(Example::Clean),
);
external_button.emit(sender, GuiMessage::Filter(DrawFilterFlag::EXTERNAL));
infinite_button.emit(sender, GuiMessage::Filter(DrawFilterFlag::INFINITE));
primary_button.emit(sender, GuiMessage::Filter(DrawFilterFlag::PRIMARY));
secondary_button.emit(sender, GuiMessage::Filter(DrawFilterFlag::SECONDARY));
input_points_button.emit(sender, GuiMessage::Filter(DrawFilterFlag::INPUT_POINT));
input_segments_button.emit(sender, GuiMessage::Filter(DrawFilterFlag::INPUT_SEGMENT));
curved_button.emit(sender, GuiMessage::Filter(DrawFilterFlag::CURVE));
vertices_button.emit(sender, GuiMessage::Filter(DrawFilterFlag::VERTICES));
site_vertices_button.emit(sender, GuiMessage::Filter(DrawFilterFlag::SITE_VERTEX));
edges_button.emit(sender, GuiMessage::Filter(DrawFilterFlag::EDGES));
curved_as_lines_button.emit(sender, GuiMessage::Filter(DrawFilterFlag::CURVE_LINE));
{
let cl = Rc::clone(&shared_data_rc);
let mut shared_data_bm = cl.borrow_mut();
shared_data_bm.visualizer.read_data(Example::Complex);
let _ = shared_data_bm.visualizer.build()?;
shared_data_bm.visualizer.re_calculate_affine()?;
}
let shared_data_c = Rc::clone(&shared_data_rc);
wind.draw(move |_| {
if let Ok(data_b) = shared_data_c.try_borrow() {
offs_rc.borrow_mut().begin();
draw::set_draw_color(enums::Color::White);
draw::draw_rectf(0, 0, FW, FH);
let _ = data_b.visualizer.draw(&data_b);
offs_rc.borrow_mut().end();
if offs_rc.borrow().is_valid() {
offs_rc.borrow().copy(5, 5, FW, FH, 0, 0);
} else {
let data_b = shared_data_c.borrow();
offs_rc.borrow_mut().begin();
draw::set_draw_color(enums::Color::Yellow);
draw::draw_rectf(5, 5, FW, FH);
let _ = data_b.visualizer.draw(&data_b);
offs_rc.borrow_mut().end();
}
}
});
let shared_data_c = Rc::clone(&shared_data_rc);
let mut mouse_drag: Option<(i32, i32)> = None;
wind.handle(move |_, ev| match ev {
enums::Event::MouseWheel => {
let mouse_position = offscreen_event_coords();
let mut shared_data_bm = shared_data_c.borrow_mut();
let event_dy = match app::event_dy() {
app::MouseWheel::Up => 3,
app::MouseWheel::Down => -3,
_ => 0,
};
let reverse_middle = shared_data_bm
.visualizer
.affine
.reverse_transform::<IType>(mouse_position.0 as f64, mouse_position.1 as f64);
if reverse_middle.is_err() {
println!("{:?}", reverse_middle.err().unwrap());
return false;
}
let reverse_middle = reverse_middle.unwrap();
if event_dy != 0 {
let scale_mod = 1.01_f64.powf(event_dy as f64);
shared_data_bm.visualizer.affine.scale[0] *= scale_mod;
shared_data_bm.visualizer.affine.scale[1] *= scale_mod;
}
let new_middle = shared_data_bm
.visualizer
.affine
.transform(reverse_middle[0] as f64, reverse_middle[1] as f64);
shared_data_bm.visualizer.affine.to_offset[0] +=
mouse_position.0 as f64 - new_middle[0];
shared_data_bm.visualizer.affine.to_offset[1] +=
mouse_position.1 as f64 - new_middle[1];
app::redraw();
true
}
enums::Event::Drag => {
let mouse_position = offscreen_event_coords();
if mouse_drag.is_none() {
mouse_drag = Some(mouse_position);
} else {
let md = mouse_drag.unwrap();
let mut shared_data_bm = shared_data_c.borrow_mut();
shared_data_bm.visualizer.affine.to_offset[0] += (mouse_position.0 - md.0) as f64;
shared_data_bm.visualizer.affine.to_offset[1] += (mouse_position.1 - md.1) as f64;
mouse_drag = Some(mouse_position);
app::redraw();
}
true
}
enums::Event::Released => {
let mouse_position = offscreen_event_coords();
if mouse_drag.is_some() {
mouse_drag = None;
} else if app::event_key_down(enums::Key::from_char('L'))
|| app::event_key_down(enums::Key::from_char('S'))
{
let mut shared_data_bm = shared_data_c.borrow_mut();
let point = shared_data_bm
.visualizer
.affine
.reverse_transform(mouse_position.0 as f64, mouse_position.1 as f64);
if point.is_err() {
println!("{:?}", point.err().unwrap());
return false;
}
let point = Point::from(point.unwrap());
if let Some(last_point) = shared_data_bm.last_click {
let line = Line {
start: last_point,
end: point,
};
if !shared_data_bm.visualizer.self_intersecting_check(&line) {
shared_data_bm.visualizer.segment_data_.push(line);
let _ = shared_data_bm.visualizer.build().unwrap();
if app::event_key_down(enums::Key::from_char('L')) {
shared_data_bm.last_click = None;
} else {
shared_data_bm.last_click = Some(point);
}
app::redraw();
}
} else {
shared_data_bm.last_click = Some(point);
}
} else if app::event_x() < FW {
let mut shared_data_bm = shared_data_c.borrow_mut();
{
let mouse_position = offscreen_event_coords();
let point = shared_data_bm
.visualizer
.affine
.reverse_transform(mouse_position.0 as f64, mouse_position.1 as f64);
if point.is_err() {
println!("{:?}", point.err().unwrap());
return false;
}
let point = Point::from(point.unwrap());
shared_data_bm.visualizer.point_data_.push(point);
}
println!(
"After added a point new len: {} ",
shared_data_bm.visualizer.point_data_.len()
);
let _ = shared_data_bm.visualizer.build().unwrap();
shared_data_bm.last_click = None;
app::redraw();
}
true
}
enums::Event::KeyDown => {
if app::event_key_down(enums::Key::from_char('C')) {
let mut shared_data_bm = shared_data_c.borrow_mut();
shared_data_bm.last_click = None;
shared_data_bm.visualizer.segment_data_.clear();
shared_data_bm.visualizer.point_data_.clear();
shared_data_bm.visualizer.diagram.clear();
app::redraw();
}
false
}
enums::Event::KeyUp => {
let mut shared_data_bm = shared_data_c.borrow_mut();
shared_data_bm.last_click = None;
false
}
enums::Event::Move => {
let mouse_position = offscreen_event_coords();
if mouse_position.0 < FW {
if let Ok(shared_data_b) = shared_data_c.try_borrow() {
let point = shared_data_b.visualizer.affine.reverse_transform::<IType>(
mouse_position.0 as f64,
mouse_position.1 as f64,
);
if let Ok(point) = point {
x_label.set_label(&point[0].to_string());
y_label.set_label(&point[1].to_string());
} else {
x_label.set_label("?");
y_label.set_label("?");
}
} else {
println!("fltk-gui: shared_data_c.try_borrow() failed (ignored)");
}
}
false
}
_ => false,
});
let shared_data_c = Rc::clone(&shared_data_rc);
while app.wait() {
if let Some(msg) = receiver.recv() {
let mut shared_data_bm: RefMut<_> = shared_data_c.borrow_mut();
match msg {
GuiMessage::MenuChoice(v) => {
let new_title = shared_data_bm.visualizer.read_data(v);
{
let w = &mut wind;
w.set_label(new_title.as_str());
}
let _ = shared_data_bm.visualizer.build()?;
let _ = shared_data_bm.visualizer.re_calculate_affine();
}
GuiMessage::Filter(flag) => {
shared_data_bm.draw_flag ^= flag;
}
}
shared_data_bm.last_message = Some(msg);
app::redraw();
}
}
Ok(())
}
pub(crate) mod fltk_gui {
pub(crate) mod demo_data;
pub(crate) mod visualizer;
}