save dialog, n=tabs y=spaces

This commit is contained in:
Dane Johnson 2022-05-09 22:11:14 -05:00
parent 738ccb53cf
commit 0291a6e941
3 changed files with 142 additions and 140 deletions

View File

@ -5,7 +5,7 @@ use image::DynamicImage;
use rfd::FileDialog; use rfd::FileDialog;
use board_builder::{ Board, CoordTransformer, read_board_from_file }; use board_builder::{ Board, CoordTransformer, read_board_from_file, write_board_to_file };
use std::path::Path; use std::path::Path;
@ -23,104 +23,106 @@ struct BoardBuilderApp {
impl eframe::App for BoardBuilderApp { impl eframe::App for BoardBuilderApp {
fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) { fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) {
TopBottomPanel::top("menubar").show(ctx, |ui| { TopBottomPanel::top("menubar").show(ctx, |ui| {
menu::bar(ui, |ui| { menu::bar(ui, |ui| {
fn choose_file() { fn choose_file() {
FileDialog::new().pick_file(); FileDialog::new().pick_file();
} }
ui.menu_button("File", |ui| { ui.menu_button("File", |ui| {
ui.button("New"); ui.button("New");
if ui.button("Open...").clicked() { if ui.button("Open...").clicked() {
if let Some(board_file) = FileDialog::new().pick_file() { if let Some(board_file) = FileDialog::new().pick_file() {
match read_board_from_file(&board_file) { match read_board_from_file(&board_file) {
Ok((board, image)) => { Ok((board, image)) => {
self.board = board; self.board = board;
match image { match image {
None => { self.image = None; self.texture = None } None => { self.image = None; self.texture = None }
Some(image) => self.load_image(ctx, image), Some(image) => self.load_image(ctx, image),
} }
} }
Err(_) => panic!("Could not open file!"), Err(_) => panic!("Could not open file!"),
} }
} }
} }
if ui.button("Save As...").clicked() { if ui.button("Save As...").clicked() {
choose_file(); if let Some(board_file) = FileDialog::new().save_file() {
} write_board_to_file(&self.board, self.image.as_ref(), &board_file);
if ui.button("Open Image...").clicked() { }
let image_file = FileDialog::new() }
.add_filter("Image", &["png", "jpg", "jpeg", "gif", "webp", "bmp", "tiff"]) if ui.button("Open Image...").clicked() {
.pick_file(); let image_file = FileDialog::new()
if let Some(image_file) = image_file { .add_filter("Image", &["png", "jpg", "jpeg", "gif", "webp", "bmp", "tiff"])
self.load_image_file(ctx, &image_file); .pick_file();
} if let Some(image_file) = image_file {
} self.load_image_file(ctx, &image_file);
}); }
ui.menu_button("Edit", |ui| { }
ui.button("Edit Nodes"); });
ui.button("Edit Edges"); ui.menu_button("Edit", |ui| {
ui.button("Edit Labels..."); ui.button("Edit Nodes");
}) ui.button("Edit Edges");
}); ui.button("Edit Labels...");
}); })
CentralPanel::default().show(ctx, |ui| { });
if let Some(texture) = self.texture.as_ref() { });
let size = ui.available_size(); CentralPanel::default().show(ctx, |ui| {
let (response, painter) = ui.allocate_painter(size, Sense::click()); if let Some(texture) = self.texture.as_ref() {
let image = widgets::Image::new(texture, size); let size = ui.available_size();
image.paint_at(ui, response.rect); let (response, painter) = ui.allocate_painter(size, Sense::click());
let view = View(response.rect); let image = widgets::Image::new(texture, size);
self.draw_board(&painter, view); image.paint_at(ui, response.rect);
} let view = View(response.rect);
}); self.draw_board(&painter, view);
}
});
} }
} }
impl BoardBuilderApp { impl BoardBuilderApp {
fn new(cc: &eframe::CreationContext<'_>) -> Self { fn new(cc: &eframe::CreationContext<'_>) -> Self {
let mut style = (*cc.egui_ctx.style()).clone(); let mut style = (*cc.egui_ctx.style()).clone();
let mut button = style::TextStyle::Button.resolve(&style); let mut button = style::TextStyle::Button.resolve(&style);
button.size = 20.0; button.size = 20.0;
style.text_styles.insert(style::TextStyle::Button, button); style.text_styles.insert(style::TextStyle::Button, button);
cc.egui_ctx.set_style(style); cc.egui_ctx.set_style(style);
BoardBuilderApp::default() BoardBuilderApp::default()
} }
fn load_image_file(&mut self, ctx: &Context, image_file: &Path) -> Result<(), image::ImageError> { fn load_image_file(&mut self, ctx: &Context, image_file: &Path) -> Result<(), image::ImageError> {
let image = image::io::Reader::open(image_file)?.decode()?; let image = image::io::Reader::open(image_file)?.decode()?;
self.load_image(ctx, image); self.load_image(ctx, image);
Ok(()) Ok(())
} }
fn load_image(&mut self, ctx: &Context, image: DynamicImage) { fn load_image(&mut self, ctx: &Context, image: DynamicImage) {
let egui_image = egui::ColorImage::from_rgba_unmultiplied( let egui_image = egui::ColorImage::from_rgba_unmultiplied(
[image.width() as _, image.height() as _], [image.width() as _, image.height() as _],
image.to_rgba8().as_flat_samples().as_slice(), image.to_rgba8().as_flat_samples().as_slice(),
); );
self.image = Some(image); self.image = Some(image);
self.texture = Some(ctx.load_texture("board-image", egui_image)); self.texture = Some(ctx.load_texture("board-image", egui_image));
} }
fn draw_board(&self, painter: &Painter, view: View) { fn draw_board(&self, painter: &Painter, view: View) {
for node in self.board.nodes.values() { for node in self.board.nodes.values() {
painter.text( painter.text(
view.from_coords(node.x, node.y), view.from_coords(node.x, node.y),
Align2::CENTER_CENTER, Align2::CENTER_CENTER,
&node.name, &node.name,
FontId::proportional(16.0), FontId::proportional(16.0),
Color32::BLACK, Color32::BLACK,
); );
let stroke = Stroke { width: 1.0, color: Color32::BLACK }; let stroke = Stroke { width: 1.0, color: Color32::BLACK };
for edge in &node.edges { for edge in &node.edges {
let other_node = &self.board.nodes[edge]; let other_node = &self.board.nodes[edge];
painter.line_segment( painter.line_segment(
[view.from_coords(node.x, node.y), view.from_coords(other_node.x, other_node.y)], [view.from_coords(node.x, node.y), view.from_coords(other_node.x, other_node.y)],
stroke, stroke,
); );
} }
} }
} }
} }

