use message passing instead of Arc to avoid panics

This commit is contained in:
2022-02-16 17:23:22 +01:00
parent 31e09f48eb
commit 06c34950a7
3 changed files with 261 additions and 154 deletions

View File

@@ -1,28 +1,19 @@
#[macro_use] #[macro_use]
extern crate derive_builder; extern crate derive_builder;
mod constants;
mod state; mod state;
mod ui; mod ui;
mod constants;
pub use crate::state::AnnotationImage;
pub use crate::constants::MARGIN_BOTTOM; pub use crate::constants::MARGIN_BOTTOM;
use crate::state::AnnotationZStack; pub use crate::state::AnnotationImage;
pub use crate::ui::ImageUI; pub use crate::ui::ImageUI;
use std::cell::{RefCell};
use std::fs;
use std::path::Path;
use std::sync::{Arc};
use adw::{prelude::*, Application}; use adw::{prelude::*, Application};
use constants::{TOGGLE_NEIGHBOURS_TEXT_TOGGLED, TOGGLE_NEIGHBOURS_TEXT, SCALE_STEP};
use glib::clone;
use gtk::gio::SimpleAction; use gtk::gio::SimpleAction;
use gtk::{glib, FileChooserDialog, FileChooserAction, ResponseType, FileFilter}; use gtk::glib::{MainContext, PRIORITY_DEFAULT};
use state::{State}; use state::{Message, State, UIMessage};
fn main() { fn main() {
let application = Application::builder() let application = Application::builder()
@@ -39,147 +30,100 @@ fn main() {
application.run(); application.run();
} }
fn build_ui(app: &Application) { fn build_ui(app: &Application) {
let (sender, receiver) = MainContext::channel(PRIORITY_DEFAULT);
let state = Arc::new(RefCell::new(State::new())); let mut state = State::new();
let image_ui = ImageUI::new(app, sender.clone());
////////////////// //////////////////
// MAIN CONTENT // // MAIN CONTENT //
////////////////// //////////////////
let image_ui = Arc::new(ImageUI::new(app));
//image_ui.build_ui(); //image_ui.build_ui();
image_ui.focus_scale.connect_value_changed(clone!(@strong image_ui, @strong state => move |x| { let _sender = sender.clone();
image_ui.focus_scale.connect_value_changed(move |x| {
let index = x.value() as usize; let index = x.value() as usize;
state.borrow_mut().set_image_index(Some(index)); _sender.send(Message::FocusLevelChange(index)).unwrap();
image_ui.update(&state.borrow()); });
}));
//////////////////// ////////////////////
// BOTTOM TOOLBAR // // BOTTOM TOOLBAR //
/////////////////// ///////////////////
image_ui.back_button.connect_clicked(|button| { image_ui.back_button.connect_clicked(|button| {
button.activate_action("win.back_focus", None) button
.activate_action("win.back_focus", None)
.expect("The action does not exist."); .expect("The action does not exist.");
}); });
image_ui.skip_button.connect_clicked(|button| { image_ui.skip_button.connect_clicked(|button| {
button.activate_action("win.skip_focus", None) button
.activate_action("win.skip_focus", None)
.expect("The action does not exist."); .expect("The action does not exist.");
}); });
image_ui.focus_button.connect_clicked(|button| { image_ui.focus_button.connect_clicked(|button| {
button.activate_action("win.mark_focus", None) button
.activate_action("win.mark_focus", None)
.expect("The action does not exist."); .expect("The action does not exist.");
}); });
let focus_image = image_ui.individual.clone(); let _sender = sender.clone();
image_ui.neighbour_toggle_button.connect_toggled( image_ui.neighbour_toggle_button.connect_toggled(move |x| {
clone!(@strong image_ui => move |x| match x.is_active() { _sender
true => { .send(Message::UI(UIMessage::ShowGrid(x.is_active())))
image_ui.focus_neighbours_aspect_frame.set_child(Some(image_ui.focus_neighbours_grid.as_ref())); .unwrap();
x.set_label(TOGGLE_NEIGHBOURS_TEXT_TOGGLED); });
}
false => {
image_ui.focus_neighbours_aspect_frame.set_child(Some(focus_image.as_ref()));
x.set_label(TOGGLE_NEIGHBOURS_TEXT);
}
}),
);
image_ui.open_button.connect_clicked(clone!(@strong image_ui, @strong state => move |_| { let _sender = sender.clone();
// TODO: actually open and load data image_ui.open_button.connect_clicked(move |_| {
_sender
.send(Message::UI(UIMessage::OpenFileChooser))
let file_chooser_action = FileChooserAction::Open; .unwrap();
let buttons = [("Open", ResponseType::Ok), ("Cancel", ResponseType::Cancel)]; });
let filter = FileFilter::new();
filter.add_pattern(r"*.json");
let file_chooser = Arc::new(FileChooserDialog::new(Some("Chose a data file!"), Some(image_ui.window.as_ref()), file_chooser_action, &buttons));
file_chooser.set_select_multiple(false);
file_chooser.set_filter(&filter);
file_chooser.connect_response(clone!(@strong image_ui, @weak state => move |dialog: &FileChooserDialog, response: ResponseType| {
if response == ResponseType::Ok {
let file = dialog.file().expect("Couldn't get file");
eprintln!("Open");
let filename = file.path().expect("Couldn't get file path");
let contents = fs::read_to_string(filename.clone()).expect("Something went wrong reading the file");
eprintln!("{}", contents);
let new_dataset : Vec<AnnotationZStack> = serde_json::from_str(&contents).unwrap();
let mut state = state.borrow_mut();
state.replace_foucs_stacks(new_dataset);
state.file_name = filename.clone().as_path().file_name().map(|x| x.to_str().expect("failed to convert filname to str").to_string());
state.root_path = filename.clone().as_path().parent().map(|x| x.to_str().expect("failed to convert filname to str").to_string());
match (state.root_path.clone(), state.file_name.clone()) {
(Some(root_path), Some(file_name)) => {
let path = Path::new(&root_path).join(Path::new(&file_name));
eprintln!("{:?}", path);
}
(_,_) => {
eprintln!("Path not properly set");
}
}
image_ui.update(&state);
}
dialog.close();
}));
file_chooser.show();
}));
//////////////////////// ////////////////////////
// Keyboard Shortcuts // // Keyboard Shortcuts //
//////////////////////// ////////////////////////
let _sender = sender.clone();
let action_toggle_neighbour = SimpleAction::new("toggle_neighbour", None); let action_toggle_neighbour = SimpleAction::new("toggle_neighbour", None);
action_toggle_neighbour.connect_activate(clone!(@strong image_ui => move |_, _| { action_toggle_neighbour
image_ui.neighbour_toggle_button.set_active(!image_ui.neighbour_toggle_button.is_active()); .connect_activate(move |_, _| _sender.send(Message::UI(UIMessage::ToggleGrid)).unwrap());
}));
let _sender = sender.clone();
let action_focus_scale_increment = SimpleAction::new("increment_focus_scale", None); let action_focus_scale_increment = SimpleAction::new("increment_focus_scale", None);
action_focus_scale_increment.connect_activate(clone!(@strong image_ui => move |_, _| { action_focus_scale_increment.connect_activate(move |_, _| {
image_ui.focus_scale.set_value(image_ui.focus_scale.value() + SCALE_STEP); _sender
})); .send(Message::UI(UIMessage::IncrementFocus))
.unwrap()
});
let _sender = sender.clone();
let action_focus_scale_decrement = SimpleAction::new("decrement_focus_scale", None); let action_focus_scale_decrement = SimpleAction::new("decrement_focus_scale", None);
action_focus_scale_decrement.connect_activate(clone!(@strong image_ui => move |_, _| { action_focus_scale_decrement.connect_activate(move |_, _| {
image_ui.focus_scale.set_value(image_ui.focus_scale.value() - SCALE_STEP); _sender
})); .send(Message::UI(UIMessage::DecrementFocus))
.unwrap()
});
let _sender = sender.clone();
let mark_focus = SimpleAction::new("mark_focus", None); let mark_focus = SimpleAction::new("mark_focus", None);
mark_focus.connect_activate(clone!(@strong image_ui, @strong state => move |_, _| { mark_focus.connect_activate(move |_, _| {
eprintln! {"Focus Set!"}; _sender.send(Message::MarkFocus).unwrap();
});
let mut state = state.borrow_mut();
state.mark_focus();
state.save();
state.skip();
image_ui.update(&state);
}));
let _sender = sender.clone();
let skip_focus = SimpleAction::new("skip_focus", None); let skip_focus = SimpleAction::new("skip_focus", None);
skip_focus.connect_activate(clone!(@strong image_ui, @strong state => move |_, _| { skip_focus.connect_activate(move |_, _| {
let mut state = state.borrow_mut(); _sender.send(Message::NextImage).unwrap();
state.skip(); });
image_ui.update(&state);
}));
let _sender = sender.clone();
let back_focus = SimpleAction::new("back_focus", None); let back_focus = SimpleAction::new("back_focus", None);
back_focus.connect_activate(clone!(@strong image_ui, @strong state => move |_, _| { back_focus.connect_activate(move |_, _| {
let mut state = state.borrow_mut(); _sender.send(Message::PreviousImage).unwrap();
state.previous(); });
image_ui.update(&state);
}));
image_ui.window.add_action(&action_toggle_neighbour); image_ui.window.add_action(&action_toggle_neighbour);
image_ui.window.add_action(&action_focus_scale_increment); image_ui.window.add_action(&action_focus_scale_increment);
@@ -187,5 +131,12 @@ fn build_ui(app: &Application) {
image_ui.window.add_action(&mark_focus); image_ui.window.add_action(&mark_focus);
image_ui.window.add_action(&skip_focus); image_ui.window.add_action(&skip_focus);
image_ui.window.add_action(&back_focus); image_ui.window.add_action(&back_focus);
image_ui.show(); image_ui.show();
receiver.attach(None, move |msg| {
eprintln!("Received message: {:?}", msg);
state.update(&msg);
image_ui.refresh(&msg, &state);
Continue(true)
});
} }

