From 06c34950a7427fb407dfc5855be97713faedcb1f Mon Sep 17 00:00:00 2001 From: Hannes Kuchelmeister Date: Wed, 16 Feb 2022 17:23:22 +0100 Subject: [PATCH] use message passing instead of Arc to avoid panics --- src/main.rs | 185 +++++++++++++++++------------------------------ src/state/mod.rs | 108 ++++++++++++++++++++++++--- src/ui/mod.rs | 122 ++++++++++++++++++++++++------- 3 files changed, 261 insertions(+), 154 deletions(-) diff --git a/src/main.rs b/src/main.rs index 9eaa4b4..6721968 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,28 +1,19 @@ #[macro_use] extern crate derive_builder; +mod constants; mod state; mod ui; -mod constants; -pub use crate::state::AnnotationImage; pub use crate::constants::MARGIN_BOTTOM; -use crate::state::AnnotationZStack; +pub use crate::state::AnnotationImage; 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 constants::{TOGGLE_NEIGHBOURS_TEXT_TOGGLED, TOGGLE_NEIGHBOURS_TEXT, SCALE_STEP}; -use glib::clone; 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() { let application = Application::builder() @@ -39,147 +30,100 @@ fn main() { application.run(); } - 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 // ////////////////// - - let image_ui = Arc::new(ImageUI::new(app)); + //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; - state.borrow_mut().set_image_index(Some(index)); - image_ui.update(&state.borrow()); - })); + _sender.send(Message::FocusLevelChange(index)).unwrap(); + }); //////////////////// // BOTTOM TOOLBAR // /////////////////// image_ui.back_button.connect_clicked(|button| { - button.activate_action("win.back_focus", None) - .expect("The action does not exist."); + button + .activate_action("win.back_focus", None) + .expect("The action does not exist."); }); image_ui.skip_button.connect_clicked(|button| { - button.activate_action("win.skip_focus", None) - .expect("The action does not exist."); + button + .activate_action("win.skip_focus", None) + .expect("The action does not exist."); }); image_ui.focus_button.connect_clicked(|button| { - button.activate_action("win.mark_focus", None) - .expect("The action does not exist."); + button + .activate_action("win.mark_focus", None) + .expect("The action does not exist."); }); - let focus_image = image_ui.individual.clone(); - image_ui.neighbour_toggle_button.connect_toggled( - clone!(@strong image_ui => move |x| match x.is_active() { - true => { - image_ui.focus_neighbours_aspect_frame.set_child(Some(image_ui.focus_neighbours_grid.as_ref())); - 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); - } - }), - ); + let _sender = sender.clone(); + image_ui.neighbour_toggle_button.connect_toggled(move |x| { + _sender + .send(Message::UI(UIMessage::ShowGrid(x.is_active()))) + .unwrap(); + }); - image_ui.open_button.connect_clicked(clone!(@strong image_ui, @strong state => move |_| { - // TODO: actually open and load data - - - 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 = 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 = 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(); - - })); + let _sender = sender.clone(); + image_ui.open_button.connect_clicked(move |_| { + _sender + .send(Message::UI(UIMessage::OpenFileChooser)) + .unwrap(); + }); //////////////////////// // Keyboard Shortcuts // //////////////////////// - + let _sender = sender.clone(); let action_toggle_neighbour = SimpleAction::new("toggle_neighbour", None); - action_toggle_neighbour.connect_activate(clone!(@strong image_ui => move |_, _| { - image_ui.neighbour_toggle_button.set_active(!image_ui.neighbour_toggle_button.is_active()); - })); + action_toggle_neighbour + .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); - action_focus_scale_increment.connect_activate(clone!(@strong image_ui => move |_, _| { - image_ui.focus_scale.set_value(image_ui.focus_scale.value() + SCALE_STEP); - })); + action_focus_scale_increment.connect_activate(move |_, _| { + _sender + .send(Message::UI(UIMessage::IncrementFocus)) + .unwrap() + }); + let _sender = sender.clone(); let action_focus_scale_decrement = SimpleAction::new("decrement_focus_scale", None); - action_focus_scale_decrement.connect_activate(clone!(@strong image_ui => move |_, _| { - image_ui.focus_scale.set_value(image_ui.focus_scale.value() - SCALE_STEP); - })); + action_focus_scale_decrement.connect_activate(move |_, _| { + _sender + .send(Message::UI(UIMessage::DecrementFocus)) + .unwrap() + }); + let _sender = sender.clone(); let mark_focus = SimpleAction::new("mark_focus", None); - mark_focus.connect_activate(clone!(@strong image_ui, @strong state => move |_, _| { - eprintln! {"Focus Set!"}; - - let mut state = state.borrow_mut(); - state.mark_focus(); - state.save(); - state.skip(); - image_ui.update(&state); - })); + mark_focus.connect_activate(move |_, _| { + _sender.send(Message::MarkFocus).unwrap(); + }); + let _sender = sender.clone(); let skip_focus = SimpleAction::new("skip_focus", None); - skip_focus.connect_activate(clone!(@strong image_ui, @strong state => move |_, _| { - let mut state = state.borrow_mut(); - state.skip(); - image_ui.update(&state); - })); + skip_focus.connect_activate(move |_, _| { + _sender.send(Message::NextImage).unwrap(); + }); + let _sender = sender.clone(); let back_focus = SimpleAction::new("back_focus", None); - back_focus.connect_activate(clone!(@strong image_ui, @strong state => move |_, _| { - let mut state = state.borrow_mut(); - state.previous(); - image_ui.update(&state); - })); + back_focus.connect_activate(move |_, _| { + _sender.send(Message::PreviousImage).unwrap(); + }); image_ui.window.add_action(&action_toggle_neighbour); 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(&skip_focus); image_ui.window.add_action(&back_focus); + image_ui.show(); -} \ No newline at end of file + receiver.attach(None, move |msg| { + eprintln!("Received message: {:?}", msg); + state.update(&msg); + image_ui.refresh(&msg, &state); + Continue(true) + }); +} diff --git a/src/state/mod.rs b/src/state/mod.rs index 6abea46..6f83945 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -1,17 +1,41 @@ +use std::collections::HashMap; +use std::fs; use std::io::Write; use std::path::Path; -use std::{collections::HashMap, fs::File}; +use gtk::{gio::File, prelude::FileExt}; use serde::{Deserialize, Serialize}; use serde_json::Value; 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)] pub struct State { stacks: Vec, stack_index: Option, - pub image_index: Option, - pub file_name: Option, + focus_image_index: Option, + file_name: Option, pub root_path: Option, } @@ -20,17 +44,71 @@ impl State { State { stacks: Vec::new(), stack_index: None, - image_index: None, + focus_image_index: None, file_name: None, root_path: None, } } - pub fn set_image_index(&mut self, image_index: Option) { - self.image_index = image_index; + + pub fn get_focus_image_index(&self) -> Option { + 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 = 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) { + self.focus_image_index = image_index; } pub fn get_current_annotation_image(&self) -> Option { - match self.image_index { + match self.focus_image_index { Some(image_index) => { let stack = self.get_current_focus_stack(); match stack { @@ -56,7 +134,7 @@ impl State { if let Some(z_stack) = self.stacks.first() { 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) } else { None @@ -87,9 +165,9 @@ impl State { } 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(_)) => { - 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()) { (Some(root_path), Some(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) => { + use std::time::Instant; + let now = Instant::now(); let contents = 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()) { Ok(_) => {} Err(e) => { eprintln!("an error occured while saving: {}", e.to_string()); } } + let elapsed = now.elapsed(); + println!("Elapsed: {:.2?}", elapsed); } Err(e) => { eprintln!("{}", e.to_string()); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index fcd4be5..14b4746 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -2,14 +2,21 @@ use std::{path::Path, sync::Arc}; use adw::{Application, ApplicationWindow, HeaderBar, SplitButton}; use gtk::{ - traits::{BoxExt, GridExt, GtkApplicationExt, RangeExt, ScaleExt, WidgetExt}, - ActionBar, AspectFrame, Box, Button, Grid, Image, Orientation, PositionType, Scale, Separator, - ToggleButton, + glib::Sender, + traits::{ + 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::{ - constants::{MARGIN_LEFT, MARGIN_RIGHT_SCALE_ADDITIONAL, MARGIN_TOP, TOGGLE_NEIGHBOURS_TEXT}, - state::State, + constants::{ + MARGIN_LEFT, MARGIN_RIGHT_SCALE_ADDITIONAL, MARGIN_TOP, SCALE_STEP, TOGGLE_NEIGHBOURS_TEXT, + TOGGLE_NEIGHBOURS_TEXT_TOGGLED, + }, + state::{Message, State, UIMessage}, AnnotationImage, MARGIN_BOTTOM, }; @@ -24,15 +31,16 @@ pub struct ImageUI { pub focus_neighbours_grid: Arc, pub focus_neighbours_aspect_frame: Arc, - pub neighbour_toggle_button: Arc, + pub neighbour_toggle_button: ToggleButton, pub open_button: Arc, pub back_button: Arc