BIN
board.zip

Binary file not shown.

View File

@ -19,56 +19,56 @@ pub struct Board {
} }
impl Board { impl Board {
pub fn new() -> Self { pub fn new() -> Self {
let nodes = HashMap::new(); let nodes = HashMap::new();
let labels = HashMap::new(); let labels = HashMap::new();
Board { nodes, labels } Board { nodes, labels }
} }
pub fn add_node(&mut self, x: f32, y: f32, name: String) { pub fn add_node(&mut self, x: f32, y: f32, name: String) {
self.nodes.insert(self.next_id(), Node { self.nodes.insert(self.next_id(), Node {
x, x,
y, y,
name, name,
edges: HashSet::new(), edges: HashSet::new(),
labels: HashMap::new(), labels: HashMap::new(),
}); });
} }
pub fn remove_node(&mut self, id: usize) { pub fn remove_node(&mut self, id: usize) {
// We remove this node from the graph, then drop it from each // We remove this node from the graph, then drop it from each
// other nodes edge. // other nodes edge.
self.nodes.remove(&id); self.nodes.remove(&id);
for node in self.nodes.values_mut() { for node in self.nodes.values_mut() {
node.edges.remove(&id); node.edges.remove(&id);
} }
} }
pub fn nearest_node(&self, x: f32, y: f32) -> Option<usize> { pub fn nearest_node(&self, x: f32, y: f32) -> Option<usize> {
let f = |n: &Node| dist_sq((n.x, n.y), (x, y)); let f = |n: &Node| dist_sq((n.x, n.y), (x, y));
let mut iter = self.nodes.iter(); let mut iter = self.nodes.iter();
if let Some((id, node)) = iter.next() { if let Some((id, node)) = iter.next() {
let mut min = *id; let mut min = *id;
let mut min_val = f(node); let mut min_val = f(node);
for (id, node) in iter { for (id, node) in iter {
let val = f(node); let val = f(node);
if val < min_val { if val < min_val {
min = *id; min = *id;
min_val = val; min_val = val;
} }
} }
Some(min) Some(min)
} else { } else {
None None
} }
} }
fn next_id(&self) -> usize { fn next_id(&self) -> usize {
for i in 0 .. { for i in 0 .. {
if !self.nodes.contains_key(&i) { if !self.nodes.contains_key(&i) {
return i; return i;
} }
} }
unreachable!(); unreachable!();
} }
} }
@ -92,10 +92,10 @@ pub fn write_board_to_file(board: &Board, image: Option<&DynamicImage>, path: &P
ar.start_file("graph.json", options)?; ar.start_file("graph.json", options)?;
ar.write_all(&serde_json::to_vec(board)?)?; ar.write_all(&serde_json::to_vec(board)?)?;
if let Some(image) = image { if let Some(image) = image {
ar.start_file("image.png", options)?; ar.start_file("image.png", options)?;
let data = encode_png(image); let data = encode_png(image);
ar.write_all(&data)?; ar.write_all(&data)?;
ar.flush()?; ar.flush()?;
} }
ar.finish()?; ar.finish()?;
Ok(()) Ok(())
@ -109,7 +109,7 @@ pub fn read_board_from_file(path: &Path) -> io::Result<(Board, Option<DynamicIma
let board = serde_json::from_reader(json_file)?; let board = serde_json::from_reader(json_file)?;
let image = ar.by_name("image.png").ok(); let image = ar.by_name("image.png").ok();
if image.is_none() { if image.is_none() {
return Ok((board, None)) return Ok((board, None))
} }
let mut image_file = image.unwrap(); let mut image_file = image.unwrap();
let mut buf = Vec::new(); let mut buf = Vec::new();
@ -143,17 +143,17 @@ pub trait CoordTransformer<I: Into<(f32, f32)> + From<(f32, f32)>> {
fn origin(&self) -> I; fn origin(&self) -> I;
fn extremes(&self) -> I; fn extremes(&self) -> I;
fn to_coords(&self, pos: I) -> (f32, f32) { fn to_coords(&self, pos: I) -> (f32, f32) {
let (sx, sy) = self.origin().into(); let (sx, sy) = self.origin().into();
let (ex, ey) = self.extremes().into(); let (ex, ey) = self.extremes().into();
let (x, y) = pos.into(); let (x, y) = pos.into();
( (
inv_lerp(sx, ex, x), inv_lerp(sx, ex, x),
inv_lerp(sy, ey, y), inv_lerp(sy, ey, y),
) )
} }
fn from_coords(&self, x: f32, y: f32) -> I { fn from_coords(&self, x: f32, y: f32) -> I {
let (sx, sy) = self.origin().into(); let (sx, sy) = self.origin().into();
let (ex, ey) = self.extremes().into(); let (ex, ey) = self.extremes().into();
(lerp(sx, ex, x), lerp(sy, ey, y)).into() (lerp(sx, ex, x), lerp(sy, ey, y)).into()
} }
} }