View File

@@ -1,17 +1,41 @@
use std::collections::HashMap;
use std::fs;
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::Path;
use std::{collections::HashMap, fs::File};
use gtk::{gio::File, prelude::FileExt};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use crate::constants::NONE_STRING_OPTION; use crate::constants::NONE_STRING_OPTION;
#[derive(Debug)]
pub enum Message {
FocusLevelChange(usize),
MarkFocus,
NextImage,
PreviousImage,
UI(UIMessage),
OpenFile(File),
}
// Messages that do not impact state
#[derive(Debug)]
pub enum UIMessage {
OpenFileChooser,
RefreshImages,
ToggleGrid,
DecrementFocus,
IncrementFocus,
ShowGrid(bool),
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct State { pub struct State {
stacks: Vec<AnnotationZStack>, stacks: Vec<AnnotationZStack>,
stack_index: Option<usize>, stack_index: Option<usize>,
pub image_index: Option<usize>, focus_image_index: Option<usize>,
pub file_name: Option<String>, file_name: Option<String>,
pub root_path: Option<String>, pub root_path: Option<String>,
} }
@@ -20,17 +44,71 @@ impl State {
State { State {
stacks: Vec::new(), stacks: Vec::new(),
stack_index: None, stack_index: None,
image_index: None, focus_image_index: None,
file_name: None, file_name: None,
root_path: None, root_path: None,
} }
} }
pub fn set_image_index(&mut self, image_index: Option<usize>) {
self.image_index = image_index; pub fn get_focus_image_index(&self) -> Option<usize> {
return self.focus_image_index;
}
pub fn update(&mut self, msg: &Message) {
match msg {
Message::OpenFile(file) => {
let filename = file.path().expect("Couldn't get file path");
let contents = fs::read_to_string(filename.clone())
.expect("Something went wrong reading the file");
//eprintln!("{}", contents);
let new_dataset: Vec<AnnotationZStack> = serde_json::from_str(&contents).unwrap();
self.replace_foucs_stacks(new_dataset);
self.file_name = filename.clone().as_path().file_name().map(|x| {
x.to_str()
.expect("failed to convert filname to str")
.to_string()
});
self.root_path = filename.clone().as_path().parent().map(|x| {
x.to_str()
.expect("failed to convert filname to str")
.to_string()
});
match (self.root_path.clone(), self.file_name.clone()) {
(Some(root_path), Some(file_name)) => {
let path = Path::new(&root_path).join(Path::new(&file_name));
eprintln!("{:?}", path);
}
(_, _) => {
eprintln!("Path not properly set");
}
}
}
Message::NextImage => {
self.skip();
}
Message::PreviousImage => {
self.previous();
}
Message::MarkFocus => {
self.mark_focus();
self.save();
self.skip();
}
Message::FocusLevelChange(lvl) => {
self.set_focus_image_index(Some(*lvl));
}
Message::UI(_) => {}
}
}
pub fn set_focus_image_index(&mut self, image_index: Option<usize>) {
self.focus_image_index = image_index;
} }
pub fn get_current_annotation_image(&self) -> Option<AnnotationImage> { pub fn get_current_annotation_image(&self) -> Option<AnnotationImage> {
match self.image_index { match self.focus_image_index {
Some(image_index) => { Some(image_index) => {
let stack = self.get_current_focus_stack(); let stack = self.get_current_focus_stack();
match stack { match stack {
@@ -56,7 +134,7 @@ impl State {
if let Some(z_stack) = self.stacks.first() { if let Some(z_stack) = self.stacks.first() {
self.stack_index = Some(0); self.stack_index = Some(0);
self.image_index = if let Some(_) = z_stack.images.first() { self.focus_image_index = if let Some(_) = z_stack.images.first() {
Some(0) Some(0)
} else { } else {
None None
@@ -87,9 +165,9 @@ impl State {
} }
pub fn mark_focus(&mut self) { pub fn mark_focus(&mut self) {
match (self.stack_index, self.image_index) { match (self.stack_index, self.focus_image_index) {
(Some(stack_index), Some(_)) => { (Some(stack_index), Some(_)) => {
self.stacks[stack_index].best_index = self.image_index; self.stacks[stack_index].best_index = self.focus_image_index;
} }
(_, _) => {} (_, _) => {}
} }
@@ -99,16 +177,24 @@ impl State {
match (self.root_path.clone(), self.file_name.clone()) { match (self.root_path.clone(), self.file_name.clone()) {
(Some(root_path), Some(file_name)) => { (Some(root_path), Some(file_name)) => {
let path = Path::new(&root_path).join(Path::new(&file_name)); let path = Path::new(&root_path).join(Path::new(&file_name));
match File::create(path) { match fs::File::create(path) {
Ok(mut file) => { Ok(mut file) => {
use std::time::Instant;
let now = Instant::now();
let contents = let contents =
serde_json::to_string(&self.stacks).expect("Could not serialize data."); serde_json::to_string(&self.stacks).expect("Could not serialize data.");
let elapsed = now.elapsed();
println!("Elapsed: {:.2?}", elapsed);
let now = Instant::now();
match file.write(contents.as_bytes()) { match file.write(contents.as_bytes()) {
Ok(_) => {} Ok(_) => {}
Err(e) => { Err(e) => {
eprintln!("an error occured while saving: {}", e.to_string()); eprintln!("an error occured while saving: {}", e.to_string());
} }
} }
let elapsed = now.elapsed();
println!("Elapsed: {:.2?}", elapsed);
} }
Err(e) => { Err(e) => {
eprintln!("{}", e.to_string()); eprintln!("{}", e.to_string());

View File

@@ -2,14 +2,21 @@ use std::{path::Path, sync::Arc};
use adw::{Application, ApplicationWindow, HeaderBar, SplitButton}; use adw::{Application, ApplicationWindow, HeaderBar, SplitButton};
use gtk::{ use gtk::{
traits::{BoxExt, GridExt, GtkApplicationExt, RangeExt, ScaleExt, WidgetExt}, glib::Sender,
ActionBar, AspectFrame, Box, Button, Grid, Image, Orientation, PositionType, Scale, Separator, traits::{
ToggleButton, BoxExt, ButtonExt, DialogExt, FileChooserExt, GridExt, GtkApplicationExt, GtkWindowExt,
RangeExt, ScaleExt, ToggleButtonExt, WidgetExt,
},
ActionBar, AspectFrame, Box, Button, FileChooserAction, FileChooserDialog, FileFilter, Grid,
Image, Orientation, PositionType, ResponseType, Scale, Separator, ToggleButton,
}; };
use crate::{ use crate::{
constants::{MARGIN_LEFT, MARGIN_RIGHT_SCALE_ADDITIONAL, MARGIN_TOP, TOGGLE_NEIGHBOURS_TEXT}, constants::{
state::State, MARGIN_LEFT, MARGIN_RIGHT_SCALE_ADDITIONAL, MARGIN_TOP, SCALE_STEP, TOGGLE_NEIGHBOURS_TEXT,
TOGGLE_NEIGHBOURS_TEXT_TOGGLED,
},
state::{Message, State, UIMessage},
AnnotationImage, MARGIN_BOTTOM, AnnotationImage, MARGIN_BOTTOM,
}; };
@@ -24,15 +31,16 @@ pub struct ImageUI {
pub focus_neighbours_grid: Arc<Grid>, pub focus_neighbours_grid: Arc<Grid>,
pub focus_neighbours_aspect_frame: Arc<AspectFrame>, pub focus_neighbours_aspect_frame: Arc<AspectFrame>,
pub neighbour_toggle_button: Arc<ToggleButton>, pub neighbour_toggle_button: ToggleButton,
pub open_button: Arc<SplitButton>, pub open_button: Arc<SplitButton>,
pub back_button: Arc<Button>, pub back_button: Arc<Button>,
pub skip_button: Arc<Button>, pub skip_button: Arc<Button>,
pub focus_button: Arc<Button>, pub focus_button: Arc<Button>,
pub sender: Sender<Message>,
} }
impl ImageUI { impl ImageUI {
pub fn new(app: &Application) -> ImageUI { pub fn new(app: &Application, sender: Sender<Message>) -> ImageUI {
let mut builder = ImageUIBuilder::default(); let mut builder = ImageUIBuilder::default();
let application_vertical_widget = Arc::new(Box::new(Orientation::Vertical, 0)); let application_vertical_widget = Arc::new(Box::new(Orientation::Vertical, 0));
@@ -48,8 +56,8 @@ impl ImageUI {
builder builder
.application_vertical_widget(application_vertical_widget.clone()) .application_vertical_widget(application_vertical_widget.clone())
.window(window); .window(window)
.sender(sender);
// TODO: move into builder // TODO: move into builder
ImageUI::build_header(&mut builder, application_vertical_widget.clone()); ImageUI::build_header(&mut builder, application_vertical_widget.clone());
ImageUI::build_center(&mut builder, application_vertical_widget.clone()); ImageUI::build_center(&mut builder, application_vertical_widget.clone());
@@ -165,12 +173,10 @@ impl ImageUI {
.build(), .build(),
); );
let neighbour_toggle_button = Arc::new( let neighbour_toggle_button = ToggleButton::builder()
ToggleButton::builder()
.label(TOGGLE_NEIGHBOURS_TEXT) .label(TOGGLE_NEIGHBOURS_TEXT)
.width_request(158) .width_request(158)
.build(), .build();
);
let focus_skip_link_widget = Box::builder() let focus_skip_link_widget = Box::builder()
.css_classes(vec!["linked".to_string()]) .css_classes(vec!["linked".to_string()])
@@ -179,7 +185,7 @@ impl ImageUI {
focus_skip_link_widget.append(skip_button.as_ref()); focus_skip_link_widget.append(skip_button.as_ref());
focus_skip_link_widget.append(focus_button.as_ref()); focus_skip_link_widget.append(focus_button.as_ref());
bottom_toolbar.pack_start(neighbour_toggle_button.as_ref()); bottom_toolbar.pack_start(&neighbour_toggle_button);
bottom_toolbar.pack_end(&focus_skip_link_widget); bottom_toolbar.pack_end(&focus_skip_link_widget);
application_vertical_widget.append(&bottom_toolbar); application_vertical_widget.append(&bottom_toolbar);
@@ -195,7 +201,67 @@ impl ImageUI {
self.window.show(); self.window.show();
} }
pub fn update(&self, state: &State) { pub fn refresh(&self, msg: &Message, state: &State) {
match msg {
Message::UI(UIMessage::ToggleGrid) => {
let curent_state = self.neighbour_toggle_button.is_active();
self.neighbour_toggle_button.set_active(!curent_state)
}
Message::UI(UIMessage::IncrementFocus) => {
self.focus_scale
.set_value(self.focus_scale.value() + SCALE_STEP);
}
Message::UI(UIMessage::DecrementFocus) => {
self.focus_scale
.set_value(self.focus_scale.value() - SCALE_STEP);
}
Message::UI(UIMessage::ShowGrid(true)) => {
self.focus_neighbours_aspect_frame
.set_child(Some(self.focus_neighbours_grid.as_ref()));
self.neighbour_toggle_button
.set_label(TOGGLE_NEIGHBOURS_TEXT_TOGGLED);
}
Message::UI(UIMessage::ShowGrid(false)) => {
self.focus_neighbours_aspect_frame
.set_child(Some(self.individual.as_ref()));
self.neighbour_toggle_button
.set_label(TOGGLE_NEIGHBOURS_TEXT);
}
Message::UI(UIMessage::OpenFileChooser) => {
let file_chooser_action = FileChooserAction::Open;
let buttons = [("Open", ResponseType::Ok), ("Cancel", ResponseType::Cancel)];
let filter = FileFilter::new();
filter.add_pattern(r"*.json");
let file_chooser = FileChooserDialog::new(
Some("Chose a data file!"),
Some(self.window.as_ref()),
file_chooser_action,
&buttons,
);
file_chooser.set_select_multiple(false);
file_chooser.set_filter(&filter);
let _sender = self.sender.clone();
file_chooser.connect_response(
move |dialog: &FileChooserDialog, response: ResponseType| {
if response == ResponseType::Ok {
let file = dialog.file().expect("Couldn't get file");
eprintln!("Open");
_sender.send(Message::OpenFile(file)).unwrap();
}
dialog.close();
},
);
file_chooser.show();
}
Message::UI(UIMessage::RefreshImages)
| Message::NextImage
| Message::PreviousImage
| Message::MarkFocus
| Message::FocusLevelChange(_)
| Message::OpenFile(_) => {
match ( match (
state.get_current_annotation_image(), state.get_current_annotation_image(),
state.root_path.clone(), state.root_path.clone(),
@@ -203,10 +269,14 @@ impl ImageUI {
(Some(annotation_image), Some(base_path)) => { (Some(annotation_image), Some(base_path)) => {
self.update_image(&annotation_image, base_path) self.update_image(&annotation_image, base_path)
} }
(_, _) => {} (_, _) => {
// TODO: write error message
}
} }
self.update_focus_scale(&state); self.update_focus_scale(&state);
} }
}
}
fn update_image(&self, annotation_image: &AnnotationImage, base_path: String) { fn update_image(&self, annotation_image: &AnnotationImage, base_path: String) {
self.individual.set_from_file(Some( self.individual.set_from_file(Some(
Path::new(&base_path).join(Path::new(&annotation_image.image_path)), Path::new(&base_path).join(Path::new(&annotation_image.image_path)),
@@ -239,7 +309,7 @@ impl ImageUI {
.set_margin_end(MARGIN_RIGHT_SCALE_ADDITIONAL); .set_margin_end(MARGIN_RIGHT_SCALE_ADDITIONAL);
} }
if let Some(current_value) = state.image_index { if let Some(current_value) = state.get_focus_image_index() {
self.focus_scale.set_value(current_value as f64); self.focus_scale.set_value(current_value as f64);
} else { } else {
self.focus_scale.set_value(f64::floor(max / 2.0)); self.focus_scale.set_value(f64::floor(max / 2.0));